Morel Cookbook

Problem

I want the top N by some measure, the next page of results, or a list sorted by one key ascending and another descending. The usual pagination and ranking questions.

Setup

val orders = [
  { id = 1, quantity = 12, customer = "Bramble Cafe",     region = "north" },
  { id = 2, quantity = 30, customer = "Granary Foods",    region = "south" },
  { id = 3, quantity = 8,  customer = "Bramble Cafe",     region = "north" },
  { id = 4, quantity = 50, customer = "Ironbridge Hotel", region = "west"  },
  { id = 5, quantity = 6,  customer = "Kiln & Crumb",     region = "east"  },
  { id = 6, quantity = 20, customer = "Granary Foods",    region = "south" }
];

Example

Top three orders by quantity:

from ord in orders
  order DESC ord.quantity
  take 3
  yield { ord.id, ord.customer, ord.quantity };
val it =
  [{customer="Ironbridge Hotel",id=4,quantity=50},
   {customer="Granary Foods",id=2,quantity=30},
   {customer="Granary Foods",id=6,quantity=20}]
  : {customer:string, id:int, quantity:int} list

What's happening

order, take, and skip are query steps, the same flavour as where and yield. They compose: each one takes a collection and returns a collection, so the order you write them is the order they run. order DESC ord.quantity take 3 is "sort descending, then keep the first three" — a top-N.

DESC is a type constructor that flips the ordering of whatever it wraps. Morel's order step compares values structurally, so DESC x comes before DESC y when the original x would have come after y. That sounds backwards until you realise it's exactly what you want for a sort direction.

take and skip together do pagination. skip n take m is the Morel equivalent of SQL's OFFSET n LIMIT m. They're separate steps, which means you can put a yield or a where between them if your pipeline calls for it.

One thing SQL makes awkward but Morel makes cheap: the "top 3 per region" question. Sort by (region, DESC quantity), then use a where that compares ordinal positions. The two-key form of order — a tuple — is where the composable pipeline starts paying off.

Variations

Skip two, take two — page 2 of a page-size-2 index:

from ord in orders
  order ord.id
  skip 2
  take 2
  yield ord.id;

Multi-key sort: region ascending, then quantity descending within each region. The tuple on the order step sorts lexicographically, so the first key dominates and later keys break ties.

from ord in orders
  order (ord.region, DESC ord.quantity)
  yield { ord.region, ord.quantity };

See also