-
Notifications
You must be signed in to change notification settings - Fork 660
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
Feature: 'where' expressions #621
Comments
|
I would love to see these in Elm too. I have the same work flow. But I don't think this will be a priority. |
Before implementing things, it's important to define what the indentation actually means. What is the scope of my where clause exactly? This is something I find mysterious in Haskell and is like there to be a set of well defined rules before trying to implement stuff. |
means
In haskell it works like its implemented like this. I assume, that your parser thinks that if Note that I think, the popularity of this language for now depends on how precise it resembles haskell. Maybe, later it'll convince some BaconJS/ReactJS guys or anybody using some FRP. |
The current decision is to not support |
I would gladly give up |
I think a lot of people agree with you @Apanatshka |
Me too, @fosskers! |
Me too, I find |
I'm not sure whether what people are wishing for here is " |
@jvoigtlaender Have you ever needed a |
Not sure how to answer that, since I do not really mentally, consciously distinguish between programming in Haskell and in Elm, and I definitely like to be able to use local definitions scoping over expressions rather than whole function definition right-hand sides in Haskell, so I assume the same applies to me programming Elm. Also, setting the bar at "where factoring the code out into top-level definitions ... wouldn't work, or would be a hack" is quite high. Of course, in some way it is always possible to do that, and whether it is a nice thing to do (or should be considered a hack) in a concrete case is a matter of taste, so difficult to judge. Anyway, prompted by your question, I just had a look into Elm code I wrote this weekend, and this line is a place where I would not like to have to live without |
I guess it really depends how you structure your code. If you structure your code as: the lower you are in your file, the more specific (and possibly private) your functions get (i.e. the general, public API functions at the top), then But if you don't structure your file this way, then My opinion: either have |
Looking further down in that module, I find that I regularly use expressions of the kind |
@jvoigtlaender Having foo :: (a,b)
foo = (a,b)
where a = ... -- something complicated
b = ... -- something complicated or bar :: [a] -> b
bar = foldl f someAccVal
where f acc x = ... -- complicated lambda |
@fosskers, all very nice. I also do that (in Haskell). But only if I want to, not because the language forces me to. Which would be the case if the language takes away the possibility of combining local definitions with anonymous functions. I can only repeat that this is at stake here when discussing to replace |
I know this is all far into opinion land, but I think larger constraints on a language gives interesting results. In the same way I'm thinking, maybe combining anonymous functions with local definitions is usually an anti-pattern. Wouldn't it be interesting to see if you get pushed to write more readable code by taking away the When I have more time (probably in 8 hours or so), I'll see if I can rewrite @jvoigtlaender's linked Elm file in |
@Apanatshka, if you rewrite the file, I'll be interested to look at the result. But I agree that it will prove little. And not just for the reason you mention, opinions, but also for another fundamental reason. Namely, "measuring" only readability of the final code ignores an at least as important aspect: how that code comes/came into being. And for practical, refactoring purposes, I think that declaring "combining anonymous functions with local definitions" an anti-pattern would be almost equivalent to declaring "using anonymous functions at all" an anti-pattern. Why? Let's take a look at some other lines of said file: https://github.com/jvoigtlaender/Elm-Kurs/blob/e7db63ebaaa398f44a5431245fd227f11d5590cd/src/lib/Lib.elm#L130-L133. Let's say they started out as: Signal.map
(\(x,y) _ t' state -> (t', { state | mousePos <- (toFloat (x1 + x), toFloat (y2 - y)) }))
Mouse.position In a world in which anonymous functions (alone) are not considered bad style per se, this should be a perfectly fine thing to write. Then, at some point I realise that I need to update the Signal.map
(\(x,y) _ t' state -> (t', { state | mousePos <- (toFloat (x1 + x), toFloat (y2 - y)),
s <- upd NoEvent (toFloat (x1 + x), toFloat (y2 - y)) t' state.s }))
Mouse.position But I really shouldn't write it like that, since the duplication of Signal.map
(\(x,y) _ t' state -> let pos = (toFloat (x1 + x), toFloat (y2 - y))
in (t', { state | mousePos <- pos, s <- upd NoEvent pos t' state.s }))
Mouse.position But in a world in which anonymous functions with local bindings inside are considered bad style (or even illegal according to the language definition), I would not be allowed to write that. Instead I would have to suddenly turn the anonymous function into a named one. Very bad, because the refactoring I want to do, abstracting the If you show me a version of the above without an anonymous function with a local binding, this doesn't take the above "history" of the expression into account. The only way I can see I could have arrived at your potential version without going through the painful step in which an anonymous function has to be turned into a named one during an innocent and internal refactoring step, would be that I would have stayed away from using an anonymous function in the first place. So: In a world in which anonymous functions with local bindings are bad style, but decent refactoring is desirable, anonymous functions should not be used at all. Because at any time they might turn foul because of an ostensibly beneficial refactoring step, causing much distress. (Well, that very last part maybe overstates a bit.) |
I personally get nauseous when the main body of a function is more than a single line long, meaning by definition I don't like foo = let x = ...
in ... -- very long expression I argue that the following is less common: foo y = let x = bar y in ... -- fits in one line as this could probably be written more cleanly in point-free style. If the aim of the To each their own, but to me |
@fosskers, how does your suggestion "If the aim of the Concretely, how would you write my example Signal.map
(\(x,y) _ t' state -> let pos = (toFloat (x1 + x), toFloat (y2 - y))
in (t', { state | mousePos <- pos, s <- upd NoEvent pos t' state.s }))
Mouse.position ? |
@fosskers, oh, I just saw that it was you who earlier said that you turn "complicated lambdas" into named functions. So I guess your answer might be you wouldn't write my example with an anonymous function at all. Which is fine. Just that having this enforced by the language would likely mean that one could rarely use anonymous functions at all (for long). Because at some point they may become "complicated" (needing a local binding inside), so have to be turned non-anonymous. |
At that I would suggest that were the lambdas complicated enough, they would deserve being promoted to a named function anyway. My view is that anonymous functions are for short, one-off functionality. |
I don't understand how this discussion can go so long without showing examples. I also notice that all the people involved know Haskell? Is this something that registers at all for a JS programmer? A JS programmer who has Furthermore, how will this interact with potential syntax for tasks? It's impossible to know right now. I closed this issue because answering the most important questions are very hard and time consuming, and it's unclear that it'd be a big benefit compared to all the other things that can be done with that time and effort. If you want to pursue this further, the opinions don't matter. The code comparisons and use-cases should speak for themselves. If you can demonstrate those things, write it up in a gist in a clear way and share the link. |
@evancz Can you re-read your post and consider if you find the tone berating? Maybe it's just my imagination, but I was in a good mood when I read it, so it's not my mood influencing things. Anyway, I know you don't mean it that way, maybe I'm just taking it the wrong way.
We're talking about a feature that's well-known in Haskell. Obviously some people who know Haskell will find it easier to join this discussion. The JS programmer will need to learn some mechanism of binding names to values. Elm already has
Interesting angle. But it's the potential syntax for tasks, I haven't seen it on the todo list yet. And the done-deal but last minute postponed Signal/Stream split is now uncertain again, so I can't know if the same is true for the tasks syntax. I think the discussion here isn't ready to look at all the repercussions at once. Let's first continue with finding what the exact merits of
There are multiple small examples in the discussion, and a larger one is linked to. If you read carefully you'll see that I was planning on using that larger example for a side-by-side comparison.
We're just three people casually discussing this right now. I'm sure that if we come up with something concrete, we can recap it for the whole mailing list to discuss. This discussion just happened to end up on the issue tracker of your repo, but I don't think you should feel obligated to follow or join the discussion at this point. You're welcome to do so of course, but as we don't have anything concrete yet you can also just ignore it. 😃 |
@jvoigtlaender I got around to refactoring your Lib.elm in As for the argument that anonymous functions can be used less without |
This is what I was implying we should strive to avoid.
I think the JS programmer coming to Elm is going to know they're tackling a new language that resembles what they're used to very little. In my opinion, the logical leap to specifying "the details" below the main function body in a |
This function may be the easiest to compare. The original: makeGrid x1 y1 x2 y2 =
let
x_ = (x1 + x2) / 2
xh = x_ - x1
y_ = (y1 + y2) / 2
yh = y_ - y1
in group <|
List.map (\i -> let x = toFloat i * gridsize - x_ in path' (dotted (Color.greyscale 0.15)) [ (x,-yh), (x,yh) ])
[ ceiling (x1/gridsize) .. floor (x2/gridsize) ]
++
List.map (\j -> let y = toFloat j * gridsize - y_ in path' (dotted (Color.greyscale 0.15)) [ (-xh,y), (xh,y) ])
[ ceiling (y1/gridsize) .. floor (y2/gridsize) ]
++
[ move (-x_,-y_) (Graphics.Collage.filled Color.red (Graphics.Collage.circle 2))] And with -- not exported
makeGrid x1 y1 x2 y2 =
[ gridCoord x1 x2
|> List.map (makeGridLine x_ yh)
|> path' (dotted (Color.greyscale 0.15))
, gridCoord y1 y2
|> List.map (makeGridLine y_ xh >> swap)
|> path' (dotted (Color.greyscale 0.15))
, [ redDot |> move (-x_,-y_) ]
]
|> concat
|> group
where
x_ = (x1 + x2) / 2
xh = x_ - x1
y_ = (y1 + y2) / 2
yh = y_ - y1
-- not exported
makeGridLine c1 c2 i = [ (c1',-c2), (c1',c2) ]
where c1' = toFloat i * gridsize - c1 |
@Apanatshka and @fosskers, you both make the point that it may be good if the language, by forbidding local bindings in anonymous functions, forces me to turn anonymous functions into named ones sooner rather than later (or at all). That is certainly a defensible position. I may not like being forced to anything, but maybe it is for the greater good. @Apanatshka, as expected, it turned out to be interesting to look at your refactoring of that code. I have a few observations:
|
@fosskers, yes, but to the revised snippet you have to also add the definitions of |
On the point that Haskell users will dislike Elm for not having |
I believe that Elm's subject seems to be closer to "declarative reactive "where" is closer to declarative style than "let". Нд, 22 трав. 2016 о 20:28 Max Goldstein notifications@github.com пише:
|
I think any effort to use Elm is much more likely for someone who's already sold on functional programming, than a Javascript developer, who already has a lot of tools at their disposal. Compared to a language built into every browser (with full access to webpage elements), a Javascript developer would have to have a very open mind to use something like Elm, which requires a totally different ecosystem (built under Haskell no less!) For a Haskell user, on the other hand, Elm lets you do Web client-side stuff, which you couldn't before, and more importantly, it lets you explore the world of FRP (more likely the reason you decide to play with Elm in the first place, from a theoretical perspective). Since having a Haskell-like thing to run in the browser is nice, changes from the Haskell syntax are best avoided. Though one has to acknowledge that the I found this very interesting list of differences between Haskell and Elm. Thanks to Python making list comprehensions very much the 'mode du jour' (being far more readable for long expressions), the whole And right, the special use case for Coming back to the "only one way to do it" logic, by excluding Man, I don't where this language is going, but implementing more features of Haskell into Elm is better than not, and the syntax doesn't need to change. If you can just use some 'Elm equivalent' it's not so bad, but |
@ColonelJ, you are entirely entitled to your own opinion about what the target audience for Elm should be, but that doesn't change a bit about what the declared target audience for Elm is. You think Elm shouldn't try to "convert" JavaScript developers. Fine. The creator of Elm has a different opinion, and certainly you cannot expect that because of the opinion you have the language changes course. |
Given that Haskellers were the initial adopters, Evan and the rest of the Elm core team have to realize they alienate their original users by taking this attitude. All power to Elm if its goal is to "win" the frontend stack race. I really hope that happens. It's unfortunate that Elm thinks it has to be "easy" and must avoid advanced concepts at all costs in order to be useful/adoptable. |
Designing to infantilize a target audience is how we got Java. JavaScript dev's who are interested in Elm, are the cream of the crop imho and good engineers will step upto the plate when challenged. I love how Elm lowers the barrier to entry, but that is a separate concern from target audience. It feels like its being implied that good JS devs are less willing or capable of dealing with abstraction than Haskell devs, and its just not the case. JS world is maddeningly complex, and good JS engineers deal with complexity way beyond what a simple 'where' clause introduces. I'd like to see Elm focus on being the best programming language in can be, and meet the goals of the language, including simplicity and a low barrier to entry. But that is very different from, 'lets exclude useful abstractions because JS devs are too dumb to understand', that's the wrong attitude. Not to say there are not stupid JS devs out there, but Elm should not en-devour to fix the unfixable. If the addition of language feature X, makes Elm for flexible and pleasurable to use, and doesn't conflict with the goals of the language or the preservation of its properties.... then it makes Elm more flexible and pleasurable to use, period; regardless of the programming background of the developer. |
@Fresheyeball, I fully agree with you. But what we are getting above is wrong statements like "Elm tries to be a Haskell derivate, so it must add this and this and this syntactic feature from Haskell". Or "It's basically hopeless to make Elm attractive for JS folks, so the language must target Haskell folks instead, and for this reason must copy as much as possible of Haskell syntax, because things must be like they are in Haskell, in order not to turn away Haskellers" etc. |
I still vote for Downside-up code breaks logical flow. You read: |
It's changed quite a bit over the years, but we can't deny the stark similarity between the two. Don't want higher-kinded types? Fine, all power to you, Elm doesn't have to be Haskell. That said, drawing the line at
Is anyone actually claiming this? Adding |
I assume the last comment is addressed to me, since it is quoting excerpts from my previous comment. If it is addressed to me, you seem to be making assumptions about my position that are not true. I have nowhere above said that I am against having About this:
I was writing that because of this paragraph in someone else's comment:
... followed by essentially an argument that "because of this", Elm must copy Haskell as much as possible in syntax and operator names. |
I was quoting you, yeah. That said:
More like the de facto agreement of the Elm team, as opposed to you specifically. The same goes for higher-kinded types. I didn't mean you. I've just reread the entire thread, and it's interesting that it has been an entire year we've been debating this.
We can agree that that's silly.
Point taken. I was going to suggest more discussion points, but having reread the entire thread as I was writing this, I can see almost everything that can be said has been. Critically, however, the following has not yet been discussed, and has been a concern of mine recently (as a user, contributor, and proponent of Elm):
|
I'm from JS and I'd like to use where over let, for the reason @Heimdell gave. |
@kana-sama Please correct me if I'm wrong, but isn't that just about Elm having a newline after |
Got it! I was interpreting function body as including the let-in, so I couldn't see it. |
Folks: where-expressions are not going to happen. A contrived diff isn't going to make a difference. (Yes, contrived: how often do you add a new parameter like this when making a change? You have a solution in search of a problem. When designing Elm, we like to start with a collection of real-world pain points, not a collection of language features.) |
@mgold the examples are coming in because Evan likes them. Also, since you need painpoints, here's mine. I thought i'd never share it since it makes me uncomfortable confessing it in public, but what the hell. It's for a higher cause. I have a medical issue where once I open a file and start reading it, I can't go back. If i need to read a couple of lines above, I need to close the file, reopen it, and start over, hoping i wouldn't miss them again. I don't need to tell you how bad let..in makes things for me.. needing to remember everything inside And doctors keep telling me how there is js compiler for haskell, but i stand my ground! And I will. keep. standing. my. ground. |
@Birowsky I'm sorry to hear about your condition. Thank you for having the bravery to share. I don't know how to balance your medical needs with the (non-medical) needs and preferences of the rest of the community, so I will bow out of this conversation. |
You are referring to the adding of f a b =
let
c = 10
in
a + b + c There is no extra (function) parameter in the above; and isn't it common to abstract out a subset of expression and assign it to a variable for re-use? I do it all the time (using
If I may ask? What is the (data) source for these real-world pain points if not the barrage of requests and complains in this Github Issues tracker? |
This is precisely the purpose of both |
Yes, it's common enough to extract named expressions, and I may have been fooled by the generic-ness of
I may be grouchy on this thread, but this community never turns away an honest question!
They're not data in any statistical sense, but we like to hold language discussions on the mailing list. The best of these focus on "how do I do this web development thing" or "how can I structure my application". This issue tracker is used by Elm's creator for bugs, not feature requests. |
Continued on the mailing list: https://groups.google.com/forum/#!topic/elm-discuss/KiKF6K9gBKU |
Miso has where. 😉 |
nothing yet about this feature? there's only intended support to |
@Mazuh see the mailing list link. Here's a summary of the arguments on both sides. Note: Elm's creator didn't participate in any of the follow-up discussion (his last comment was in this GH thread in 2015), and considers the matter closed. |
I Haskell in the frontend nowadays and make both use of let and where. The
intent-first thing is my primary motivation to use where clause.
Le dim. 22 juill. 2018 10:07 p.m., Colin Woodbury <notifications@github.com>
a écrit :
… @Mazuh <https://github.com/Mazuh> see the mailing list link. Here's a
summary of the arguments on both sides.
<https://groups.google.com/forum/#!msg/elm-discuss/KiKF6K9gBKU/jzMuSTZmEwAJ>
*Note:* Elm's creator didn't participate in any of the follow-up
discussion (his last comment was in this thread in 2015), and considers the
matter closed.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#621 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAAPngtOGr2LPLRBErGt5e5RmTmXcFl-ks5uJS_dgaJpZM4B4ePC>
.
|
Hey folks, we're not gonna do |
I'd love to see support for "upside down"
let ... in
expressions, like you have in Haskell:Sometimes code is easier to read if you see the Big Picture first, with the details hidden. Having a Haskell background, I often find myself laying out new definitions by typing
let \n in
first, leaving empty thelet
part, then sketching the Big Picture in thein
part, and finally filling out all the details in thelet
part:I understand that 'where' expressions are a bit problematic because they flip the order in which code is executed (since Elm is eagerly evaluated, you'll have to evaluate the
where ...
block before evaluating the Big Picture expression). But I think this is something you can easily adapt your thinking to, so I wouldn't expect it to cause a lot of confusion.The text was updated successfully, but these errors were encountered: