-
Notifications
You must be signed in to change notification settings - Fork 148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
"Chaining" style #568
Comments
ImplicationsCurrently the example in the issue is formatted like so:
This style is in widespread use in To illustrate this choice, here's an example from floatWithinTests : Test
floatWithinTests =
describe "Expect.within"
[ describe "use-cases"
[ fuzz float "pythagorean identity" <|
\x ->
sin x ^ 2 + cos x ^ 2 |> Expect.within (AbsoluteOrRelative 0.000001 0.00001) 1.0
, test "floats known to not add exactly" <|
\_ -> 0.1 + 0.2 |> Expect.within (Absolute 0.000000001) 0.3
]
] I chose this because it's a real-world test suite that uses a newline (and indentation) after the anonymous function (namely This proposed change would introduce a third option: no newline after the It would mean the above could be optionally written like this instead: floatWithinTests : Test
floatWithinTests =
describe "Expect.within"
[ describe "use-cases"
[ fuzz float "pythagorean identity" <| \x ->
sin x ^ 2 + cos x ^ 2 |> Expect.within (AbsoluteOrRelative 0.000001 0.00001) 1.0
, test "floats known to not add exactly" <| \_ ->
0.1 + 0.2 |> Expect.within (Absolute 0.000000001) 0.3
]
] Permitting this does not seem like a cause for concern to me. SupportAs noted in the issue, @albertdahlin's team has had a positive experience using this decoding API (without
@ianmjones found it confusing at first:
I added a
(I'm considering that a "support" because historically I've had little success teaching what Pushback
@alexkorban expressed a strong preference for
Other Feedback@sporto expressed a preference for the indented style when decoding a small number of fields, but considered the non-indented version beneficial for larger ones:
@JoelQ commented that users are likely to continue to use record constructor functions in this API regardless:
I responded with a clarification. @Chadtech compared the error messages of both styles and concluded that they were not much different in quality. I came to the opposite conclusion and explained why. @rgrempel pointed out that "whether this is an idiom that ought to be generally encouraged is a significant question." I think that's worth discussing in this thread. |
If I had to give a quick description of this idiom, it would be "gradually bringing identifiers into scope without monotonous indentation". As a general matter, I think this is most essential when dealing with multiple So, with a bunch of Now, Elm 0.17 flipped the argument order of So, for instance, suppose the API were such that you could write this (in post-Elm-0.17 style) require "id" int
|> andThen (\id -> require "email" string)
|> andThen (\email -> default "name" string "Guest")
|> andThen (\name -> succeed { id = id, email = email, name = name }) ... then the difficulty is that Now, elm-json-decode-pipeline gets around this by passing a function into the beginning of the pipeline, but that leads to the difficulties that @rtfeldman mentions. To keep everything in scope at the end, you'd have to re-bracket, something like this ... require "id" int
|> andThen
(\id ->
require "email" string
|> andThen
(\email ->
default "name" string "Guest"
|> andThen (\name -> succeed { id = id, email = email, name = name })
)
) ... which is clearly not nice, at least as it would be formatted currently. In other languages, this general problem of "gradually bringing variables into scope without excessive indentation" is handled via special syntax -- for instance, "do notation". For instance, decoder : Decoder User
decoder =
do
id <- require "id" int
email <- require "email" string
name <- default "name" string "Guest"
succeed { id = id, email = email, name = name } If you compare this to the proposal ... decoder =
require "id" int <| \id ->
require "email" string <| \email ->
default "name" string "Guest" <| \name ->
succeed { id = id, email = email, name = name } ... what I find fascinating is how much of the benefit of "do notation" you can get from a simple formatting change. However, there are a few questions that I think might be worth exploring (when thinking about this as a general idiom). Usage with explicit
|
Actually, perhaps I have the "more complex stage" example wrong -- perhaps the proposal would result in this instead: decoder : Decoder User
decoder =
require "id" int <| \id ->
require "email" string <| \email ->
let
defaultName =
computeDefaultName id email
in
default "name" string defaultName <| \name ->
succeed { id = id, email = email, name = name } That is, we wouldn't indent |
As an extra data point, someone was asking about chaining a bunch of HTTP requests and accumulating all the results at the end. The style under discussion here was one possible solution mentioned, as an alternative to a cumbersome nesting of |
Our package webbhuset/elm-json-decode using the continuation style is now published. |
Hello! This was a really interesting idea. Im warming up to it more and more. At our most recent Elm Munich meet up, I briefly showed this issue, as it became related to our discussion. It generated two comments that I think could be summed up as "I dont like this format, its not readable, I would never think to look for the parameter there" and then one reply "well you are just saying that because you arent used to it". |
A variant is actually used in For example:
which is currently formatted by
|
@avh4 (Repost from Slack; here the Slack history-eating monster has no power 😄 ) I'd like to ask about whether there are plans for implementing some variant of this PR to allow nice Canonical.If { test, then_, else_ } ->
let
( test_, id1 ) =
f currentId test
( then__, id2 ) =
f id1 then_
( else__, id3 ) =
f id2 else_
in
assignId id3
(Typed.If
{ test = test_
, then_ = then__
, else_ = else__
}
) could, if I hid the ID threading into a monad, become something like Canonical.If { test, then_, else_ } ->
f test <| \test_ ->
f then_ <| \then__ ->
f else_ <| \else__ ->
assignId
(Typed.If
{ test = test_
, then_ = then__
, else_ = else__
}
) but, as you can guess by people asking you about this time and time again, the Canonical.If { test, then_, else_ } ->
f test <|
\test_ ->
f then_ <|
\then__ ->
f else_ <|
\else__ ->
assignId
(Typed.If
{ test = test_
, then_ = then__
, else_ = else__
}
) Which IMHO is a pity: I could use the "abstract state/writer/... away" benefit of monads and Elm has the feature set to make it possible. EDIT: Here is an Ellie with what I think illustrates the benefits of the proposed elm-format change: https://ellie-app.com/f83Y99bgJgDa1 |
Motivation
There are a few longstanding issues with
elm-json-decode-pipeline
. I wrote them up here.(Briefly: reordering fields in
type alias
can break its decoders' implementations, its unusual types are detrimental to learning and to error message quality, and customizing its decoders is challenging.)Solution
I wrote a Discourse post looking for feedback on an experimental API that addresses these problems.
@albertdahlin responded that his team independently came up with essentially the same API and have been using it at work for months, with positive results:
Their code for it is here.
Proposal
Add experimental (as in,
elm-format@exp
) support for this formatting style:Specifically, the proposed formatting rule change would be:
This would not change the formatting of any existing code. It would be a new formatting choice that has not been supported before, and it would specifically be added to support this "chaining" style of DSL. (One way to think of it is as “
do
notation without the syntax sugar.“)The text was updated successfully, but these errors were encountered: