views:

1029

answers:

5

How does one go about doing doubly linked lists in a pure functional language? That is, something like Haskell where you're not in a Monad so you don't have mutation. Is it possible? (Singly linked list is obviously pretty easy).

+14  A: 

There are a number of approaches.

If you don't want to mutate the doubly-linked list once you have constructed it you can just 'tie the knot' by relying on laziness.

http://www.haskell.org/haskellwiki/Tying_the_Knot

If you want a mutable doubly-linked list you need to fake references somehow -- or use real ones -- ala the trick proposed by Oleg Kiseylov and implemented here:

http://hackage.haskell.org/packages/archive/liboleg/2009.9.1/doc/html/Data-FDList.html

Interestingly, note that the former relies fundamentally upon laziness to succeed. You ultimately need mutation or laziness to tie the knot.

Edward Kmett
+4  A: 

Just an addendum to Edward Kmett's answer: the problem of "backing up" in a recursive data structure (be it a list, tree, etc.) is often solved using a helper data structure that functional folks call a Zipper.

jberryman
+30  A: 

In a pure functional language, a doubly-linked list is not that interesting. The idea of a doubly linked list is to be able to grab a node and go in either direction, or to be able to splice into the middle of a list. In a pure functionaly language you probably are better off with one of these two data structures:

  • A singly linked list with a pointer in the middle, from which you can go either left or right (a variant of Huet's "Zipper")

  • A finger tree, which is a mind-blowing data structure invented by Ralf Hinze and Ross Paterson.

I'm a big fan of the zipper; it's useful in a lot of situations.

Norman Ramsey
+1 for the zipper. +1 for the finger tree. Oops, doesn't work in the vote system... :)
Pascal Cuoq
I definitely agree that those are far better options. =)
Edward Kmett
Finger trees... interesting... :)
gnucom
+6  A: 

I would reiterate musicfan's question: "what exactly do you need this for?" As Norman Ramsey notes: if you need multi-directional traversal, then zippers are easier; if you need fast splicing, finger trees work well.

But, just to see how it looks...

import Control.Arrow
import Data.List

data LNode a = LNode { here :: a, prev :: LList a, next :: LList a }
type LList a = Maybe (LNode a)

toList :: LList a -> [a]
toList = unfoldr $ fmap $ here &&& next

fromList :: [a] -> LList a
fromList l = head nodes where
    nodes = scanr ((.) Just . uncurry LNode) Nothing $ zip l $ Nothing : nodes

append :: LList a -> LList a -> LList a
append = join Nothing where
    join k (Just a) b = a' where
        a' = Just $ a { prev = k, next = join a' (next a) b }
    join k _ (Just b) = b' where
        b' = Just $ b { prev = k, next = join b' Nothing (next b) }
    join _ _ _ = Nothing
ephemient
+1  A: 

In OCaml, for circular simply linked list you can always do something like that:

type t = { a : t Lazy.t }

let cycle n =
  let rec start = {a = lazy (aux n) }
  and aux = function
    | 0 -> start
    | n -> { a = lazy (aux (n-1))}
  in start

For doubly linked lists, I imagine it's possible to do something similar. But you have to rely on laziness and on records being friendly structures when it comes to typing. Quick and dirty cyclic doubly linked list:

  type 'a t = { data : 'a; before : 'a t Lazy.t; after : 'a t Lazy.t }

  let of_list l =
    match l with [] -> assert false | hd::tl ->
    let rec start = { data = hd; before = last; after = next }
    and couple = lazy (aux (lazy start) hd) 
    and next = lazy (Lazy.force (fst (Lazy.force couple)))
    and last = lazy (Lazy.force (snd (Lazy.force couple)))
    and aux before = function
    | [] -> (lazy start), before
    | hd::tl -> let rec current = lazy { data = hd; before = before; after = after }
                   and couple = lazy (aux current tl) 
                   and after = lazy (Lazy.force (fst (Lazy.force couple)))
                   and last = lazy (Lazy.force (snd (Lazy.force couple))) in
                   current, last
    in start
Guillaume Yziquel