elm / compiler

Compiler for Elm, a functional language for reliable webapps.
https://elm-lang.org/
BSD 3-Clause "New" or "Revised" License
7.51k stars 660 forks source link

Feature: 'where' expressions #621

Closed jonashaag closed 9 years ago

jonashaag commented 10 years ago

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 commented 10 years ago

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

Apanatshka commented 10 years ago

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 commented 10 years ago

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 commented 10 years ago

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 commented 9 years ago

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

Apanatshka commented 9 years ago

I would gladly give up let for where :grin:

fosskers commented 9 years ago

I think a lot of people agree with you @Apanatshka

Heimdell commented 9 years ago

Me too, @fosskers!

BethAr commented 9 years ago

Me too, I find let very confusing.

jvoigtlaender commented 9 years ago

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 commented 9 years ago

@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 commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

@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 commented 9 years ago

@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 commented 9 years ago

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 commented 9 years ago

@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 commented 9 years ago

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 commented 9 years ago

@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 commented 9 years ago

@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 commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

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

Apanatshka commented 9 years ago

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

fosskers commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

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

jvoigtlaender commented 9 years ago

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

polux commented 9 years ago

+1, I find where clauses both easier to read and to write then let..in clauses. As a reader of a function with a where clause, I can focus on the high-level aspects of the function before diving into the details. As a writer I can lay out the skeleton of the function before filling out the "holes".

mgold commented 9 years ago

I disagree. Elm's philosophy has been "there's only one way to do it", which makes writing programs almost automatic (everything magically follows the Elm Architecture). It's a neat academic point that because of purity and immutability we can reorder our definition (and lazily evaluate them in Haskell), but I think let....in is fine.

jvoigtlaender commented 9 years ago

Disagree with what? Some of the discussion above argues for replacing let...in by where. Then the philosophy of "there's only one way to do it" would still be adhered to. So there's two proposals under discussion here:

Do you only disagree with the first of those?

polux commented 9 years ago

But let x = a in b and (\x -> b) a are already two ways to do it.

I'm not seriously suggestion let...in should be gotten rid of, but "there's only one way to do it" is a rather vague notion in my opinion.

jvoigtlaender commented 9 years ago

@polux, well, that's only part of the story, right? Because there is let {x = f y; y = z} in u, for which no equivalent with just an application of a lambda-expression exists. And vice versa, a stand-alone lambda-expression cannot be replaced by a let-expression. So the two are not interchangeable.

Yes, some syntactic subclass of let-expressions is interchangeable with some syntactic subclass of applications of lambda-expressions. But that's not the same kind of relationship as between let and where, where one completely subsumes all use cases of the other.

polux commented 9 years ago

Let expressions are at least a special case of lambda applications, assuming you're ready to play tricks with fixpoint operators and tuples, aren't they? I didn't mean to be serious anyway, and I see your point, which I agree with.

joneshf commented 9 years ago

@jvoigtlaender with the Y combinator you can write any let expression (recursive or not) as a series of lambda expressions.

jvoigtlaender commented 9 years ago

@joneshf, I know. But, as at least @polux admits, that's beside the point in discussing the surface language (having or not having let and where and lambda-expressions). Or how, exactly, do you envision the Y combinator showing up in your Elm program?

polux commented 9 years ago

I guess you can always circumvent the absence of where by writing

let body = ...
    x = ...
    y = ...
in body

That's a weird style but it addresses my issues with let.

joneshf commented 9 years ago

@jvoigtlaender sorry, I thought you were rebutting the fact that you can write any arbitrary let expression as a lambda expression.

Or how, exactly, do you envision the Y combinator showing up in your Elm program?

I'm not sure what your question is here. Are you asking how to implement the Y combinator?

EDIT:

But, as at least @polux admits, that's beside the point in discussing the surface language (having or not having let and where and lambda-expressions)

I never said it was realistic or appropriate.

jvoigtlaender commented 9 years ago

@joneshf, no, I'm not asking how to implement the Y combinator. I know all that. I teach it. Though, btw, you would indeed have a hard time implementing the Y combinator in Elm. Given that Elm is not the untyped lambda-calculus and all that.

But the whole point of this discussion (read the many messages above if you haven't) is not about theoretic connections between these language concepts, and whether one feature (let expressions) can in principle be simulated by another. The discussion is exactly about what is "realistic or appropriate" in code meant for readability. So in that light I was refuting the idea that the presence of let in Elm could be "attacked" by observing that let is superfluous since it can be replaced by some convoluted uses of lambda expressions and fixpoints. (If you would be willing to go down that road, why having even lambda-expressions? Already the old supercombinator papers tell us that we can do without local bindings and without lambda-expressions. But if you want to discuss all these relationships, that's decidedly not what the above discussion of the Elm surface language was about. It would be a nice academic exercise, but not very relevant for the issue under discussion here. I think.)

joneshf commented 9 years ago

So in that light I was refuting the idea that the presence of let in Elm could be "attacked" by observing that let is superfluous since it can be replaced by some convoluted uses of lambda expressions and fixpoints.

Ah, gotcha. I understand now.

Apanatshka commented 9 years ago

