Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell fmap composition misunderstanding

Tags:

haskell

If I compose two fmaps

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

I get a function which applies a function to a value inside 2 nested leves of structure, f1 and f.

And I can use it—this works as I expected:

Prelude> (fmap.fmap) (+1) [[1,2]]
[[2,3]]

With inferred type as I expected (2 leves of structure around result)

Prelude> :t  (fmap.fmap) (+1) [[1,2]]
(fmap.fmap) (+1) [[1,2]] :: Num b => [[b]]

The following does not work. I also expect this (because we can't apply sum to a single number):

Prelude>  (fmap.fmap) sum [[1,2]]

<interactive>:39:2: error:
    • Could not deduce (Num (t0 b))
      from the context: (Num (t b), Num b, Foldable t)
        bound by the inferred type for ‘it’:
                   (Num (t b), Num b, Foldable t) => [[b]]
        at <interactive>:39:2-24
      The type variable ‘t0’ is ambiguous
    • In the ambiguity check for the inferred type for ‘it’
      To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
      When checking the inferred type
        it :: forall (t :: * -> *) b.
              (Num (t b), Num b, Foldable t) =>
              [[b]]
Prelude> :t  (fmap.fmap) sum [[1,2]]
(fmap.fmap) sum [[1,2]] :: (Num (t b), Num b, Foldable t) => [[b]]

BUT! If I change one level of structure to a Maybe type:

Prelude> (fmap.fmap) sum Just [1,2]
Just 3

then it begins to work, but in my opinion breaking the type signature (fmap.fmap) :: (Functor f, Functor f1) => (a -> b) -> f1 (f a) -> f1 (f b) (because it applies the sum function inside the first level of structure, not the second as I expected).

I think problem in my undersanding how function application order evaluates here, because I find that with parentheses this works as expected inside two levels of structure with foldable list values (vs numbers in first exapmles):

Prelude> (fmap.fmap) sum (Just [[1,2],[2,3]])
Just [3,5]

But what happens here:

Prelude> (fmap.fmap) sum Just [1,2]
Just 3
  1. Why is the first level of stucture skipped?

  2. What is the order of function applications here?

  3. How does Haskell infer the final type?

     Prelude> :t (fmap.fmap) sum Just [1,2]
     (fmap.fmap) sum Just [1,2] :: Num t => Maybe t
    

Why Maybe t and not Maybe List t as I understand (fmap.fmap) must determine f1 (f b) two levels of structure not one?

like image 490
Evg Avatar asked Mar 12 '26 16:03

Evg


1 Answers

Let's compute, pretending that numeric literals are Ints for the sake of simplicity.

(fmap.fmap) sum Just [1,2]
= fmap (fmap sum) Just [1,2]
        |         |    \ -- an additional argument applied to the result of fmap
        |         \ -- the value with a type of the form f a with f Functor
        \ -- the function to fmap

Here, Just is a function [Int] -> Maybe [Int], so the first fmap operates on the functor f = (->) [Int], we have fmap = (.) because that's how it's defined in Functor ((->) [Int]).

= (.) (fmap sum) Just [1,2]
= (fmap sum) (Just [1,2])

Now, fmap f (Just x) = Just (f x) since that's how Functor Maybe is defined.

= Just (sum [1,2])
= Just 3
  1. why first level of structure skipped?

It isn't. The first level is (->) [Int].

  1. what is the order of function applications here?

The usual one. fmap.fmap is applied to sum. The result is applied to Just. The final result is applied to [1,2].

  1. how does Haskell infer the final type?

It sees that Just is a "value wrapped inside the (->) [Int] functor", and uses that to instantiate the first fmap. The second fmap is instead used on the Maybe level since Just returns that.

like image 146
chi Avatar answered Mar 14 '26 06:03

chi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!