Closed jonashaag closed 9 years ago
where
is easily the number 1 thing in Elm I want right now.
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 ;)
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.
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.
The current decision is to not support where
. I want to avoid adding redundant syntax for the foreseeable future.
I would gladly give up let
for where
:grin:
I think a lot of people agree with you @Apanatshka
Me too, @fosskers!
Me too, I find let
very confusing.
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.
@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.
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.)
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.
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.
@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
@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?
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".
@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.)
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.
@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
?
@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.
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.
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.
@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:
@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:
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.
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
@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:
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?).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.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.@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.)
+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".
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.
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:
where
.where
and take away let
.Do you only disagree with the first of those?
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.
@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.
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.
@jvoigtlaender with the Y combinator you can write any let
expression (recursive or not) as a series of lambda expressions.
@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?
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.
@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.
@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.)
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.
@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... )
Last words in a nutshell.
Where:
let
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.
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
I can see that causing confusion.
It is a poor substitute for where
for sure, but all that it means is: the order of let
and in
doesn't matter.
I like the in ... let
proposal :)
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.
where
+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...
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.