Reputation: 133
I am trying to write a function in SML that is going to return a subsequence of the elements of a list, as well as a sequence of the rest of the elements of the list. So, my function is
fun subseq (left,right,0) = (left,right) |
subseq (left,h::t,len) =
let
val (left,right) = subseq ([],t,len-1)
in
(h::left,right)
end;
fun subseqMain (mainList, length) = subseq ([],mainList,length);
My question is how to make this function more efficient, by converting it to a tail recursive one, without using the @ operator.
Thank you for your time!
Upvotes: 1
Views: 953
Reputation: 4049
The easiest way to do this is to use rev
. Basically, the idea is to cons onto left
in each iteration. Your left subsequence will then be in reverse order, which is where rev
comes in. It is unfortunate that you have to make two passes over the subsequence, but there really isn't anything else that you can do that will be much faster. The performance gain that you will achieve is dependent on what compiler that you are using. SML/NJ performs a CPS transformation, so non-tail calls incur a small amount of heap allocation as opposed to stack allocation, so you pay some GC overhead. You can see throughout the SML/NJ Basis that they do this sort of thing in a few places, for example, the definition of partition
is
fun partition pred l = let
fun loop ([],trueList,falseList) = (rev trueList, rev falseList)
| loop (h::t,trueList,falseList) =
if pred h then loop(t, h::trueList, falseList)
else loop(t, trueList, h::falseList)
in loop (l,[],[]) end
Your other option is to manually transform this into Continuation Passing Style (CPS), where you basically wrap the "rest of the computation" up in a function, which we call the continuation. When a function finishes, it simply passes its result off to the incoming continuation. Explaining CPS in its entirety is probably beyond the scope of this answer, so I would suggest doing some Googling to get a real grasp on it. The partition
example might look something like this:
fun partition pred l = let
fun loop([], k) = k([], [])
|loop(h::t, k) = loop(t, fn (tr, fl) => if pred h then k(h::tr, fl) else k(tr, h::fl))
in loop(l, fn res => res) end
That said, if you are not going to convert to tail recursion, there is a more straightforward way of implementing what you have:
fun subseq(l, 0) = ([], l)
|subseq(h::t, i) =
let val (l, r) = subseq(t, i-1)
in (h::l, r) end
|subseq([], i) = raise Subscript
Upvotes: 2