First, we can try simple input to see what happens there:
mkDList [1] = first where (first,last) := go last [1] first = let { prev=last; next=first; -- prev = last x=1; xs=[] } -- next = first in go prev (x:xs) next = let this := (DLNode prev 1 rest) -- a node is constructed, with -- the two pointers still pointing into the unknown (rest,last2) := go this [] next = (next,this) -- rest := next -- last2 := this in (this,last2) -- first := this -- last := last2
let in Haskell is recursive: the same name can appear on both the left and right sides of the equation sign and will refer to the same object.
First, go is called with last , [1] and first . Both last and first do not yet refer to any value; they exist only as an identity, a kind of "named pointers" for still empty boxes, boxes that are not yet filled with values.
Passing into the courage of go , both names are “smoothed out”, and then the final value is returned (this, last2) ; then the template (first, last) corresponds to this value. Thus, last finally gets its value, although it was used as a named name inside go invocations. This is what is called node binding: imagine the arrow going from last to go calls, returning to it from the depths; and the same with first ; thus creating equivalence chains:
first := this = (DLNode prev 1 rest) last := last2 := this prev = last rest := next = first
The above follows a somewhat imperative view of Haskell's semantics as a single-assignment language. The operator := used as a pseudo-code to provide a visual key to the fact that the value is calculated using the expression on the right and is "passed" to the variables in the template to the left of the equal sign (when this template matches the calculated value).
Actually, the name "next" does not fit, because we just go through the first node to the end to use it as the last node of the next node:
mkDList xs@ (_:_) = first where (first,last) = go last xs first go :: DList a -> [a] -> DList a -> (DList a, DList a) go prev (x:xs) first = (this, last) -- (this , last ) where this := DLNode prev x rest ( rest, last) := go this xs first go prev [] first = (first, -- first --> rest of the last node prev)
This can be "described" by the equivalent definition of Prolog:
mkDList( [X|XS], First) :- % mkDList( in, out) go( Last, [X|XS], First, First, Last). % go( in, in, in, out, out) go( Prev, [X|XS], First, This, Last) :- This = dlnode(Prev, X, Rest), go( This, XS, First, Rest, Last). % fill the Rest, return Last go( Prev, [], First, First, Prev).
Really,
?- mkDList([1,2,3],X). X = dlnode(_S1, 1, _S2), % where _S2 = dlnode(X, 2, _S1), _S1 = dlnode(_S2, 3, X).