hydromatic / morel

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

Pattern that re-uses a variable rather than creating a new one #184

Open julianhyde opened 2 years ago

julianhyde commented 2 years ago

It would be useful to add syntactic sugar to re-use a variable in from, constraining the new variable. Consider the following expression to compute all paths of length 2:

(*
   1 -> 2 -> 3
   |    ^    ^
   |    |    |
   +--> 4 ---+
*)
val edges = [(1, 2), (2, 3), (1, 4), (4, 2), (4, 3)];
from (x, y) in edges,
    (y2, z) in edges
  where y = y2
  group x, z;
val it = [{x=4,z=3},{x=1,z=2},{x=1,z=3}] : {x:int, z:int} list

It is inconvenient to have to declare y2 and then immediately constrain it to equal y. With the new same operator, the following would be equivalent:

from (x, y) in edges,
    (same y, z) in edges
  group x, z;
julianhyde commented 2 years ago

same is not just for within from. Here is its use in a function:

fun f (1,  1) = 0
  | f (x, same x) = x
  | f (x,  y) = x + y;
val f = fn : int * int -> int
f (1, 1);
val it = 0: int;
f (2, 2);
val it = 2: int;
fun (2, 3);
val it = 5: int;

In [Successor ML](), you could achieve a similar effect with a pattern guard (if):

fun f (1, 1) = 0
  | f (x, x2 if x = x2) = x
  | f (x, y) = x + y;

(The effect is not identical: the pattern guard (x, x2 if x = x2) would add x and x2 to scope, whereas (x, same x) would only add x to scope.)

But in Standard ML and current Morel, you would have to expand as follows:

- fun f (1, 1) = 1
=   | f (x, y) = if (x = y) then x else x + y;
val f = fn : int * int -> int
- f (1, 1);
val it = 1 : int
- f (2, 2);
val it = 2 : int
- f (2, 3);
val it = 5 : int