What does let 5 = 10 do? Is it not an assignment operation?
Asked Answered
M

3

40

If I say let 5 = 10, why does 5 + 1 return 6 instead of 11?

Muntjac answered 8/10, 2015 at 4:15 Comment(8)
An immediate answer would be "because you can't redefine what 5 is". However, I am actually quite surprised by writing let 5 = 10 being even possible!Garvey
what you can do is overload the +: let 1+1=3 in 1+1 ;)Lurk
btw: I think the 5 in let 5 = 10 is still a pattern (just one that is never matched) so it will not bind anything (as in let (x,5) = (6,6) )Lurk
@Carsten Indeed -- that is what Chapter 4 of the Report says, assuming that I'm parsing it correctly.Garvey
If I say let 5 = 10, when I type 5 into ghci it returns 10Muntjac
@brett It doesn't happen here. If reproduced, it would be a serious bug in ghci.Vano
Even if you do declare 5=10 you should run a 5==10 check and see if it is returning true or not ...Psychomancy
Prelude> :set -XBangPatterns Then Prelude> 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
V
53

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.

Vano answered 8/10, 2015 at 4:38 Comment(5)
Reassuringly let (5,x) = (10,True) in x delivers *** Exception: <interactive>:2:5-21: Irrefutable pattern failed for pattern (5, x).Unpaidfor
This is silly for let-bindings, but not for all: e.g. only5 xs = [5 | 5 <- xs] makes sense (if you're OK with lists fail).Scarce
@user3237465 yes, I only meant let-bindings. Updating.Vano
pigworker: and let (~5,x) = (10,True) in x returns True only because we've explicitly told the compiler to assume the 5 pattern matched.Lezley
Similar case: let [] = [1,2] in TrueLezley
M
17

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.

Moolah answered 8/10, 2015 at 8:19 Comment(1)
Similar pattern matches can be useful. In particular, it's not unusual to write 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
P
2

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)
Prelatism answered 21/5, 2017 at 16:4 Comment(5)
"Since Haskell is lazy, your code will succeed if you do not use the resulting value" -- It is worth mentioning it also matters that 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
@duplode, I think that lazy default in let and where was a bad idea, and is the reason bang patterns can be confusing sometimes.Hoxha
@Hoxha What about binding to an infinite list? For example let nats = [1..] in takeWhile isOkay natsPrelatism
@Teodor, I was referring to patterns on the LHS in 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
@Hoxha Thanks for the clarification. I agree with your point.Prelatism

© 2022 - 2024 — McMap. All rights reserved.