Understanding Haskell Type Signatures

前端 未结 3 1808
轮回少年
轮回少年 2020-12-14 07:18

I am in the process of teaching myself Haskell and I was wondering about the following type signatures:

Prelude> :t ($)
($) :: (a -> b) -> a -> b         


        
相关标签:
3条回答
  • 2020-12-14 07:48

    Well, as said already, $ can be easily understood if you just forget about currying and see it like, say, in C++

    template<typename A, typename B>
    B dollar(std::function<B(A)> f, A x) {
      return f(x);
    }
    

    But actually, there is more to this than just applying a function to a value! The apparent similarity between the signatures of $ and map has in fact a pretty deep category-theory meaning: both are examples of the morphism-action of a functor!

    In the category Hask that we work with all the time, objects are types. (That is a bit confusionsome, but don't worry). The morphisms are functions.

    The most well-known (endo-)functors are those which have an instance of the eponymous type class. But actually, mathematically, a functor is only something that maps both objects to objects and morphisms to morphisms1. map (pun intended, I suppose!) is an example: it takes an object (i.e. type) A and maps it to a type [A]. And, for any two types A and B, it takes a morphism (i.e. function) A -> B, and maps it to the corresponding list-function of type [A] -> [B].

    This is just a special case of the functor class signature operation:

    fmap :: Functor f   =>   (a->b) -> (f a->f b)
    

    Mathematics doesn't require this fmap to have a name though. And so there can be also the identity functor, which simply assigns any type to itself. And, every morphism to itself:

    ($) :: (a->b) -> (a->b)
    

    "Identity" exists obviously more generally, you can also map values of any type to themselves.

    id :: a -> a
    id x = x
    

    And sure enough, a possible implementation is then

    ($) = id
    

    1Mind, not anything that maps objects and morphisms is a functor... it does need to satisfy the functor laws.

    0 讨论(0)
  • 2020-12-14 07:58

    I'll start with map. The map function applies an operation to every element in a list. If I had

    add3 :: Int -> Int
    add3 x = x + 3
    

    Then I could apply this to a whole list of Ints using map:

    > map add3 [1, 2, 3, 4]
    [4, 5, 6, 7]
    

    So if you look at the type signature

    map :: (a -> b) -> [a] -> [b]
    

    You'll see that the first argument is (a -> b), which is just a function that takes an a and returns a b. The second argument is [a], which is a list of values of type a, and the return type [b], a list of values of type b. So in plain english, the map function applies a function to each element in a list of values, then returns the those values as a list.

    This is what makes map a higher order function, it takes a function as an argument and does stuff with it. Another way to look at map is to add some parentheses to the type signature to make it

    map :: (a -> b) -> ([a] -> [b])
    

    So you can also think of it as a function that transforms a function from a to b into a function from [a] to [b].


    The function ($) has the type

    ($) :: (a -> b) -> a -> b
    

    And is used like

    > add3 $ 1 + 1
    5
    

    All it does is take what's to the right, in this case 1 + 1, and passes it to the function on the left, here add3. Why is this important? It has a handy fixity, or operator precedence, that makes it equivalent to

    > add3 (1 + 1)
    

    So whatever to the right gets essentially wrapped in parentheses before being passed to the left. This just makes it useful for chaining several functions together:

    > add3 $ add3 $ add3 $ add3 $ 1 + 1
    

    is nicer than

    > add3 (add3 (add3 (add3 (1 + 1))))
    

    because you don't have to close parentheses.

    0 讨论(0)
  • 2020-12-14 08:02

    ($) is just function application. It gets a function of type a->b, an argument of type a, applies the function and returns a value of type b.

    map is a wonderful example for how reading a function type signature helps understanding it. map's first argument is a function that takes a and returns b, and its second argument is a list of type [a]. So map applies a function of type a->b to a list of a values. And the result type is indeed of type [b] - a list of b values!

    (a->b)->[a]->[b] can be interpreted as "Accepts a function and a list and returns another list", and also as "Accepts a function of type a->b and returns another function of type [a]->[b]". When you look at it this way, map "upgrade" f (the term "lift" is often used in this context) to work on lists: if double is a function that doubles an integer, then map double is a function that double every integer in a list.

    0 讨论(0)
提交回复
热议问题