[Haskell-cafe] Re: Optimization problem

Ross Paterson ross at soi.city.ac.uk
Sun Sep 17 19:23:11 EDT 2006

On Thu, Sep 14, 2006 at 07:51:59PM +0200, Bertram Felgenhauer wrote:
> It's a result of thinking about lazy evaluation, and
> especially lazy patterns (and let bindings) for some time. A wiki article
> that helped me a lot to understand these is
>   http://www.haskell.org/hawiki/TyingTheKnot
> I'd like to point out the trustList function there which uses the idea
> of encoding the structure of a term and its actual values in different
> arguments, i.e. a blueprint.

One view of your device is as separating the shape (blueprint) from the
contents, e.g. one can split a finite map type

	data Map k a  = Node !Int k a (Map k a) (Map k a) | Leaf

into a pair of types

	data MapShape k = SNode !Int k (MapShape k) (MapShape k) | SLeaf
	data MapData a = DNode a (MapData a) (MapData a) | DLeaf

(We don't even need DLeaf, as it's never examined.)
We need functions

	fill :: a -> MapShape k -> MapData a
	update :: Ord k => k -> (a -> a) -> MapShape k ->
		MapData a -> MapData a
	insert :: Ord k => k -> MapShape k ->
		(MapShape k, MapData a -> (a, MapData a))

In a dependently typed language we could parameterize MapData with shapes
to give more precise types:

	fill :: a -> forall s :: MapShape k. MapData s a
	update :: Ord k => k -> (a -> a) ->
		forall s :: MapShape k.  MapData s a -> MapData s a
	insert :: Ord k => k -> forall s :: MapShape k.
		(exists s' :: MapShape k, MapData s' a -> (a, MapData s a))

hinting that the function returned by insert reverses the effect of
the insertion.  It's not possible to code this with GADTs, because
existentials are incompatible with irrefutable patterns, at least
in GHC.

Anyway, splitSeq then becomes

	splitSeq :: Ord a => [(a,b)] -> [(a,[b])]
	splitSeq = fst . splitSeq' SLeaf

	splitSeq' :: Ord a => MapShape a -> [(a,b)] -> ([(a,[b])], MapData [b])
	splitSeq' bp [] = ([], fill [] bp)
	splitSeq' bp ((a,b):xs)
	  | member a bp = let (l, m)         = splitSeq' bp xs
			  in  (l, update a (b:) bp m)
	  | otherwise   = let (bp', extract) = insert a bp
			      (l, m')        = splitSeq' bp' xs
			      (bs, m)        = extract m'
			  in  ((a, b:bs) : l, m)

Again, no knot-tying is required.

To prove that (even for partial and infinite lists ps)

	splitSeq ps = [(a, seconds a ps) | a <- nub ps]


	seconds :: Eq a => a -> [(a,b)] -> [b]
	seconds a ps = [b | (a', b) <- ps, a' == a]

we need another function

	get :: Ord k => k -> MapShape k -> MapData a -> a

and the properties

	member k s  =>
		get k s (fill v s) = v

	member k s  =>
		get k s (update k f s m) = f (get k s m)
	member k s /\ member x s /\ x /= k  =>
		get x s (update k f s m) = get x s m

	not (member k s) /\ insert k s = (s', extract)  =>
		member x s' = member x s || x == k
	not (member k s) /\ insert k s = (s', extract)  =>
		fst (extract m) = get k s' m
	not (member k s) /\ insert k s = (s', extract) /\ member x s  =>
		get x s (snd (extract m)) = get x s' m

Then we can establish, by induction on the input list,

(1)	fst (splitSeq' s ps) =
		[(a, seconds a ps) | a <- nub ps, not (member a s)]
(2)	member x s  =>
		get x s (snd (splitSeq' s ps)) = seconds x ps

This is enough to sustain the induction and obtain the desired property,
but it's a bit inelegant not to have an exact characterization of the
second component.  It seems essential to observe the map data only
though get.

More information about the Haskell-Cafe mailing list