capitalize
takes a String
and returns a String
. Never mind the implementation, it's the type signature we're interested in.a -> b
where a
and b
are variables for any type. So the signatures for capitalize
can be read as "a function from String
to String
". In other words, it takes a String
as its input and returns a String
as its output.strLength
is the same idea as before: we take a String
and return you a Number
.match
you can interpret as: It takes a Regex
and a String
and returns you [String]
. But an interesting thing is going on here that I'd like to take a moment to explain if I may.match
we are free to group the signature like so:Regex
and returns us a function from String
to [String]
. Because of currying, this is indeed the case: give it a Regex
and we get a function back waiting for its String
argument. Of course, we don't have to think of it this way, but it is good to understand why the last type is the one returned.onHoliday
is match
that already has a Regex
.replace
, the extra notation can get a little noisy and redundant so we simply omit them. We can give all the arguments at once if we choose so it's easier to just think of it as: replace
takes a Regex
, a String
, another String
and returns you a String
.id
function takes any old type a
and returns something of the same type a
. We're able to use variables in types just like in code. Variable names like a
and b
are convention, but they are arbitrary and can be replaced with whatever name you'd like. If they are the same variable, they have to be the same type. That's an important rule so let's reiterate: a -> b
can be any type a
to any type b
, but a -> a
means it has to be the same type. For example, id
may be String -> String
or Number -> Number
, but not String -> Bool
.map
similarly uses type variables, but this time we introduce b
which may or may not be the same type as a
. We can read it as: map
takes a function from any type a
to the same or different type b
, then takes an array of a
's and results in an array of b
's.a
to b
, an array of a
, and it delivers us an array of b
. The only sensible thing for it to do is call the bloody function on each a
. Anything else would be a bold face lie.reduce
is perhaps, the most expressive of all. It's a tricky one, however, so don't feel inadequate should you struggle with it. For the curious, I'll try to explain in English though working through the signature on your own is much more instructive.b
and a
, and produces a b
. Where might it get these a
s and b
s? Well, the following arguments in the signature are a b
and an array of a
s so we can only assume that the b
and each of those a
s will be fed in. We also see that the result of the function is a b
so the thinking here is our final incantation of the passed in function will be our output value. Knowing what reduce does, we can state that the above investigation is accurate.head
, we see that it takes [a]
to a
. Besides the concrete type array
, it has no other information available and, therefore, its functionality is limited to working on the array alone. What could it possibly do with the variable a
if it knows nothing about it? In other words, a
says it cannot be a specific type, which means it can be any type, which leaves us with a function that must work uniformly for every conceivable type. This is what parametricity is all about. Guessing at the implementation, the only reasonable assumptions are that it takes the first, last, or a random element from that array. The name head
should tip us off.reverse
possibly be up to? Again, it cannot do anything specific to a
. It cannot change a
to a different type or we'd introduce a b
. Can it sort? Well, no, it wouldn't have enough information to sort every possible type. Can it re-arrange? Yes, I suppose it can do that, but it has to do so in exactly the same predictable way. Another possibility is that it may decide to remove or duplicate an element. In any case, the point is, the possible behaviour is massively narrowed by its polymorphic type.head
of our array, then run some function f
on it, that is equivalent to, and incidentally, much faster than, if we first map(f)
over every element then take the head
of the result.filter
theorem is similar. It says that if we compose f
and p
to check which should be filtered, then actually apply the f
via map
(remember filter
will not transform the elements - its signature enforces that a
will not be touched), it will always be equivalent to mapping our f
then filtering the result with the p
predicate.compose
function itself. The fruit is low hanging and the possibilities are endless.a
must be an Ord
. Or in other words, a
must implement the Ord
interface. What is Ord
and where did it come from? In a typed language it would be a defined interface that says we can order the values. This not only tells us more about the a
and what our sort
function is up to, but also restricts the domain. We call these interface declarations type constraints.Eq
and Show
. Those will ensure that we can check equality of our a
s and print the difference if they are not equal.