Well done, T.B.! The same idea applies to recursive data in any language; you'd use exactly this to write a tree-walker in Java, for instance. And likewise in Racket. E.g.:
fun f(l :: List<T>):
cases (List) l:
| empty => ...
| link(f, r) => ... f ... f(r) ...
end
end
Note that because you can put type annotations in the cases statement, you can remind yourself of the type of the locals:
fun f(l :: List<T>):
cases (List) l:
| empty => ...
| link(f :: T, r :: List<T>) => ... f ... f(r) ...
end
end
which further nudges you towards a possibly recursive solution.