Skip to content
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

Closed
jonashaag opened this issue May 6, 2014 · 85 comments
Closed

Feature: 'where' expressions #621

jonashaag opened this issue May 6, 2014 · 85 comments

Comments

@jonashaag
Copy link

I'd love to see support for "upside down" let ... in expressions, like you have in Haskell:

spam = foo bar where
  bar = ...

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 the let part, then sketching the Big Picture in the in part, and finally filling out all the details in the let part:

foo = let
      in

==>

foo = let
      in ... Big Picture ...

==>

foo = let details
      in ... Big Picture ...

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.

@fosskers
Copy link
Contributor

fosskers commented May 6, 2014

where is easily the number 1 thing in Elm I want right now.

@Apanatshka
Copy link
Contributor

I would love to see these in Elm too. I have the same work flow.
And when I extend an already written function to something more extensive it's also easier to add a where.

But I don't think this will be a priority. let-in allows the same functionality and Evan is busy with the canonicalize branch, which will fix lots of bugs.
So if you really want this and have the time, you should implement it yourself and open a PR ;)

@evancz
Copy link
Member

evancz commented May 30, 2014

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.

@Heimdell
Copy link

Where could be implemented by rewriting it to let and prepending it to the code. For instance:

fun = 
    let a = b in
    c
  where
    d = f
      where
        f = e
    g = i

means

fun =
    let g = i in
    let d =
        let f = e in
        f
    let a = b in
    c

In haskell it works like its implemented like this. I assume, that your parser thinks that if a is an expr, then is let b = c in a is an expr too.

Note that where starts a layout and could be inside another where.

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.

@evancz
Copy link
Member

evancz commented May 8, 2015

The current decision is to not support where. I want to avoid adding redundant syntax for the foreseeable future.

@evancz evancz closed this as completed May 8, 2015
@Apanatshka
Copy link
Contributor

I would gladly give up let for where 😁

@fosskers
Copy link
Contributor

fosskers commented May 8, 2015

I think a lot of people agree with you @Apanatshka

@Heimdell
Copy link

Me too, @fosskers!

@BethAr
Copy link

BethAr commented May 18, 2015

Me too, I find let very confusing.

@jvoigtlaender
Copy link
Contributor

I'm not sure whether what people are wishing for here is "where as in Haskell". But if so, they should take into account that not everything that can be written with let in Elm (or in Haskell) can be written with where in Haskell. Apart from the difference about whether the local definitions come before or after the main expression, where is strictly less general than let in Haskell. So in Haskell, at least, omitting let and keeping where would not make sense.

@Apanatshka
Copy link
Contributor

@jvoigtlaender Have you ever needed a let expression somewhere in Elm where a where clause would not have worked? And where factoring the code out into top-level definitions with where clauses wouldn't work, or would be a hack?
I haven't seen let being used anywhere where you wouldn't be able to use where instead. We already try to stay away from overly short variables too in Elm. So having the main expression first, and a where after that using sensible names makes more sense to me.

@jvoigtlaender
Copy link
Contributor

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 let. (I don't know whether it makes sense to ask how you would propose to write that function with where, because you would have to study that function first, and in any case, nothing in that module is very pretty. In a sense, most of it consists of quick hacking.)

@TheSeamau5
Copy link

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 where clauses make sense because you are replicating this structure within a single function.

But if you don't structure your file this way, then where clauses don't really buy you much. I have a more JS/Python/C background, and to me, the thing after the in is the return statement. It would just feel so weird to have the return statement be at the very top.

My opinion: either have let-in or where but not both. I personally prefer let-in because it is familiar if I treat the thing after the in as the return statement. But, just pick one cuz having both would be confusing.

@jvoigtlaender
Copy link
Contributor

Looking further down in that module, I find that I regularly use expressions of the kind (\x -> let y = ... in ...). Having only where, you would make me giving up (some uses of) anonymous functions or local definitions, because I would not be able to have local definitions inside a lambda-expression. That's expressiveness I don't want to miss in a functional language.

@fosskers
Copy link
Contributor

@jvoigtlaender Having let in that case is nice, but personally if my lambdas get too complicated I just relegate them to where. I much prefer brevity in the main line, a la:

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

@jvoigtlaender
Copy link
Contributor

@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 let by where-a-la-Haskell: two useful functional features, local definitions and anonymous functions, would suddenly not anymore be usable together. Or do I miss anything?

@Apanatshka
Copy link
Contributor

I know this is all far into opinion land, but I think larger constraints on a language gives interesting results.
Like Elm has only the foldp primitive to get a stateful program, so you have to package all your state together in one big record. It sounds strange and bad, but it seems to be doing well. Perhaps distributing state more around a program will turn out to be an anti-pattern.

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 let and only supporting where? Your code style would have to change. And code style is always considered a subjective thing. But really that's only so because people loathe to do user studies (in general, afaict, I've never seen or heard of any) to see what kind of code is objectively more readable to humans.

