Reason: Replace `fun { |...` (aka ocaml `function`) with just `{ |...`

Created on 12 Sep 2017  路  27Comments  路  Source: reasonml/reason

Inspired by @bobzhang's #1351

let f = fun {
| A => 1
}
let f = switch {
| A => 1
}

GENIUS

cc @jordwalke

Edit: this conflicts with master's parens-less switch syntax. Better proposal:

let f = {
| A => 1
}
FEATURE REQUEST Parser

Most helpful comment

I would vote that the lambda form be:

let f = switch {
  | A => 1
  | B => 2
}

^ Is this subtly different? It seems quite obviously different to me.

All 27 comments

I've been wanting to use this pattern particularly like so:

gimmeSomeData()
|> switch {
   | None => "nah"
   | Some(x) => x
   }

@ckknight how is it different from

switch (gimmeSomeData()){
  | None =>"nah"
  | Some (x) => x
}

Interesting proposal!
It does have a conflict with the paren-less switch argument. If parents were required then there would be no conflict.

You know, there may even be a way to avoid the switch keyword even!

How

If you omit the switch keyword and just begin with { |, then it can implicitly be a lambda that pattern matches.

Ok, let's do it

@bobzhang: There is no semantic difference in that example.

Would you still be able to do { pat => exp... }? E.g. OCaml:

let optionIsEmpty = function Some _ -> false | _ -> true

Reason:

let optionIsEmpty = { Some _ => false | _ => true }

?

@yawaramin I don鈥檛 believe that would work - you would need a leading BAR to determine that it is a lambda switch vs some other sequence that happens to contain a lambda.

One concern would be that there鈥檚 an implicit function allocation happening. Not sure if that matters a lot though, just a thought.

maybe it would be less confusing in SML expression style? This wouldn't help with anonymous functions though.

```
let f (A) => 1
| f (B) => 2

@TheSpyder You could perhaps fix the lambda scenario by doing:

let f =
  | (A) => 1
  | f (B) => 2;

There are some parsing issues with this though - such as when returning a multi-case lambda from a switch.

let result = switch foo {
   | None => | (A) => 1 | (B) => 2
   | Some x => 0
};

Is the | (B) => 2 another switch match case or a case of the returned lambda. It requires some parenthesis to disambiguate.

let result = switch foo {
   | None => (| (A) => 1 | (B) => 2)
   | Some x => 0
};

Maybe not such a big deal if this is a rare pattern? I don't think I've ever written code like that. The more common scenario is passing lambdas like this:

let result = List.map( | Some(x) => 0 | None => 1, myList);

I think having the switch keyword would make it more accessible to newcomers -- can't we just say "you can't have the switch argument be a bracketed block?"
So instead of

switch { soFancy } {
 | Thing => x
}

you'd have to do

switch ({ soFancy}) {
}

I can't imagine that's a huge drawback

Agree with Jared; alternatively the syntax rule for switch, if, etc. could be that their arguments need to be enclosed in parens. Then you have either switch (arg) PAT-MATCH-BLOCK or switch PAT-MATCH-BLOCK.

@jaredly Might that be a separate issue? What of the multi-match form of lambdas?

Actually, when I teach SML to new JS programmers on my team they are way more comfortable with expression form that repeats the function name using pipes than let f arg = case arg of... style.

The problem with let result = switch foo { style is that in normal code that would imply foo comes from the parent scope. The point of this technique, to me anyway, is that you don't give a name to the function argument.

I also think overloading switch to mean "normally like JS switch, but if you use it in this subtly different way it actually defines a function" is confusing.

Using my proposal inside a switch would definitely require brackets as it does in regular ML, but isn't that already true of the existing fun style?

And I might extend my proposal to fit with the current standard syntax 馃

let f = (A) => 1;
  | f = (B) => 2;

@TheSpyder I wouldn't recommend having the switch keyword represent a function for the reasons you suggested. There's a couple other proposals here that don't involve the switch keyword though.

I like:

let f =
  | (A) => 1
  | (B) => 2;

Better than

let f = (A) => 1;
  | f = (B) => 2;

because it doesn't look like f is bound to two different things. The repeated name syntax of Haskell

let f (A) = 1
let f (B) = 2

makes sense because f itself is never "equal" to two things. f(A) is equal to something and f(B) is equal to something.

But this:

let f =
  | (A) => 1
  | (B) => 2;

Extends from the (A) => 1 ES6 lambda syntax that you already know without f being "equal to" multiple things.

I agree repeating f is weird, but I have seen JS programmers who preferred it even when I pointed out doing it with long function names makes the code harder to read. It's a small sample size, but interesting that they thought about pattern matching in that way.

Your example is just the current syntax with fun removed? I'm fine with that. Whether we add braces as in the original proposal I don't have much of an opinion about.

I would vote that the lambda form be:

let f = switch {
  | A => 1
  | B => 2
}

^ Is this subtly different? It seems quite obviously different to me.

As far as

let f =
  | (A) => 1
  | (B) => 2;

goes, I think it's more of a departure from our recent standardization than the anonymous switch would be.

it's not about whether it's subtle, having switch generate a function seems like it would cause confusion.

I know SML style is a departure. I really only suggested it because I've seen JS programmers latch onto it.

@jordwalke there's another argument for this. This is invalid:

fun
| A => b; c
| B => d

Because you need:

fun
| A => {b; c}
| B => d

You avoided that with switch thanks to braces. I think we should here too.

In light of #1804, I'd like to propose

switch (_) {
  | A => B
  | C => D
}

used like so:

gimmeSomeData()
|> switch (_) {
   | None => "nah"
   | Some(x) => x
   }

It still retains switch as the primary way to do pattern matching, and is a nice corollary with the function call syntax that uses _. This behavior would only apply if just _ were used, the pattern switch (Some(_)) { ... } would be invalid.

Was there ever consensus here?

Hmmm as a newcomer I've just been having fun ;) with this little corner of Reason (it definitely feels like a bit of a wart). It's not really mentioned anywhere in the docs apart from the ocmal vs reason section.
https://reasonml.github.io/docs/en/comparison-to-ocaml#single-argument-match-functions

Instinct makes me think to go with the switch options that @jaredly mentioned this would make it seem like an extension to the switch pattern matching elsewhere but it is definitely weird having the switch keyword represent a function.
Couldn't we just use an underscore with the standard arrow syntax so we get the benefit of not needing to give a name to the function argument.

let f = _ =>
    |None => "nah"
    |Some(x) => x

instead of needing

let f = (arg) =>
   switch(arg) {
    |None => "nah"
    |Some(x) => x
    }

it would also make things like

let safe_division = n =>
  fun
  | 0 => failwith("divide by 0")
  | m => n / m

clearer by doing

let safe_division = n =>(_)=>
  | 0 => failwith("divide by 0")
  | m => n / m

alternatively if _ worked with switch (doesn't seem to be added yet but perhaps on it's way 8e79673?) and the first option can't work with the parser we could just do

let f = _ =>
   switch _ {
    |None => "nah"
    |Some(x) => x
    }

This isn't perfect but get's rid of the need to name an argument.
Then as someone pointed out this morning on discord .. that
fun (x, y) => x + y should have the type ((int, int)) => int, not (int, int) => int ie pattern matched as a tuple. Perhaps we could just drop fun as we already have fat arrow syntax and fun just seems to add confusion? (Is it useful elsewhere?)

Was this page helpful?
0 / 5 - 0 ratings