Closed robinheghan closed 1 year ago
On "Loss of applicatives":
There have been quite a few discussions about pitfalls of elm's "applicatives" with messing up argument order which somewhat still exist even without positional record type alias constructor functions. I once proposed field mapping syntax which doesn't rely on currying and would fix the order issue. With a decoder it would look this:
decoder : Person
decoder =
Json.succeed { name = {}, status = {}, age = {} }
|> Json.andField !name "name" Json.string
|> Json.andField !status "status" Json.string
|> Json.andField !age "age" Json.int
(which the elm slack has taught me is still applicative)
Here's a solution in elm which doesn't rely on currying but isn't viable in gren because it doesn't have tuples but I wanted to mention it anyway: a style where you collect all the arguments
type alias Person =
{ name : String
, age : Int
}
decoder : Person
decoder =
Json.succeed
|> Json.andField "name" Json.string
|> Json.andField "status" Json.string
|> Json.andField "age" Json.int
|> Json.map
(\( ( ( (), name ), status ), age ) ->
{ name = name, status = status, age = age }
)
@lue-bird In your first example, you're relying on the fact that !name
can return a "different" record, in that the type of name
changes from {}
to String
, right?
Yes, that's why I called the feature "field mapping syntax", not "field update syntax"
One could also make use of
map2
,map3
,map4
etc. type of functions, but these must be handwritten and therefore doesn't scale. It's unlikely that Gren's core package will provide aJson.map16
function, for instance.
What about andMap
?
@laurentpayot andMap
relies on automatic currying
This wasn't an easy decision.
Since the monadic form must be performed sequentially, it's a worse fit for decoding, validating and concurrency.
Applicatives can be used without currying, but only if mapX
functions are written by hand. This isn't only tedious, but also increases code-size in the compiled application.
Leaving currying in, means that <|
and |>
doesn't have to be builtins, but can remain as they are.
Currying, while confusing for beginners, hasn't seemed to prevent people from learning or enjoying Elm and the performance overhead they currently represent is possible to optimize away in the common case without too much effort.
Currying as a concept is, especially the advanced applications of it, a difficult concept for beginners to wrap their head around. In addition, currying imposes a runtime overhead that is non-trivial to optimize away.
This proposal is about removing automatic currying from the language.
Benefits
The benefits can be listed quite easily:
Disadvantages
As with the benefits section, one can briefly list the disadvantages, but I also think it's wise to explore them in depth.
|>
has to become builtins of the languageThe biggest downside is the loss of applicatives, which means code like the following won't be possible anymore:
Instead, one would have to use a monadic style (the API listed below doesn't exist)
Stylistically, I don't mind the monadic approach. Even if I did, one could always introduce syntax to make it look nicer. I also think it's harder to mess up with regards to getting the order of arguments wrong. However, the monadic approach has to stop at the first encountered error when decoding. The applicative style can catch all runtime errors at once.
So, in the case where there's something wrong with both name and age when decoding a json blob, the monadic approach will only tell you the first error it encounters, while the applicative approach will be able to tell you about all errors that was encountered.
One could also make use of
map2
,map3
,map4
etc. type of functions, but these must be handwritten and therefore doesn't scale. It's unlikely that Gren's core package will provide aJson.map16
function, for instance.At first glance, one would assume that the monadic approach would be more "Gren-like" anyway, as we've already removed tuples on the basis that it encourages the use of positional semantics. However, applicatives aren't as likely to be littered throughout your code as datastructures are, and so I'm not to concerned about that point here.
One could imagine that one could introduce a
curry
keyword so that currying could be opt-in. I am, however, wary of introducing keywords that look like functions but doesn't actually work like functions, so I'm not too keen on that idea.Finally, the lack of currying means that operators like
|>
would have to become "magic" builtins of the language, as opposed to being API as it is today.