When I have more time (probably in 8 hours or so), I'll see if I can rewrite @jvoigtlaender's linked Elm file in where style. Then we have a concrete example, see if it's a clearer code style. It won't prove anything, but it's a quick way to see if turns out to be the counter-argument that refutes my "theory".

@jvoigtlaender
Copy link
Contributor

@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 state.s component as well in there, and that I need the newly computed logical mouse position for that. So, what I need is:

   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 (toFloat (x1 + x), toFloat (y2 - y)) is obviously bad style. So what I want is:

   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 pos, is completely local to that function, so I should be able to do it in place, without changing the outside or the nature of that function (as an anonymous vs. named one).

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.)

@fosskers
Copy link
Contributor

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 let statements as the following is common:

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 let is to use one value in multiple places, then a where serves just as well and frees up the main function body at the same time.

To each their own, but to me let itself is always an anti-pattern.

@jvoigtlaender
Copy link
Contributor

@fosskers, how does your suggestion "If the aim of the let is to use one value in multiple places, then a where serves just as well" play out inside an anonymous function? (Does it?)

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

?

@jvoigtlaender
Copy link
Contributor

@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.

@fosskers
Copy link
Contributor

Because at some point they may become "complicated"

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.

@evancz
Copy link
Member

evancz commented May 19, 2015

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 let as of ES6?

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.

@Apanatshka
Copy link
Contributor

@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.

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 let as of ES6?

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 where for module definitions, so it's not that far-fetched. ES6 is getting fully supported by modern browsers but the average JS programmer will probably need to worry about backward compatibility. Also: ES6 let is a hack to get block scope into JS without breaking changes to var syntax. That's confusingly different from having an let expression that kind bind multiple names simultaneously to immutable values.

Furthermore, how will this interact with potential syntax for tasks? It's impossible to know right now.

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 where are.

I don't understand how this discussion can go so long without showing examples.

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.

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. 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.

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. 😃

@Apanatshka
Copy link
Contributor

@jvoigtlaender I got around to refactoring your Lib.elm in where style. It took me three revisions, and since I wrote this in a gist and where isn't supported I don't know if it's completely correct. I'm still not quite happy with the huge elaborateDisplay but I couldn't factor out any more without even larger refactorings, which would just make it harder to do the comparison.

As for the argument that anonymous functions can be used less without let-in, I agree, that diminishes the power of the anonymous function. But I look at it from the other side: let-in lures you into endlessly extending your anonymous functions into monstrosities that are not readable any more. You will have to refactor that anonymous function and give it a name at some point if you want to do a lot there and keep the code maintainable. Without let-in you just notice earlier that the time has come to say farewell to your once sweet little anonymous function 😉

@fosskers
Copy link
Contributor

let-in lures you into endlessly extending your anonymous functions into monstrosities that are not readable any more.

This is what I was implying we should strive to avoid.

Is this something that registers at all for a JS programmer?

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 where is fairly low-friction.

@fosskers
Copy link
Contributor

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 where.

-- 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

@jvoigtlaender
Copy link
Contributor

@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:

  • Some of the names you gave to once anonymous functions are a bit off, from my perspective. For example, gridUpdate and gridRestart. This module as a whole is not really about grids. The grid just happens to be an auxiliary construct the users (beginning programmers) can blend in if it helps them. The real thing they are working with is a graphics plane or maybe game field, so maybe these concepts should have gone into these function names. Of course, you couldn't really know this, because you are not the author of the code and probably haven't studied it to that depth. So the point is only that as an author I would have come up with other names, after putting some thinking into it. I just avoided that lazily by not naming those functions (yet?).
  • You turned a lot of stuff into top-level functions. In contrast, I obviously have a tendency to not do this. In fact, if you were to look back through the commits of that file in my repo, you would find that I had more top-level functions originally, but later turned them into local functions. Generally, I did/do this for all functions that are used only at one place. For example, since makeGridLine (in your version) is used only in makeGrid, I would not want to have it lie around at top-level. Instead, I would put it inside the definition of makeGrid. It helps me to know that this function is used there and only there. Of course, this is a matter of taste, like in your other discussion.
  • But there is another point to this. Namely, as current your version will not work, because while moving out local functions (anonymous or not) to the top-level, you have missed a lot of dependencies. For example, gridUpdate accesses upd, which was in scope inside elaborateDisplay, but when you moved gridUpdate out to the top-level you forgot to explicitly pass upd along. Likewise, gridRestart accesses ini, so you would have to add a corresponding argument to the gridRestart definition and make sure to pass ini from elaborateDisplay to gridRestart. And so on. In some cases several arguments need to be passed on additionally. For example, mouseUpdate depends on upd, x1, and y2, which are in scope in toScreen where mouseUpdate previously lived, and would now all have to be passed from toScreen to mouseUpdate. I wonder whether if you were to revise your version further to fix these missing dependencies, you would like the resulting version less than your current one. This moving stuff out of its "natural (and only use) context" and paying the price of having to pass along dependencies can add a lot of clutter.

