Created
November 10, 2019 18:07
-
-
Save showell/7d5fd44f6c809ff7d6a7df2b1067bd0c to your computer and use it in GitHub Desktop.
Elm functions vs. Elm values
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
If you're coming from a language like or JavaScript (JS) or Python, you may | |
used to be a mental model where functions (or lambdas) can actually have | |
zero parameters. In those languages nothing gets evaluated until you | |
invoke the functions. | |
## zero parameters | |
Here's JS: | |
~~~ js | |
> f = function() { return 6 * 7; } | |
[Function: f] | |
> f | |
[Function: f] | |
> f() | |
42 | |
~~~ | |
Here's Python: | |
~~~ python | |
>>> f = lambda : 6 * 7 | |
>>> f | |
<function <lambda> at 0x018103D0> | |
>>> f() | |
42 | |
~~~ | |
Ok, now here's Elm: | |
~~~elm | |
> f () = 7 * 6 | |
<function> : () -> number | |
> f() | |
42 : number | |
~~~ | |
Everything seems roughly equivalent between the tree languages. In all | |
cases there is a syntax to lazily evaluate a function/lambda without any | |
parameters. | |
In Elm, is `f` a function with zero parameters? Or is it a one-parameter | |
function? Well, I don't know what you call `()` here. | |
Next, let's ask if these are equivalent in Elm? | |
~~~ elm | |
f () = 7 * 6 | |
f = 7 * 6 | |
~~~ | |
The answer is they're *not* the same. The second `f` there is a value: | |
~~~elm | |
> f = 7 * 6 | |
42 : number | |
~~~ | |
It gets evaluated immediately. It's a *value*. (If you really want to | |
think of it as a function, then 1) don't, but 2) if you must, consider | |
it to be "eager".) | |
## one parameter | |
Now let's look at one-parameter functions. | |
JS: | |
~~~js | |
> f = function (a) { return a*2; } | |
[Function: f] | |
> f | |
[Function: f] | |
> f(5) | |
10 | |
~~~ | |
Python: | |
~~~python | |
>>> f = lambda a: a*2 | |
>>> f | |
<function <lambda> at 0x03A803D0> | |
>>> f(5) | |
10 | |
~~~ | |
And here's Elm: | |
~~~elm | |
> f a = a * 2 | |
<function> : number -> number | |
> f | |
<function> : number -> number | |
> f(5) | |
10 : number | |
~~~ | |
Note than in the Elm version we don't have any parentheses. Is | |
there some profound reason why Elm's syntax differs from that | |
of Python and JS. Not really. Elm just likes a cleaner syntax. | |
## Mental mappings | |
There's a mapping from JS to Elm that looks like this: | |
~~~ | |
JsToElm("function f (a,b,c,d,e) { return a+b+c+d+e; }") = "f a b c d e = a+b+c+d+e" | |
JsToElm("function f (a,b,c,d) { return a+b+c+d; }") = "f a b c d = a+b+c+d" | |
JsToElm("function f (a,b,c) { return a+b+c; }") = "f a b c = a+b+c" | |
JsToElm("function f (a,b) { return a+b; }") = "f a b = a+b" | |
JsToElm("function f (a) { return a; }") = "f a = a" | |
JsToElm("function f () { return 0; }") = "f () = 0" | |
JsToElm(" f = 0" ) = "f = 0" | |
~~~ | |
If you were to write a three-argument function in JS and mentally | |
translate it to Elm, it goes something like this: | |
~~~ | |
// JS | |
function f(a,b,c) = { return a+b+c; } | |
-- Elm | |
f a b c = a+b+c | |
~~~ | |
Without thinking too deeply about things, you can just strip a lot | |
of punctuation away and get equivalent Elm code. So even if you're | |
still "thinking in JS", you can start to write Elm code. | |
Neither of the above functions get evaluated at page load. You have | |
to "physically invoke" them, for lack of a better term. | |
~~~ | |
// JS | |
f(3, 5, 7) # computes 15 | |
-- Elm | |
f 3 5 7 -- computes 15 | |
~~~ | |
But beware of this pitfall: | |
~~~ | |
# JS | |
# does not call some_helper_function_that_does_lotsa_stuff | |
function f () { return some_helper_function_that_does_lotsa_stuff(); } | |
-- Elm | |
-- DOES call some_helper_function_that_does_lotsa_stuff | |
f = some_helper_function_that_does_lotsa_stuff | |
~~~ | |
All three languages behave similar for funtions with one or more | |
parameters. But it's the zero-parameter case that's tricky, if you | |
don't know the syntax. | |
Here Elm is actually creating a value. And this gets evaluated at | |
page load time. | |
## Discussion | |
Ok, so does any of this matter? | |
Well, it depends. | |
But let's say you have a helper function in one of your views | |
that just has some canned text, and maybe a little helper | |
function to draw a button: | |
~~~ | |
lesson3 : Html Msg | |
lesson3 = | |
let | |
text = | |
""" | |
bla bla bla bla | |
bla bla bla bla | |
bla bla bla bla | |
bla bla bla bla | |
bla bla bla bla | |
bla bla bla bla | |
""" | |
in | |
div [] | |
[ formatText text | |
, nextButton GoToLesson3 | |
] | |
~~~ | |
You may not realize it, but the "lesson3" code will get evaluated | |
at page time. It may look and smell like a function, but it's really | |
a value. | |
Often this is actually desired behavior. If you're gonna show | |
that component eventually anyway, why not compute it up front? | |
Also, why compute it more than once? | |
90% of the time what Elm does here is the *right* thing. | |
But it may surprise you. | |
Let's say `lesson3` is misbehaving. And you want to debug it. | |
You might think it's only gonna get invoked when you navigate to | |
the view that "calls" `lesson3`. But you'd be wrong. | |
Here's what you need to know: | |
*Any definition in Elm that does not have parameters is not | |
a function. It's a value. And it will get evaluated at | |
page load*. | |
If you don't like this behavior, here's the way to express that | |
`lesson3` is lazy: | |
~~~ | |
lesson3 : Html Msg | |
lesson3 = | |
let | |
text = | |
""" | |
bla bla bla bla | |
bla bla bla bla | |
bla bla bla bla | |
bla bla bla bla | |
bla bla bla bla | |
bla bla bla bla | |
""" | |
in | |
div [] | |
[ formatText text | |
, nextButton GoToLesson3 | |
] | |
~~~ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment