If I say let 5 = 10
, why does 5 + 1
return 6
instead of 11
?
When you say
let 5 = 10
it's not a redefinition of 5, it's a pattern matching, the same which occurs when you say
foo 5 = undefined
... foo 10 ...
The pattern simply fails if it's ever matched.
In let-expressions the match is lazy. This means the match is only being done when a variable bound by it is evaluated. This allows us to write things like
let foo = undefined in 10
In your expression, no variable is bound, so the pattern is never matched.
Arguably such patterns with no variables make no sense in let-bindings and should be detected by the compiler, but the language doesn't forbid them.
let (5,x) = (10,True) in x
delivers *** Exception: <interactive>:2:5-21: Irrefutable pattern failed for pattern (5, x)
. –
Unpaidfor only5 xs = [5 | 5 <- xs]
makes sense (if you're OK with lists fail
). –
Scarce let (~5,x) = (10,True) in x
returns True
only because we've explicitly told the compiler to assume the 5 pattern matched. –
Lezley let [] = [1,2] in True
–
Lezley Basically,
let 5 = 10 in ...
is equivalent to
case 10 of ~5 -> ...
Note the ~
, which marks a lazy, or irrefutable pattern. This is a pattern that matches everything, and that postpones the match to the point where some variable is actually demanded. There are no variables in the pattern 5
, so nothing ever happens.
This corner case is quite useless, and arguably the compiler should emit a warning here.
To clarify the meaning of lazy patterns, consider this:
case f 3 of
(x,y) -> g 10 x y
here f 3
is evaluated first (to WHNF), exposing the pair constructor. Then x,y
are bound to the (not yet evaluated) pair components. Finally, g 10
is computed, the result is applied to x
(which might be demanded now), and then to y
(which may cause x
or y
to be demanded).
By comparison,
case f 3 of
~(x,y) -> g 10 x y
does not start with evaluating f 3
. Instead x
is bound to the unevaluated fst (f 3)
and y
is bound to the unevaluated snd (f 3)
. We instead start with evaluating g 10
. Then, we apply that to x
: this might cause x
to be demanded, triggering the evaluation of f 3
. Then, we apply the result to y
, causing a similar evaluation. Most implementation will actually share the result of f 3
between x
and y
so that it is computed at most once.
catch m $ \ ~MyException -> ...
, where the non-binding lazy pattern match is a convenient way to provide the exception type. But using a numeric literal in such a fashion seems like a stretch. –
Hoxha As @n.m. is saying, you are pattern matching. Here are some examples.
Pattern matches can succeed
Prelude> let (a, 10) = (15, 10) in a
15
or fail.
Prelude> let (a, 10) = (15, 15) in a
*** Exception: <interactive>:5:5-22: Irrefutable pattern failed for pattern (a, 10)
Since Haskell is lazy, your code will succeed if you do not use the resulting value. This is essentially what you're doing:
Prelude> let (a, 10) = (15, 15) in "Something else"
"Something else"
Note that the types must still check:
Prelude> let (a, 10, 999) = (15, 15) in "Something else"
<interactive>:7:20: error:
• Couldn't match expected type ‘(t, Integer, Integer)’
with actual type ‘(Integer, Integer)’
• In the expression: (15, 15)
In a pattern binding: (a, 10, 999) = (15, 15)
In the expression: let (a, 10, 999) = (15, 15) in "Something else"
• Relevant bindings include a :: t (bound at <interactive>:7:6)
let
bindings match lazily by default. case (15, 15) of { (a, 10) -> "Something else" }
crashes, while case (15, 15) of { ~(a, 10) -> "Something else" }
(which, thanks to the lazy match, is equivalent to your third example) succeeds. –
Garvey let
and where
was a bad idea, and is the reason bang patterns can be confusing sometimes. –
Hoxha let nats = [1..] in takeWhile isOkay nats
–
Prelatism let
and where
, not simple variable bindings. I think that let p = e1 in e2
should be exactly the same as case e1 of p -> e2
, no matter what p
looks like. Unfortunately, there's no fixing that in Haskell, but hopefully future languages will consider doing it my way. –
Hoxha © 2022 - 2024 — McMap. All rights reserved.
5
is". However, I am actually quite surprised by writinglet 5 = 10
being even possible! – Garveyoverload
the+
:let 1+1=3 in 1+1
;) – Lurk5
inlet 5 = 10
is still a pattern (just one that is never matched) so it will not bind anything (as inlet (x,5) = (6,6)
) – LurkPrelude> :set -XBangPatterns
ThenPrelude> let !5 = 10
Yields*** Exception: <interactive>:9:5-11: Non-exhaustive patterns in pattern binding
. Laziness was hiding the failed pattern match and allowed your misunderstanding to persist. – Taxation