2

Conversion of Lists to Linked Lists

I'm trying to convert a list to a singly linked list (not sure if doubly linked lists are possible anyway) in standard ML. However, I am not sure how this is done, but will show my attempt so far.

datatype 'a mlist = Nil
        | Cons of 'a * 'a mlist ref

fun listToLL [] = Nil
    | listToLL (x::xs) = Cons(x, (ref (listToLL xs)))

I have created a linked list over datatype first, but I'm confused by the 'ref' function. I want to avoid making the linked list into a lazy list, so the ref is there for me to explicitly make the next sequence a reference of the previous one only. But the problem is that this function does not make use of the linked list data type at all, and works even if I don't use this linked list definition. So is my implementation actually correct?

As a side question, is the only thing distinguishing linked lists from lazy lists the 'ref' function? Judging from their datatype definitions, this seem to be that way.

Joining two linked lists together

fun joining (mlpx, mlpy) = 
    case !mlpx of
        Nil => mlpx := mlpy
    |   Cons(_, mlp) => joining(mlp, mlpy)          


fun join (mlpx, mlpy) = 
    let
        val mlp = ref mlpx
    in
        joining(mlp, mlpy);
        !mlp
    end;    

For the second operation, I'm very confused by the need for ref in the second join function at all. When I handle a linked list, do I expect that I handle a reference or do I handle the entire Cons structure that contains the reference from within? I don't get why we can't just use the joining function by itself as it seems to be really sufficient. Why is there a need to create a ref in the join function?

oldselflearner1959
  • 633
  • 1
  • 5
  • 22

2 Answers2

2

I have created a linked list over datatype first, but I'm confused by the 'ref' function. I want to avoid making the linked list into a lazy list [...]

[...]

As a side question, is the only thing distinguishing linked lists from lazy lists the 'ref' function?

Lazy lists are quite different, because they require "suspending" a computation, typically by creating a closure (for example, see this answer for an implementation of lazy lists in SML). Your implementation isn't lazy. The usage of ref simply allows mutability.

But the problem is that this function does not make use of the linked list data type at all, and works even if I don't use this linked list definition. So is my implementation actually correct?

Your implementation of listToLL is correct. I'm not sure I understand what you mean with the rest of this question. Your function does use the linked list data type, because it uses the constructors Nil and Cons.

I'm very confused by the need for ref in the second join function at all. When I handle a linked list, do I expect that I handle a reference or do I handle the entire Cons structure that contains the reference from within?

Your confusion here is very natural. I remember I became very confused by the exact same issue when I first attempted to implement a mutable linked list in SML.

The join implementation you provided creates an extra ref to cleanly handle the case where the first list is empty, because the helper function joining operates on objects of type 'a mlist ref rather than 'a mlist. It is possible to avoid creating this extra ref, however. Here's how I would do it:

(* find the last ref of a linked list *)
fun getLast mlp =
  case !mlp of
    Nil => mlp
  | Cons (_, mlp') => getLast mlp'

fun join (mlpx, mlpy) =
  case mlpx of
    Nil => mlpy
  | Cons (_, mlp) => (getLast mlp := mlpy; mlpx)
Community
  • 1
  • 1
Sam Westrick
  • 1,248
  • 1
  • 7
  • 9
1

convert a list to a singly linked list

Lists in SML are already implemented as linked lists.

When you define a data type like

datatype 'a mylist = Nil | Cons of 'a * 'a mylist

you're creating a type that is isomorphic to the built-in 'a list type with [] (nil) and :: (cons).

It seems that your attempt is rather at how to make a mutable linked list using references.

I want to avoid making the linked list into a lazy list

As Sam Westrick pointed out, lazy lists are typically made using closures:

datatype 'a lazylist = Nil | Cons of 'a * (unit -> 'a lazylist)

Edit: See his example of suspensions [stackoverflow.com] for more details.

not sure if doubly linked lists are possible anyway

Both mutable linked lists and doubly linked lists are possible using references, but I wouldn't recommend this approach in SML. If you need to traverse a list forwards and backwards, or if you need to update it underway, you can do so purely functionally using zippers:

The original article has examples in OCaml and the LYAH chapter has examples in Haskell.

I'd like to see some good examples in Standard ML, but I haven't found any.

sshine
  • 15,635
  • 1
  • 41
  • 66
  • 1
    Just a note on laziness, for those who are curious (Simon, I'm guessing you probably just omitted this for brevity): typically, lazy evaluation guarantees that the expression is evaluated at most once. So, while it is *possible* to implement laziness with a function (namely, the memoization function), the function type itself doesn't provide this guarantee. For example, in this definition of lazylists, simply wrapping the tail of the list in `fn () => ...` won't be "lazy". – Sam Westrick Jan 08 '18 at 16:53