Morel Cookbook

Problem

I have a plain list — not a query result — and I want the shape of operations a from gives me. Map, filter, reduce. Also: the relationship between List.map and yield, and List.filter and where, once the penny drops.

Setup

val quantities = [12, 30, 8, 50, 6, 20];

Example

The three classical higher-order functions, applied directly. Each one takes a function as its first argument:

List.map (fn q => q * 10) quantities;
List.filter (fn q => q > 10) quantities;
List.foldl (op +) 0 quantities;
val it = [120,300,80,500,60,200] : int list
val it = [12,30,50,20] : int list
val it = 126 : int

What's happening

List.map applies a function to every element and collects the results. List.filter keeps the elements that satisfy a predicate. List.foldl walks left-to-right with an accumulator — the general-purpose reducer that sum and count are specialisations of. Together they cover most of the useful shape-changing you'd do without reaching for a full query.

The trick Morel rewards is seeing the equivalence. from q in xs yield f q is the same thing as List.map f xs. from q in xs where p q is the same thing as List.filter p xs. from q in xs yield (…) group {} compute { x = sum over ... } is a List.foldl with the aggregation function baked in. Pick whichever form reads better for the task — query pipelines win when there are multiple steps; direct HOF calls win when there's just one.

op + is SML's spelling of "the function version of +". Any infix operator becomes a regular two-argument function when you wrap it in op. That's useful with foldl and foldr all the time — foldl (op +) 0, foldl (op *) 1, foldl String.^ "".

Variations

Compose map and filter as a pipeline — triple every quantity that's at least 20:

val tripledBulk = List.map (fn q => q * 3) (List.filter (fn q => q >= 20) quantities);
tripledBulk;

The same question as a from-query. One collection goes in, one comes out, and the intermediate step is implicit:

from q in quantities
  where q >= 20
  yield q * 3;

Option.map transforms what's inside a SOME without touching NONE. This is how you "apply a function to a value that might be missing" without unwrapping and re-wrapping:

val nums = [SOME 10, NONE, SOME 25, NONE, SOME 5];
List.map (fn opt => Option.map (fn n => n * 2) opt) nums;

See also