@fosskers I think we basically agree :smile:

@jvoigtlaender I agree with your points. I didn't study the code well enough to find the best names for the functions. Top-level functions is something of a code style preference. I like top-level helper functions because I identify a function with lots of local helpers as one large blob that needs to be understood completely. It also hides a lot of complexity by directly using local variables, and I guess I missed a bunch while pulling them to the top-level. You'll find in the revisions that I tried to pull displayGrid out of elaborateDisplay but couldn't without introducing way too many arguments. Sometimes it's valuable to have a local function, but in my opinion they usually hide complex dependencies and you're forced to write more cleanly separated code when you disentangle them. But as I said, I didn't want to go that far with the refactoring :) Anyway, I think we understand each others preferences and opinions by now.

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

I guess that's the question. The design decision is whether this is a warning from a lint tool that recommends a better code style or if you constraint the language to not allow (one form of) "bad code". Regardless, I don't think it would be easy to convince Evan of the benefits here, considering his reaction to this discussion. I'm not interested in leading an attempt to convince Evan to do this experimental change in hopes of improving general code readability. But I certainly enjoyed discussing this with you. So thanks @jvoigtlaender, @fosskers and others. This was interesting. (Of course if someone else wants to try bringing this idea into Elm, I'll gladly support that. But I don't have the bandwidth for it myself, I should be working... )

fosskers commented 9 years ago

Last words in a nutshell.

Where:

Let:

Evan seems to have his mind made up about this, but no one should be surprised to see a stream of "+1 yes Elm needs where" posts in this thread for the next years.

BethAr commented 9 years ago

I'm very used to where syntax in Haskell. Without going into details, would it be at least possible to have a reversed let variant? That is, remove the let --> in order requirement?

So, besides:

function =
    let these variables
    in this expression

We could also do:

function =
    in this expression
    let these variables
fosskers commented 9 years ago

I can see that causing confusion.

BethAr commented 9 years ago

It is a poor substitute for where for sure, but all that it means is: the order of let and in doesn't matter.

polux commented 9 years ago

I like the in ... let proposal :)

thSoft commented 9 years ago

A problem can be approached either top-to-bottom (where) or bottom-to-top (let). Functional programming itself lends towards the former, that's why I would prefer having only where or both where and let in Elm.

Birowsky commented 8 years ago

where

yashaka commented 8 years ago

+1 for where regardless of keeping let..in or not

because it's closer to the "declarative" style of coding. Elm is FRP, FRP is declarative by its nature, then why not support where?

@evancz

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?

I believe that this should not be an argument. Because we definitely are talking about FRP/declarative style of programming and common JS programmers are nevertheless far from these ideas.

Just consider this official example from elm architecture tutorial:

view : Signal.Address Action -> Model -> Html
view address model =
  let counters = List.map (viewCounter address) model.counters
      remove = button [ onClick address Remove ] [ text "Remove" ]
      insert = button [ onClick address Insert ] [ text "Add" ]
  in
      div [] ([remove, insert] ++ counters)

As for me this is pretty good example of "have to read from the bottom to top" which is pretty uncomfortable, because it is against to the normal "reading flow" - from top to bottom, and against the declarative abstract programming, when first you tell about some abstraction, and then describe details...

All the time when playing with clojure I felt this "tiny pain" when opening each file of code and scrolling to the end to start reading it from bottom to top.

There is one idea that the code is good when it tells a good story. And "bottom to top" does not help with this much...

The following tells a better story:

view : Signal.Address Action -> Model -> Html
view address model = 
    div [] ([remove, insert] ++ counters)
where
    remove = button [ onClick address Remove ] [ text "Remove" ]
    insert = button [ onClick address Insert ] [ text "Add" ]
    counters = List.map (viewCounter address) model.counters

I understand that this may be a tricky task. Because it's really not obvious to what "previous" context should where apply, and how to deal with indentation.

From other point of view maybe where should not be as powerful as let .. in. Because too much of nested "where-s" will end in too complicated code...

Actually I see the need for where only in describing in declarative way - the functions and data that is needed only inside one function/expression. I don't see that "nesting support" will help much with this. This can make the "task" much easier to implement.

The other way of implementation may be to go the "let..in" way in context of "using two words".

E.g. design it like:

view : Signal.Address Action -> Model -> Html
view address model = 
    return 
        div [] ([remove, insert] ++ counters)
    where
        remove = button [ onClick address Remove ] [ text "Remove" ]
        insert = button [ onClick address Insert ] [ text "Add" ]
        counters = List.map (viewCounter address) model.counters

Just "return" seems to be "bad" choice in case Elm will use return the same way haskell uses it. But haskell's return - seemed nevertheless awkward to me... So... maybe it's a good choice to use it as a starting point for where scope in Elm.

Currently no any other proper words comes to my head... Any ideas? do .. where ? explain .. where ? the .. where ? this .. where ? give .. where ? take .. where ? is .. where (but this will be tautologic a bit in ... = is ... where ...

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.

Yes, for sure there are many tasks of much higher priority. But this means that the "where support" should be postponed (e.g. via marking with correspondent label), but not closed and forgotten...