hydromatic / morel

Standard ML interpreter, with relational extensions, implemented in Java
Apache License 2.0
291 stars 15 forks source link

Allow scans (in `from` clause) to assign to patterns #212

Open julianhyde opened 5 months ago

julianhyde commented 5 months ago

Currently we only allow simple identifiers as patterns in a scan:

from d in scott.dept;
> val it =
>   [{deptno=10,dname="ACCOUNTING",loc="NEW YORK"},
>    {deptno=20,dname="RESEARCH",loc="DALLAS"},
>    {deptno=30,dname="SALES",loc="CHICAGO"},
>    {deptno=40,dname="OPERATIONS",loc="BOSTON"}]
>   : {deptno:int, dname:string, loc:string} list

It would be nice to assign to variables directly, by pattern deconstruction:

from {deptno, dname, loc} in scott.dept;
> val it =
>   [{deptno=10,dname="ACCOUNTING",loc="NEW YORK"},
>    {deptno=20,dname="RESEARCH",loc="DALLAS"},
>    {deptno=30,dname="SALES",loc="CHICAGO"},
>    {deptno=40,dname="OPERATIONS",loc="BOSTON"}]
>   : {deptno:int, dname:string, loc:string} list

If we re-order the fields, the effect is the same. (Record fields are bound by name, not position):

from {dname, loc, deptno} in scott.dept;
> val it = ...  : {deptno:int, dname:string, loc:string} list

Equivalent (id is just short-hand for id = id):

from {deptno = deptno, dname = dname, loc = loc} in scott.dept;
> val it = ...  : {deptno:int, dname:string, loc:string} list

Not the same. This one binds the deptno field to a variable x, which is last in alphabetical order:

from {deptno = x, dname = dname, loc = loc} in scott.dept;
> val it = ...  : {dname:string, loc:string, deptno:x} list

Filtering via patterns:

from {deptno, dname, loc = "CHICAGO"} in scott.dept;
> val it = ...  : {deptno:int, dname:string} list

from {deptno, dname, loc = _} in scott.dept;
> val it = ...  : {deptno:int, dname:string} list

Using filtering to match and deconstruct algebraic datatypes. In this case, the datatype is Option, and the query returns only employees who have a not-null commission:

from {deptno, sal, comm = SOME c, _} in scott.emp;
> val it = ...  : {c:real, deptno:int, sal:real} list

Layered pattern (as):

from d as {deptno, dname, loc} in scott.dept;
> val it = ...  : {d:{deptno:int, dname:string, loc:string}, deptno:int, dname:string, loc:string} list

Assigning to tuples is by position, not name. This does what you expect, because the variables in the tuple happen to be in alphabetical order.

from (deptno, dname, loc) in scott.dept;
> val it = ...  : {deptno:int, dname:string, loc:string} list

The output is a record, all fields in alphabetical order; people might find that a bit surprising, but that's always what from does.

This assigns deptno to z, dname to y, loc to x:

from (z, y, x) in scott.dept;
> val it = ...  : {x:string, y:string, z:int} list

If the from chooses to return a tuple, rather than the usual record, the results are very confusing:

from d in scott.dept yield (loc, dname, deptno);
> val it = ...  : string * string * int list

Position-based binding can be confusing. Here, deptno is assigned values from the first field, loc:

from (deptno, dname, loc) in (
  from d in scott.dept yield (loc, dname, deptno));
> val it = ...  : {deptno:string, dname:string, loc:int} list

Even more confusing, a tuple is assigned from the alphabetically ordered fields of the record. deptno is assigned values from the alphabetically first field, a, originally dname:

from (deptno, dname, loc) in (
  from d in scott.dept yield {c = deptno, a = dname, b = loc});
> val it = ...  : {deptno:string, dname:string, loc:int} list