Home > OS >  How do you determine the way to write functions so that they are compatible for composing?
How do you determine the way to write functions so that they are compatible for composing?

Time:11-14

Let's say we have these 2 functions

f1 :: Int -> [Int] -> Int -> [Int]
f1 _ [] _ = []
f1 x (h:t) y = (x * h   y):(f1 x t y)

f2 :: [Int] -> Int
f2 (h:t) = h

Why does (f2 . f1 1 [1..10]) 1 work, but (f2 . f1 1) [1..10] 1 doesn't work?

CodePudding user response:

All functions in Haskell take exactly one argument and return one value. The argument and/or the return type could be another function.

f1 has an argument type of Int and a return type of [Int] -> Int -> [Int]. The right associativity of (->) means we don't have to explicitly write this as

f1 :: Int -> ([Int] -> (Int -> [Int]))

but can instead drop the parentheses.


(Yes, (->) is an operator. You can use :k in GHCi to see the kind, but unfortunately what you get back is more complicated than we want to explain here:

> :k (->)
(->) :: TYPE q -> TYPE r -> *

Don't worry about what TYPE q and TYPE r stand for. Suffice it to say that (->) takes two types and returns a new type, and we can assume a simpler kind like

(->) :: * -> * -> *

The kind * is the kind of an ordinary type, more frequently written as Type these days. )


In order to compose two functions, the return type of one must match the argument type of the other. We can see this from the type of (.) itself:

(.) :: (b -> c) -> (a -> b) -> a -> c
        ^                ^

That's not the case with f1 and f2:

--            vvvvvvvvvvvvvvvvvvvvv
f1 :: Int -> ([Int] -> Int -> [Int])
f2 ::        [Int]                    -> Int
--           ^^^^^
-- [Int] -> Int -> [Int] and [Int] are different types

nor with f1 1 and f2

--                vvvvvvvvvvvv
f1 1 :: [Int] -> (Int -> [Int])
f2   ::          [Int]           -> Int
--               ^^^^^
-- Int -> [Int] and [Int] are different types

but it is true of f1 1 [1..10] and f2:

--                     vvvvv
f1 1 [1..10] :: Int -> [Int]
f2           ::        [Int] -> Int
--                     ^^^^^
-- [Int] and [Int] are the same type

While (->) is right-associative, function application is left-associative, which is why we can write

((f1 1) [1..10]) 1

as f1 1 [1..10] 1 instead, leading to the appearance of f1 as taking 3 arguments, rather than an expression involving 3 separate function calls. You can see the three calls more clearly if you use a let expression to name each intermediate function explicitly:

let t1 = f1 1
    t2 = t1 [1..10]
    t3 = t2 1
in t3
  • Related