Composing function composition: How does (.).(.) work?

后端 未结 7 1637
温柔的废话
温柔的废话 2020-11-29 06:36

(.) takes two functions that take one value and return a value:

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

7条回答
  •  没有蜡笔的小新
    2020-11-29 07:06

    This is one of those neat cases where I think it's simpler to grasp the more general case first, and then think about the specific case. So let's think about functors. We know that functors provide a way to map functions over a structure --

    class Functor f where
      fmap :: (a -> b) -> f a -> f b
    

    But what if we have two layers of the functor? For example, a list of lists? In that case we can use two layers of fmap

    >>> let xs = [[1,2,3], [4,5,6]]
    >>> fmap (fmap (+10)) xs
    [[11,12,13],[14,15,16]]
    

    But the pattern f (g x) is exactly the same as (f . g) x so we could write

    >>> (fmap . fmap) (+10) xs
    [[11,12,13],[14,15,16]]
    

    What is the type of fmap . fmap?

    >>> :t fmap.fmap
      :: (Functor g, Functor f) => (a -> b) -> f (g a) -> f (g b)
    

    We see that it maps over two layers of functor, as we wanted. But now remember that (->) r is a functor (the type of functions from r, which you might prefer to read as (r ->)) and its functor instance is

    instance Functor ((->) r) where
      fmap f g = f . g
    

    For a function, fmap is just function composition! When we compose two fmaps we map over two levels of the function functor. We initially have something of type (->) s ((->) r a), which is equivalent to s -> r -> a, and we end up with something of type s -> r -> b, so the type of (.).(.) must be

    (.).(.) :: (a -> b) -> (s -> r -> a) -> (s -> r -> b)
    

    which takes its first function, and uses it to transform the output of the second (two-argument) function. So for example, the function ((.).(.)) show (+) is a function of two arguments, that first adds its arguments together and then transforms the result to a String using show:

    >>> ((.).(.)) show (+) 11 22
    "33"
    

    There is then a natural generalization to thinking about longer chains of fmap, for example

    fmap.fmap.fmap ::
      (Functor f, Functor g, Functor h) => (a -> b) -> f (g (h a)) -> f (g (h b))
    

    which maps over three layers of functor, which is equivalent to composing with a function of three arguments:

    (.).(.).(.) :: (a -> b) -> (r -> s -> t -> a) -> (r -> s -> t -> b)
    

    for example

    >>> import Data.Map
    >>> ((.).(.).(.)) show insert 1 True empty
    "fromList [(1,True)]"
    

    which inserts the value True into an empty map with key 1, and then converts the output to a string with show.


    These functions can be generally useful, so you sometimes see them defined as

    (.:) :: (a -> b) -> (r -> s -> a) -> (r -> s -> b)
    (.:) = (.).(.)
    

    so that you can write

    >>> let f = show .: (+)
    >>> f 10 20
    "30"
    

    Of course, a simpler, pointful definition of (.:) can be given

    (.:) :: (a -> b) -> (r -> s -> a) -> (r -> s -> b)
    (f .: g) x y = f (g x y)
    

    which may help to demystify (.).(.) somewhat.

提交回复
热议问题