3

The way that the "fast pipe" operator is compared to the "pipe last" in many places implies that they are drop-in replacements for each other. Want to send a value in as the last parameter to a function? Use pipe last (|>). Want to send it as the first parameter? Use fast pipe (once upon a time |., now deprecated in favour of ->).

So you'd be forgiven for thinking, as I did until earlier today, that the following code would get you the first match out of the regular expression match:

Js.String.match([%re "/(\\w+:)*(\\w+)/i"], "int:id")
|> Belt.Option.getExn
-> Array.get(1)

But you'd be wrong (again, as I was earlier today...)

Instead, the compiler emits the following warning:

We've found a bug for you!
OCaml preview 3:10-27
This has type:
  'a option -> 'a
But somewhere wanted:
  'b array

See this sandbox. What gives?

Julian Suggate
  • 627
  • 1
  • 6
  • 14

2 Answers2

4

Looks like they screwed up the precedence of -> so that it's actually interpreted as

Js.String.match([%re "/(\\w+:)*(\\w+)/i"], "int:id")
|> (Belt.Option.getExn->Array.get(1));

With the operators inlined:

Array.get(Belt.Option.getExn, 1, Js.String.match([%re "/(\\w+:)*(\\w+)/i"], "int:id"));

or with the partial application more explicit, since Reason's syntax is a bit confusing with regards to currying:

let f = Array.get(Belt.Option.getExn, 1);
f(Js.String.match([%re "/(\\w+:)*(\\w+)/i"], "int:id"));

Replacing -> with |. works. As does replacing the |> with |..

I'd consider this a bug in Reason, but would in any case advise against using "fast pipe" at all since it invites a lot of confusion for very little benefit.

glennsl
  • 28,186
  • 12
  • 57
  • 75
  • Thanks! Your answer helps a lot (so does your cookbook :) ). There appear to be quite a few mentions of this in the repo issue tracker. [This one](https://github.com/facebook/reason/issues/2249) suggests using parens a little too liberally for my liking. What's your suggestion for coding style that avoids pipe first? Using the pizza pipe with `_` positioning? Or just use intermediate `let` bindings and embrace the imperative feel? (I realise that's a matter of opinion, hence asking in the comments! Just interested what your personal pref is). – Julian Suggate Mar 03 '19 at 01:58
  • `|> _` can be used reliably in place of fast pipe I think, but I'd try to avoid "t first" functions entirely, which means ditching Belt. Functions designed with t first also doesn't work well with partial application, which is essential for effective function composition and a large part of what makes a language functional. – glennsl Mar 03 '19 at 07:18
  • 1
    It seems like Reason is trying to push conventions that deemphasize the functional nature of the underlying language in favor of idioms from JavaScript. But since the ecosystem and even most of the functionality that ships with the compiler is based on the old conventions it ends up as a very confusing mess. And there seems to be little interest in addressing, or even acknowledging these issues. Belt and the fast pipe was introduced over a year ago with promises that everything would be explained, but very little has been said about it since. – glennsl Mar 03 '19 at 07:23
  • But with `_` positioning you can achieve the partial applications one might desire, surely? There is the expense of extra punctuation and mental overhead of course. – Julian Suggate Mar 05 '19 at 03:32
  • I've definitely found the Reason APIs confusing. They seem to suffer from an overabundance of ways to do things (e.g. best way to make a Map). Which would by itself be undesirable, but one must also choose wisely because there are plenty of gotchas. The lack of official docs that go beyond introductory level really hurts once you start building out code. Hopefully my company buys in and is prepared to sponsor some time contributing to documentation / tutorials. – Julian Suggate Mar 05 '19 at 03:38
  • Here's an example of what I mean with partial application of t first vs t last: In t first, if you want to `List.map` over a function like `Belt.Option.getWithDefault` you have to do `things |> List.map(thing => thing |> Option.getWithDefault(_, "foo"))`. With t last you can get away with just `List.map(Option.getWithDefault("foo"))`. And while this example might seem trivial, it quickly adds up if you do a lot of function composition in this way. – glennsl Mar 05 '19 at 08:04
  • To take it another step, if you want to make this example into a function, and `List.map` is also t first, you would have to write `let defaultMap = things => things |> List.map(_, thing => thing |> Option.getWithDefault(_, "foo"))`, while with t last it would just be `let defaultMap = List.map(Option.getWithDefault("foo"))`. – glennsl Mar 05 '19 at 08:08
  • Eww yes that is nasty. I was under the perhaps mistaken impression that you could go `things |> List. map(Option.getWithDefault(_, "foo"))` (or `let defaultMap = List.map(Option.getWithDefault(_, "foo"))` for the function definition). But https://reasonml.chat/t/partial-application-and/714 does seem to cast some doubt on that... must try it on the sandbox when I get to my desktop. The statement "we might remove this in future" doesn't inspire confidence. – Julian Suggate Mar 05 '19 at 20:10
  • Getting a bit OT in here perhaps ... but what do you make of Chenglou's readability argument? I can somewhat agree that it might be confusing to newcomers, but only if they haven't made the functional shift yet. – Julian Suggate Mar 05 '19 at 20:13
  • Ah, yes I forgot that works. And I agree it's not very readable, even to experienced reasoners because it overloads the meaning of `_` and has issues with nesting. It would have been better with a separate and more searchable placeholder (Kotlin for example uses `it`). And in that case I think it would be friendlier to newcomers than currying. Unless you start nesting it, in which case it still becomes confusing to have multiple `it`s referring to different things. Currying is conceptually much simpler and more flexible, but unfortunately not very discoverable. – glennsl Mar 06 '19 at 11:04
  • I feel like the meaning of "computer, I don't need this symbol" and "computer, you don't need this symbol" are close enough to work with :) I'm ok with scattering my code with placeholders, but agree that searchability should have been a higher priority. Especially, as you say, given that it can change the semantics of an expression so dramatically. – Julian Suggate Mar 08 '19 at 22:40
1

Also see this discussion on Github, which contains various workarounds. Leaving @glennsl's as the accepted answer because it describes the nature of the problem.

Update: there is also an article on Medium that goes into a lot of depth about the pros and cons of "data first" and "data last" specifically as it applies to Ocaml / Reason with Bucklescript.

Julian Suggate
  • 627
  • 1
  • 6
  • 14