-
-
Save munro/7a57a36cbf6eeac3a1a4 to your computer and use it in GitHub Desktop.
-- Explicitly stating the order of computation `a (b c)` makes this not compile | |
-- While, if we used a C style, order of computation would be explicit by default `a(b(c))` vs `a(b, c)` | |
example1 = a b c | |
where | |
a first second = first ++ second | |
b = "hey" | |
c = " world" | |
-- Explicitly stating `a (b c)` makes this compile | |
example2 = a b c | |
where | |
a str = str | |
b str = "hey" ++ str | |
c = " world" | |
-- But we can create an unambiguous version of the function using tuples! | |
example3 = a(b, c) | |
where | |
a(first, second) = first ++ second | |
b = "hey" | |
c = " world" | |
main = do | |
putStrLn example1 | |
putStrLn example2 | |
putStrLn example3 |
What I think is also tripping you up is that, while Haskell has a pair type, and you can write:
example1 = a ("hey", " world") where a (b, c) = b ++ c -- we can also write a = uncurry (++)
OH MY GAWD
I was thinking about it more, and a problem with C-style is operators are an exception, because you obviously don't write (123)+(321)
... that just looks weird. Also, like you said things like if ... then ... else ...
could be thought of as a series of functions.
Taking another look, Haskell style is really a super set of C-style, because as you demonstrated, you can replicate that style within Haskell! So an explicit set of arguments should be looked more as a type, being a tuple! Mondo cool.
There is however an ambiguity in evaluation order here
This is my favorite part of Haskell! I love how it feels like I'm programming from left to right, not top to bottom.
Not sure what the proper term for order of computation
is, perhaps the correct way for what I'm trying to say is: ambuguitiy in function application ... which is conceptually different to me than evaluation order.
Thanks for the amazing response! You completed the puzzle in my head, ♡ #haskell
. If something I said sounds wrong please school me again.
Heya. Saw your gist randomly while browsing.
It's just a different syntax, not an "ambiguity." Let me take JS for my "C style" language. Haskell has explicitly stated that the function application
a b c
is always left-associative, in other words it is always exactly the same as(a b) c
in Haskell, ora(b)(c)
in JavaScript. I like to remember this as the rule of "greedy nom": a function always eats up exactly what's in front of it, no more and no less. Do you want it to eat a bigger expression? Then parenthesize.What I think is also tripping you up is that, while Haskell has a pair type, and you can write:
it's considered non-idiomatic Haskell to do it that way. Haskellers would rather just write
a "hey" " world"
and be done with it. Following the "greedy nom" idea: Do you want it to eat up multiple arguments? You do that by having the function nom one argument, returning a function which noms a second. In C-style,a b c
becomesa(b)(c)
. You need a language which has first-class functions for that to even make sense, but if you have first-class functions, thena(b)
returns a function which consumes the last element.Operators have a lower precedence than function applications, so
a b c + d e f
would look like the JavaScript:plus(a(b)(c), d(e)(f))
. And finally, there are special forms like\x y z ->
anonymous functions,case ... of ...
pattern matching,let ... in ...
binding, and theif ... then ... else ...
control form, which all happen with lower precedence than operators. In particular, the anonymous function will often last until the end of the current expression.There is however an ambiguity in evaluation order here: it is totally unimportant to Haskell's semantics whether
d e f
ora b c
happens first, due to a property called referential transparency. There is a non-semantic difference between them when you get into exceptional cases likeerror "A" + error "B"
, but Haskell-the-mathematical-language treats both of those as equivalent to the infinite loopa str = a ""
(which type-checks toa :: String -> x
but of course crashes the program when evaluated on any string).Haskell can do this because everything with any other effect is put into the
IO
type, which uses functions to sequence effects (since a function application does still have a well-defined order even if the order doesn't matter when applying a function to 2+ args).Your
do
block is actually just syntactic sugar for an operator called>>
and you can writemain = putStrLn example1 >> putStrLn example2
if you wish. The more generic operator is called>>=
, and it is the driving force behind the do-blocks of Haskell. With it, you can do things like:We can of course write the
>>
and>>=
in do-notation, but if you can understand all of that then I can confidently say that you understand most of the weird syntax of Haskell.