The error you get is caused by the SML compiler not being able to infer what type of tuple you have. #1 and #2 are not functions but macros that work on tuples of arbitrary type, as long as they have two elements. So a quick way to overcome this problem is to add type annotation,
fun getDirectNodes(startNode, Tuples : (int * int) list, list) = ...
But since you posted so much of your solution, I'd like to give some general feedback on it:
reverse
Your implementation of reverse does a few things wrong:
When you write if L = [] ..., you force L to be an equality type, ''a. This may seem odd, since you're just testing that L is empty, but before L = [] can be a valid expression, its elements must also be comparable for equality. You can solve this via pattern matching or by using the function List.null (which uses pattern matching) to avoid the equality type restriction.
It uses hd and tl instead of pattern matching; these functions are partial, which means they can crash at run-time when not used properly. You can avoid them by using pattern matching on the empty and the non-empty list.
It uses @ recursively which is very inefficient: Your algorithm is O(n²) because the left-hand side of @ takes linear time to resolve for each recursive call, i.e. geometric complexity:
reverse [1,2,3,4]
~> reverse [2,3,4] @ [1]
~> reverse [3,4] @ [2] @ [1]
~> reverse [4] @ [3] @ [2] @ [1]
~> reverse [] @ [4] @ [3] @ [2] @ [1]
At this point reverse has used O(n) stack space.
~> [] @ [4] @ [3] @ [2] @ [1]
~> [4] @ [3] @ [2] @ [1] (* 1 recursive call in @ *)
~> [4,3] @ [2] @ [1] (* 2 recursive calls in @ *)
~> [4,3,2] @ [1] (* 3 recursive calls in @ *)
~> [4,3,2,1] (* 4 recursive calls in @ *)
And at this point reverse has used O(n + (n-1) + ... + 1) = O(n²) recursive calls.
There's actually a built-in function called rev.
It is implemented like this:
fun rev xs =
let fun rev_helper [] xs_bw = xs_bw
| rev_helper (x::xs) xs_bw = rev_helper xs (x::xs_bw)
in rev_helper xs [] end
and calling it looks like:
rev [1,2,3,4]
~> rev_helper [1,2,3,4] []
~> rev_helper [2,3,4] [1] (* 1 recursive call *)
~> rev_helper [3,4] [2,1] (* 1 recursive call *)
~> rev_helper [4] [3,2,1] (* 1 recursive call *)
~> rev_helper [] [4,3,2,1] (* 1 recursive call *)
~> [4,3,2,1]
which uses heap memory instead of stack memory, and O(n) recursive calls.
getDirectNodes
Here is a non-exhaustive list of comments:
The same thing wrt. equality types applies here wrt. Tuples = [].
Having list as an accumulated result is neat! I might have called it something more descriptive, just as I would call Tuples something like edges to describe its content rather than its type.
While using an accumulated result like list is neat, it means that your function takes as input an empty list. What if the caller feeds it a non-empty list? Exposing this extra argument leaves room for errors, so hide it in an inner function, like I did with rev_helper.
Use pattern matching instead of hd and tl.
Your use of reverse seems meaningful: You've experienced that list ends up in reverse. But instead of calling reverse on list on every recursive call, do it once at the very end (when Tuples is empty).
Given this advice, here is a variation of your code:
fun getDirectNodes (startNode, []) = []
| getDirectNodes (startNode, (x, endNode) :: edges) =
if x = startNode
then endNode :: getDirectNodes (startNode, edges)
else getDirectNodes (startNode, edges)
And a variation that uses an inner tail-recursive function and a single rev at the end:
fun getDirectNodes (startNode, edges) =
let fun helper ([], endNodes) = endNodes
| helper ((x, endNode) :: edges, endNodes) =
if x = startNode
then helper (edges, endNode :: endNodes)
else helper (edges, endNodes)
in rev (helper (edges, [])) end
Here is how I would make it using higher-order functions:
fun getDirectNodes (startNode, edges) =
List.map #2 (List.filter (fn (x, _) => x = startNode) edges)
The reason I don't get a warning wrt. my use of #2 here, in spite of not having any type annotations, is because I'm pattern matching on the elements of edges in the code fn (x, _) => .... This constrains edges to a list of 2-tuples.
Running this:
- getDirectNodes (1, [(1,2),(1,3),(2,3),(2,4)]);
> val it = [2, 3] : int list