For the examples given below, I assume you have "The Glorious Glasgow Haskell Compilation System" ghc installed and use ghci for Haskell's interactive interpreter (ghc 8.10.1) and python installed and use the interactive interpreter python (Python 3.0). In addition, this go round I've thrown in some Ruby examples for good measure. I'm using ruby with the interactive interpreter irb (using Ruby 1.9-rc2).
A quick word about functions. In this blog, when I say functions, I will be referring to the concept of a "pure function" in the functional programming sense unless noted otherwise.
A pure function:
- maps zero or more input arguments to a single output argument (note that a function which takes no arguments and returns an output is typically called a constant)
- and does nothing else!
In Python, we can write such a pure function as follows:
In Ruby, the same function would be:
# add.py
def add(x, y):
return x + y
In Haskell, we write:
# add.rb
def add(x, y)
x + y
end
The Python and Haskell functions are defined in two lines, Ruby in three -- all extremely short and readable for this trivial exercise. The first line of the Haskell function is called a type declaration. The Haskell definition could, in fact, be shorter by omitting the type declaration since type declarations in Haskell are usually optional. However, I highly recommend using type declarations based on what I've seen of the language so far as they are an excellent form of documentation for your code.
-- Add.hs
add :: (Num a) => a -> a -> a
add x y = x + y
Note: I say "type declarations are usually optional" because there are cases when types are ambiguous and so the compiler needs the programmer to explicitly fill in the context. At any rate, always write a type declaration and you'll have no problems.
TO TYPE OR NOT TO TYPE
So what is this Haskell type declaration telling us, anyway? The "::" translates to "has type" in English and the entire type declaration's meaning is roughly as follows:
add has type "a" to "a" to "a" given that "a" is a kind of Number (Num)What this means is that the function "add" is a mapping from two numerical types to the same kind of numerical type. For example, in "3 + 2 = 5" the "+" function maps a number 3 and number 2 to a number 5. That is, "+" takes two Number types and maps them to an output which is also of the same Number type. What are some "Number types"? Well, for example, you might consider all real numbers (for example -4.2, 10.3, 100.0). Another set of numbers are the counting numbers (1, 2, 3, 4). We could also consider complex numbers and rational numbers.
This is subtle and I don't want to dwell on it too much here as we'll return to this later but we are defining add over all arguments of a given class of types (in this case, numbers).
In contrast, Python and Ruby, by design, avoid any upfront discussion of types and do not enforce any constraints on the user. This is both powerful and (sometimes) dangerous. In fact, in both the Python and Ruby implementations, x and y need not be any kind of numbers at all. The only thing we know about x and y is that they should implement the "+" operator. However, for example, as written, the following would be a perfectly valid function call from Python:
In contrast to Ruby and Python, with the Haskell code we are being much more explicit―the add function only works on types of numbers.
>>> from add import add
>>> add("I'm not a ", "number!")
"I'm not a number!"
As a quick aside, there is a mantra from the Python community called the "Zen of Python" (which can be accessed from within Python using "import this" at the Python interactive prompt). One of the key "Pythonic values" is stated as "explicit is better than implicit". Therefore, as a Pythonista at heart, I appreciate being able to explicitly say that I want add to only apply to those types that have the qualities of numbers.
Let's explore calls to our Python add implementation. The first line loads function add from file add.py (assumed to be in the same directory):
And similarly for Ruby (using irb). Here the first line loads the file "add.rb" assumed to be in the same directory:
>>> from add import add
>>> add(3, 4)
7
>>> add(3.0, 4)
7.0
>>> add("I'm not a ", "number!")
"I'm not a number!"
In contrast, let's look at our Haskell function. Note that we define our add function in a separate file called "Add.hs":
irb(main):001:0> require("add")
=> true
irb(main):002:0> add(3, 4)
=> 7
irb(main):003:0> add(3.0, 4)
=> 7.0
irb(main):004:0> add("I'm not a ", "number!")
=> "I'm not a number!"
The Haskell implementation does not allow us to apply the function add to anything other than numbers as is shown by the error message when we tried to add the strings together. As we will see later, if it is our intention to allow Haskell to add both strings and numbers with add, this is possible (I'll make this my next blog post). Note that for each call of add, numbers must be of the same type. In the call "add 3.0 4", "4" is being understood by the compiler as the floating point type "4.0".
Prelude> :load ./Add.hs
*Main> add 3 4
7
*Main> add 3.0 4
7.0
*Main> add "I'm not a " "number!"
<interactive>:1:0:
No instance for (Num [Char])
arising from a use of `add' at <interactive>:1:0-25
Possible fix: add an instance declaration for (Num [Char])
In the expression: add "I'm not a " "number!"
In the definition of `it': it = add "I'm not a " "number!"
So far you may be wondering what all the fuss is about. It would seem at first glance that Python and Ruby are much more powerful than Haskell as our add function can be used anywhere it can be used without having to get into this esoteric discussion of types. However, if you're feeling that way, I'd like to shift your thinking a bit. Yes, Python is able to dynamically consume any type that implements "+" in its add function and that is powerful. However, Haskell's power *is* in the confines we've placed on add. The Haskell version of add only works for numbers and that, my friends, is powerful as we shall later see.
In Haskell, types are your friends as opposed to C, C++, and Java where they seem to be more of a nuisance. But rejoice, my friends, for Haskell types are out on the front lines with us watching our backs. Python and Ruby favor "duck typing" -- if it looks like a duck, quacks like a duck, then for all extents and purposes, it's a duck. Types (or classes of types at any rate which I might be so bold as to refer to as interfaces) are implied in Python and Ruby. You go along assuming everything is alright until it isn't (in which case you use the exception mechanisms to deal with things). However, the onus is on the programmer to keep things straight. Now let's face it, Python and Ruby are extremely powerful, extremely fun, and work quite well. However, I hope to demonstrate in this blog to both you and myself that the Haskell way is not only all these things, but more. That is, the Haskell solution is an elegant solution.
In the context of our problem thus far, did we really intend to be able to add strings with our add function as Python and Ruby let us do?
SPICY CURRYING
Let's cover just a few more concepts before leaving our simple add function. Let's revisit the type definition for the Haskell add function. In truth, Haskell functions only operate on one argument at a time and this can be seen by placing parenthesis in the type definition as follows:
What this definition tells us is that add, when given one argument of a Numeric type ("a"), returns a function that takes an argument of the same kind of Numeric type ("a") and returns a value of that same Numeric type. Let's show an example:
add :: (Num a) => a -> (a -> a)
The function add3 is a function that takes a Numeric type and adds 3 to it. Let me show you an equivalent way to write this:
add3 :: (Num a) => a -> a
add3 y = add 3 y
Notice in this case we are specifically saying that add3 is the function which adds 3 to a number. The extra variable is missing but if you follow the type signature for add given only one variable, you will see the type signature matches add3's type signature. This form is called "point free style" where "point" here has more of a meaning of "value". Let's look at some examples:
add3 :: (Num a) => a -> a
add3 = add 3
Can you see the equivalence between "(add 3) 3" and "add3 3"? "add 3" is the function that adds 3 to its next argument. "add3" is also the function that adds 3 to its next argument.
-- Add.hs
add :: (Num a) => a -> a -> a
add x y = x + y
add3 :: (Num a) => a -> a
add3 = add 3
----------------
Prelude> :l ./Add.hs
*Main> add3 3
6
*Main> add3 3.0
6.0
*Main> (add 3) 3
6
*Main> (add 3) 3.0
6.0
The same sort of thing can be achieved in Python with a little more work using the functools library:
Note with the last call in Python, we get a type error which shows that, although Python is dynamically typed, it is also strongly typed. That is, the Python interpreter does not willy-nilly do type conversion for us to try to make sense of adding the number 3 to the string "a string?". Haskell, as we previously demonstrated, is statically typed, and thus would also not put up with such foolishness as trying to add a number to a string.
# add.py
def add(x, y):
return x + y
----------------
>>> from add import add
>>> import functools
>>> add3 = functools.partial(add, 3)
>>> add3(3)
6
>>> add3(3.0)
6.0
>>> add3("a string?")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "add.py", line 6, in add
return x + y
TypeError: unsupported operand type(s) for +: 'int' and 'str'
A similar opportunity to define add3 exists using the lambda anonymous function (which incidentally originates from functional programming which originated from lambda calculus, hence the name):
So thus, Python, true to its philosophy of making easy things easy and hard things possible (although admittedly this isn't that hard), does have the ability to do partial function application or Currying (named after logician Haskell Curry, for which Haskell is also named).
>>> from add import add
>>> add3 = lambda x: add(x, 3)
>>> add3(4)
7
Let's look at how we could do currying in Ruby:
The syntax in Ruby is a bit clumsy in that it requires the "." before the parenthesis in a function call (that is, "add3.(4)" instead of "add3(4)"). Although you'd like there to be a transparency between a lambda function called add3 and a method called add3 (defined using "def"), that's not the way it happens when you use Ruby's lambda syntax. There may be a way around this but I don't know it. Other than that, though, lambda's in Ruby are pretty straightforward. Note that this is Ruby 1.9 syntax (based on release candidate 2). The Ruby 1.8 usage is "lambda {|x| add(3, x)}".
irb(main):001:0> require("add")
=> true
irb(main):002:0> add3 = ->(x){add(3, x)}
=> #<proc:0x0015a8c4@(irb):2>
irb(main):003:0> add3.(4)
=> 7
Haskell, having a different emphasis than both Python and Ruby offers a much more straight-forward syntax for currying -- namely, no special syntax at all. When we want to partially apply or curry a function in Haskell, all we have to do is not provide all the arguments and it's as simple as that.
SYMBOLS AND SECTIONS
Let's make a couple of final points regarding Haskell before we leave this section. Let's talk about symbols as functions and the notion of prefix and infix functions (operators). We have been using the name "add" for the function that takes a number and a number and returns their addition. However, from mathematics, we have a perfectly good symbol that represents this "add" function: "+". In fact, "add" is defined using "+" (which is ultimately defined by the compiler). Haskell has a syntax for defining symbols as functions. If the function "add" was built into the language, we could define "+" as follows:
Hopefully you'll forgive me for circularly defining "+" using "add" (which is defined using "+") but the point of this exercise is not to define addition, but instead to demonstrate how symbols can be used and defined as functions.
(+) :: (Num a) => a -> a -> a
(+) x y = add x y
Note that when we write the "+" symbol in parenthesis, the symbol has the analogous role to the "add" function. That is "(+) x y" is "add x y". This is called prefix notation. The function name is given and the arguments follow. In contrast, the normal way of using "+" is called infix notation: "x + y". Here, the symbol "+" is written, without parenthesis, between its arguments. Alternatively, we could have defined "+" using infix notation as well (note the type definition is always defined in prefix form):
Functions with two arguments (i.e., functions of arity 2) can also be written in infix notation using the back quote as follows:
(+) :: (Num a) => a -> a -> a
x + y = add x y
The "let" syntax has not been introduced yet but for reasons I will touch on later, it is needed when at the interactive prompt ("ghci").
Prelude> :l ./Add.hs
*Main> 3.0 `add` 3.0
6.0
*Main> (+) 4 7
11
*Main> let add4 = (+ 4)
*Main> add4 3
7
Note we can also use partial function application or currying with symbol functions, too, as in the case of "(+ 4)" above. In this case, this is referred to as a section. This is slightly more powerful than the basic currying as the order of the argument one wishes to curry can be easily chosen as demonstrated below:
The same type of effect can be achieved with normal currying using the function "flip" which reverses the order of arguments:
-- Div.hs
div2 :: (Fractional t) => t -> t
div2 = ( / 2)
-- or we could have said:
-- div2 x = x / 2
div2' :: (Fractional t) => t -> t
div2' = (2 / )
-- or we could have said:
-- div2' x = 2 / x
--------------------
*Main> :l ./Div.hs
*Main> div2 4
2.0
*Main> div2' 4
0.5
Note that the type class "Fractional" is used to define those numeric types which can be divided into "fractional" parts. For example, if we are forced to keep with "whole numbers", 5 divided by 2 is going to be 2 (with a remainder of 1 -- remember that stuff from grade school?). If we allow for fractional division, 5 divided by 2 is 2.5. By specifying "Fractional" we can use any and all types which perform this latter type of "floating point" division.
-- Div.hs
div' :: (Fractional t) => t -> t -> t
div' x y = x / y
----------
Prelude> :l ./Div.hs
*Main> div' 6 2
3.0
*Main> let div3 = div' 3
*Main> div3 1
3.0
*Main> let divBy4 = (flip div') 4
*Main> divBy4 8
2.0
Flip is a function defined in the standard Prelude (which is why we didn't have to import anything to use it). The type definition for flip is as follows:
Therefore, flip takes a function of "some type a to some type b to some type c" and turns that into a function of "some type b to some type a to some type c". That is, the types "a" and "b" are flipped. Note that flip provides the possibility for "a" and "b" to be of different types but they don't have to be.
flip :: (a -> b -> c) -> b -> a -> c
Lastly, both in Python and Ruby above, I introduced the lambda function. The lambda function takes it's name from lambda calculus which is a precursor to functional programming. The lambda is an anonymous or unnamed function (i.e., "that function which does ..."). In Haskell, the symbol "\" is used to represent lambda (mainly for it's similarity in shape to the Greek letter lambda or "λ"-- it's basically only missing the second shorter stroke). Note that Ruby 1.9 uses the symbol "->" to represent lambda (Ruby 1.8 uses "lambda" and a slightly different syntax).
At any rate, for completeness, let's see how Haskell could define curried functions using lambda:
This syntax using lambda ("\") is very similar to the Python and Ruby code we encountered above which would be written as follows:
-- DivAgain.hs
divByTen :: (Fractional a) => a -> a
divByTen = \ x -> x / 10
divTwoBy :: (Fractional a) => a -> a
divTwoBy = \ x -> 2 / x
------------
*Main> :l ./DivAgain.hs
*Main> divByTen 100
10.0
*Main> divTwoBy 2
1.0
and similarly for Ruby (again with the unfortunate need to use the call method to apply the function):
# div_again.py
divByTen = lambda x: x / 10
divTwoBy = lambda x: 2 / x
-------------
>>> from div_again import divByTen, divTwoBy
>>> divByTen(100)
10
>>> divTwoBy(2)
1
# div_again.rb
divByTen = ->(x){ x / 10 }
divTwoBy = ->(x){ 2 / x }
puts(divByTen.(100))
puts(divTwoBy.(2))
--------------
> ruby div_again.rb
10
1
WRAPPING IT UP
Pulling it all together, today's blog has taken a simple example and tortured it to death in the name of learning functional programming. I have used Python and Ruby to illustrate the same concepts as functional programming in Haskell.
The key concepts were the presentation of what a pure function is. At the end of the day, much of the rest of the discussion was an overview of what is basically just (a very nice) syntactic sugar.
Cheers!

> Note: I say "type declarations are usually optional"
ReplyDeleteAFAIK Haskell always infers the type of any expression by itself, the type signature you supply is only used for checking purposes or possibly to _restrict_ the domain of a function. Do you know any counterexample?
Any function that returns a polymorphic type not based on it's input is a counterexample.
ReplyDeletereadIO "3"
If there is no more context the type of the above expression must be explicitly stated as IO Int.
Really. Thanks.
ReplyDeleteNathan -- I don't think that's the case, although it may sometimes look that way, due to the "monomorphism restriction" (on by default and turned off with a compiler/source flag).
ReplyDeleteFor instance,
foo str = read (str ++ str)
Load up into GHCi and ask its type:
*Main> :type foo
foo :: (Read a) => [Char] -> a
Hi!
ReplyDeleteNice post ;)
Now, as someone who has to dive into Python/Django for web programming - Happstack is not for me (yet) do you recommend using stuff like:
add3 = functools.partial(add, 3) or
add3 = lambda x: add(x, 3)
in a real code or it was just interesting exercise?
So far this is just an "interesting exercise" for me. I've been a huge python fan up to now but I'm pretty impressed by the potential of Haskell. In this blog I'm trying to take things I'm learning in Python and "Haskelize" them and vice versa. I don't expect a one-to-one mapping but the thought is to look at "things we know" in a new light and "torture the data" until it confesses!
ReplyDeleteSo... in that light I don't have a recommendation for you, yet, gour. I'm hoping to do something a little more substantial than the "add function" stuff as I go forward so hopefully some clearer thoughts on "functional programming with Python" will emerge.
Thanks all for the great comments!