> A big gotcha for me was understanding the -> syntax. How can a function that accepts two arguments possibly have a type annotation like this?
connectWords : String -> String -> String
This is one of the most maddening things for me about Haskell-like languages. I can never remember if -> is left or right associative. I mean there is only one way that makes sense:
# String -> (String -> String)
But it could also be
# (String -> String) -> String
Of course you get used to it after a while, but a nagging feeling remains. I would really prefer a bit of syntactic sugar.
If you consider the case of currying and the fact that Haskell functions can be partially applied by just not providing all of the parameters, it becomes clear that only the first version you wrote is correct. It is a single-parameter function with a return type of another single-parameter function.
I think when these type annotations are used, at least traditionally in e.g. Haskell, the functions are curried by default. In other words, all functions only take one argument. If all functions only take one argument, then it has to be the first interpretation, not the second.
Thanks to ncd for clarifying, here are my own comments too. When you write
f: Int -> Int -> Int
f a b = a + b
the compiler (at least in Haskell) creates f as a function that takes a single argument, a, and outputs a function. That outputted function takes a single argument, b and outputs an Int.
When you apply the function f:
f 5 7
what's really happening here is that f is first applied to 5. This creates a new function, call it g, taking in a single integer and outputting that integer plus 5. Then, this new function is applied to 7. (The compiler may optimize a lot of this away, but that is how you should think of what's happening in my opinion.)
I guess this comes back full circle to the original point: you should think of everything being left-associative here, so by default the above is interpreted as
It's a function that takes a String and returns a new function which takes a String and returns a String. All functions in Haskell/Elm are arity 1.
So in order to construct functions that accept more than one argument, you actually return successive functions that apply successive arguments, known as currying.
This is really important when looking at it from "the outside" (or, one is too lazy to lookup the documentation ;-). Without knowing that, it could be a function that takes a string and compiles it to a function that takes two strings (ie: a macro, I guess). Or one string, and returns a string. Or...
Does this kind of 1/arity have performance implications for Haskell? Does the compiler lower (or is it rise? inline?) the functions to produce efficient machine code?
In this case, you think of `connectWords` as a function that takes two arguments. But since it is curried, you can also do this:
let prefix = connectWords "Hello "
world = prefix "world"
bob = prefix "bob"
in ...
`world` is "Hello world", and `bob` is "Hello bob". That is the power of currying. A maybe more useful example is specifying the mapping function in `List.map` without supplying the list to map over. This allows you to use the same map with multiple lists.