Skip to content

Instantly share code, notes, and snippets.

@munro
Last active August 29, 2015 14:07
Show Gist options
  • Save munro/7a57a36cbf6eeac3a1a4 to your computer and use it in GitHub Desktop.
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
@crdrost
Copy link

crdrost commented Oct 15, 2014

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, or a(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:

example1 = a ("hey", " world")
    where a (b, c) = b ++ c  -- we can also write a = uncurry (++)

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 becomes a(b)(c). You need a language which has first-class functions for that to even make sense, but if you have first-class functions, then a(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 the if ... 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 or a 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 like error "A" + error "B", but Haskell-the-mathematical-language treats both of those as equivalent to the infinite loop a str = a "" (which type-checks to a :: 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 write main = 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:

import System.Random (randomRIO)

affix 1 = "1st"
affix 2 = "2nd"
affix 3 = "3rd"
affix n = show n ++ "th"

randomInteger lo hi = randomRIO (lo, hi) :: IO Integer

guessingGame target prompt n = putStrLn prompt >> getLine >>= \guess ->
        if target == guess then
            putStrLn $ "You got it on your " ++ affix n ++ " try!"
        else if n >= 5 then
            putStrLn $ "Nope. It was " ++ show target ++ ". You lose!"
        else
            guessingGame target "Nope. Try again: " (n + 1)

main = randomInteger 1 10 >>= \n -> guessingGame (show n) "Guess my number, 1-10: " 1

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.

@munro
Copy link
Author

munro commented Oct 16, 2014

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment