Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add syntax for monoid comprehensions #69

Closed
julianhyde opened this issue Sep 26, 2021 · 1 comment
Closed

Add syntax for monoid comprehensions #69

julianhyde opened this issue Sep 26, 2021 · 1 comment

Comments

@julianhyde
Copy link
Collaborator

julianhyde commented Sep 26, 2021

Today a from ... group ... [compute ...] query returns a list. If the group key is empty (i.e. group is immediately followed by compute, or group marks the end of the from expression) then the list will have one element. You can use only (which was added in #62):

# Current syntax
= only (from e in scott.emp group compute sum of e.sal);
val it = 29025.0 : real
= only (from e in scott.emp group compute sum of e.sal, count);
val it = {count=14,sum=29025.0} : {count:int, sum:real}
= only (from e in scott.emp group);
val it = () : unit

But it is rather circuitous to first build a list with a single element, and then strip away the list. We just wanted to aggregate the elements.

In functional programming parlance, we want to fold the elements. A type that knows how to fold elements into one is called a monoid. SQL aggregate functions (e.g. SUM, COUNT, MIN, MAX) are generally monoids. So are the boolean operations AND, OR and XOR. In Comprehending Monoids with Class, Perreaux makes the case that a monoid comprehension is more powerful and general than a monad comprehension.

Morel's compute clause already uses monoids (admittedly without all of the knobs to choose your own zero element, or fold or rollup functions, but at least you can replace sum in the above example with any function of type α list → α), but we should have syntax for a monoid comprehension without an intermediate list.

We propose compute without group as that syntax:

# Proposed syntax
= from e in scott.emp compute sum of e.sal;
val it = 29025.0 : real
(*) if the monoid is of type "unit list -> 'a" you can omit the "of"
= from e in scott.emp compute count;
val it = 14 : int
(*) when there are two or more aggregates, the result is a record
= from e in scott.emp compute sum of e.sal, count;
val it = {count=14,sum=29025.0} : {count:int, sum:real}
(*) the degenerate case of zero aggregates always returns unit, the empty record
= from e in scott.emp compute;
val it = () : unit
(*) apply user-defined monoid "and_agg"
= from e in scott.emp compute and_agg of e.is_manager;
val it = false : bool

The compute must be the last clause in the from. (After the compute, you have folded the list to a single element, so a where, yield, order, group or second compute doesn't make sense.) The following is illegal:

from e in scott.emp
  compute s = sum of e.sal, c = count
  yield s + c

You'd write instead:

let
  val {s, c} = from e in scott.emp compute s = sum of e.sal, c = count
in
  s + c
end;
@julianhyde
Copy link
Collaborator Author

I simplified the fix in e790d0a.

@julianhyde julianhyde mentioned this issue Sep 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant