Implementing tail recursion optimizes efficiency, because one doesn't have to evaluate and "pop-off" the stack after creating the recursive calls.
In general, to use tail-recursion, you must store some "memory" from prior computations to compare with in the current one, and update it for future computations, so as to immediately exit the function in the base case.
As such, your function is already tail recursive.
However, here is a tail-recursive maxList
function, more in the spirit of SML :
fun maxList l =
let
fun maxListHelper l acc =
case l of
[] => acc
| x :: xs' => if x > acc
then (maxListHelper xs' x)
else (maxListHelper xs' acc)
in
case l of
[] => error "Empty List!"
| x :: xs' => maxListHelper xs' x
end
Your function is written in a very Haskell-like syntax with different cases handled on different lines without being explicitly declared as nested cases inside a function definition. This is quite alright, but is usually not done in SML.