@jvoigtlaender
Copy link
Contributor

@fosskers, yes, but to the revised snippet you have to also add the definitions of gridCoord, swap, and redDot. Otherwise, the revised snippet is incomplete, and looks maybe more compact or shorter than the original one only by virtue of "cheating". (Not that I want to specifically defend the original version. As said earlier, I did not pay particular attention to readability when writing that code last weekend.)

@mgold
Copy link
Contributor

mgold commented May 22, 2016

On the point that Haskell users will dislike Elm for not having where, Haskellers are not Elm's target audience, JS programmers are. And coming from an imperative/procedural world, let makes a lot more sense and where seems "backwards".

@yashaka
Copy link

yashaka commented May 22, 2016

I believe that Elm's subject seems to be closer to "declarative reactive
functional programming". And target audience is what will be a consequence
of the former. Declarative and FRP is nevertheless a completely other world
from imperative/procedural. So why care of the latter? :)

"where" is closer to declarative style than "let".

Нд, 22 трав. 2016 о 20:28 Max Goldstein notifications@github.com пише:

On the point that Haskell users will dislike Elm for not having where, Haskellers
are not Elm's target audience, JS programmers are
. And coming from an
imperative/procedural world, let makes a lot more sense and where seems
"backwards".


You are receiving this because you commented.
Reply to this email directly or view it on GitHub
#621 (comment)

@ColonelJ
Copy link

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 <| borrowed from F# is a lot better than Haskell's $ when provided alongside the popular |> (with people even adding these to Haskell). Other stuff, like swapping the meaning of : and :: (from F#, Scala, etc.), tuple constructors (,,), and so on, we can live with, but a lot of other differences just show up as omissions from Elm or are just plain annoying/confusing. (E.g. I don't agree with << instead of ., which represents the circle composition operator from maths. There's already >>> for the other option in Haskell - why need a different syntax?)

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 map/reduce idea has lost favour, and this is another Elm missing feature which shows up just like where in the above list. (Of course there's an issue for it already #147 which is also CLOSED.)

And right, the special use case for where being needed in Haskell. Indeed, that doesn't apply, because Elm doesn't have guard expressions... Great, yet another feature that Elm doesn't have! Maybe it should be added too? (And forget about the multi-way if syntax. This is NOT the same as pattern guards, and is probably going to be removed anyway.)

Coming back to the "only one way to do it" logic, by excluding where from Elm, one might wonder why you wouldn't also consider removing <| and << for exactly the same reason: they don't follow the imperative control flow, and you can use |> and >> instead. Makes sense right, who needs 'em?

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 let is no substitute for where.

@jvoigtlaender
Copy link
Contributor

@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.

@fosskers
Copy link
Contributor

Haskellers are not Elm's target audience, JS programmers are.

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.

@Fresheyeball
Copy link

Fresheyeball commented May 23, 2016

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.

@jvoigtlaender
Copy link
Contributor

@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.

@Heimdell
Copy link

Heimdell commented May 24, 2016

I still vote for where. But not solely because it is present in haskell - that is not the root cause. I vote for this but because it allows me to make things upside-down. For now, Elm allows only downside-up constructions.

Downside-up code breaks logical flow. You read: problem deps... = let now stop and see how I shift bits in ... and think "wait, that doesn't looks like a solution... oh, it is (10 lines below)".

@fosskers
Copy link
Contributor

fosskers commented May 24, 2016

Elm tries to be a Haskell derivate

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 where vs let using that same argument seems silly.

It's basically hopeless to make Elm attractive for JS folks

Is anyone actually claiming this? Adding where to appease the Haskellers is not anti-JS nihilism. Adding where isn't even just for the Haskellers. where is just better than let.

@jvoigtlaender
Copy link
Contributor

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 where. If you look through the old comments above from months ago, you will find that I was arguing against replacing let by where, not against adding where. And if you look throught the comments from the recent days, you will find that I didn't take a position about having or not having where at all. Nor do you know anything about my preferences concerning higher-kinded types or whatever. All I was arguing against was claims that Elm must add where, "because Haskell".

About this:

It's basically hopeless to make Elm attractive for JS folks

Is anyone actually claiming this?

I was writing that because of this paragraph in someone else's comment:

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!)

... followed by essentially an argument that "because of this", Elm must copy Haskell as much as possible in syntax and operator names.

@fosskers
Copy link
Contributor

I was quoting you, yeah. That said:

you seem to be making assumptions about my position that are not true

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.

All I was arguing against was claims that Elm must add where, "because Haskell".

We can agree that that's silly.

I was writing that because of this paragraph...

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):

Designing to infantilize a target audience is how we got Java.

@ghost
Copy link

ghost commented Nov 16, 2016

I'm from JS and I'd like to use where over let, for the reason @Heimdell gave.
JS has already gone to (x) => x * 2, so it shouldn't be too hard for a JS programmer to learn in Elm what would amount to (x) => x * y where y = ....

@kana-sama
Copy link

kana-sama commented Dec 25, 2016

let/in goes against elm style guide

Goal: a consistent style that is easy to read and produces clean diffs.

Haskell:

Before:

f a b = a + b

After new feature:

f a b = a + b + c
  where c = 10

image
Easy to read changed body (it is immediately after the old).

Elm:

Before:

f a b =
    a + b

After new feature:

f a b =
    let
        c = 10
    in
        a + b + c

image
It is like a new function with old name

@ghost
Copy link

ghost commented Dec 26, 2016

@kana-sama Please correct me if I'm wrong, but isn't that just about Elm having a newline after f a b =, rather than about let vs where?

@kana-sama
Copy link

kana-sama commented Dec 26, 2016

@willnwhite Nope.
image
Diff is much cleaner after adding where than after adding let-in, because a + b + c comes immediately after a + b.

@ghost
Copy link

ghost commented Dec 26, 2016

Got it! I was interpreting function body as including the let-in, so I couldn't see it.

@mgold
Copy link
Contributor

mgold commented Dec 26, 2016

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.)

@Birowsky
Copy link

@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 let, just so i can use it inside in. Countless times have I forgotten just because there was one too many line inside the let for me to remember. It's a good thing I have a shortcut for reopening files.

And doctors keep telling me how there is js compiler for haskell, but i stand my ground! And I will. keep. standing. my. ground.

@mgold
Copy link
Contributor

mgold commented Dec 26, 2016

@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.

@srid
Copy link

srid commented Dec 27, 2016

@mgold Yes, contrived: how often do you add a new parameter like this when making a change?

You are referring to the adding of c in this code?

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 where in PureScript, or let in Elm).

When designing Elm, we like to start with a collection of real-world pain points

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?

@fosskers
Copy link
Contributor

isn't it common to abstract out a subset of expression and assign it to a variable for re-use?

This is precisely the purpose of both let and where. The written out characters from the above example were contrived, but the example itself was not.

@mgold
Copy link
Contributor

mgold commented Dec 27, 2016

Yes, it's common enough to extract named expressions, and I may have been fooled by the generic-ness of a, b, and c. However, once a let (or Haskell where) is in use, extracting another item creates a simple diff in both cases.

If I may ask?

I may be grouchy on this thread, but this community never turns away an honest question!

What is the (data) source for these real-world pain points if not the barrage of requests and complains in this Github Issues tracker?

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.

@ghost
Copy link

ghost commented Dec 30, 2016

Continued on the mailing list: https://groups.google.com/forum/#!topic/elm-discuss/KiKF6K9gBKU

@alk-jerber
Copy link

alk-jerber commented Dec 6, 2017

Miso has where. 😉

@Mazuh
Copy link

Mazuh commented Jul 23, 2018

nothing yet about this feature? there's only intended support to let ... in expressions as it is?

@fosskers
Copy link
Contributor

fosskers commented Jul 23, 2018

@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.

@srid
Copy link

srid commented Jul 23, 2018 via email

@lukewestby
Copy link
Member

Hey folks, we're not gonna do where. Please see https://discourse.elm-lang.org/t/why-does-elm-support-let-but-not-where/1634/7 for an explanation.

@elm elm locked as resolved and limited conversation to collaborators Aug 16, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests