Почему <$> действует только на второго члена пары? - PullRequest
8 голосов
/ 22 сентября 2010

Быстро взгляните на следующую интерактивную сессию в GHCi:

Prelude> import Control.Applicative
Prelude Control.Applicative> (+1) <$> [1,2]
[2,3]
Prelude Control.Applicative> (+1) <$> (1,2)
(1,3)

Я думаю, есть веская причина для поведения <$> в отношении пар, но я не смог найти одиндо сих пор, так:

Почему <$> (или fmap) определено так, чтобы действовать только на второй член пары, а не на оба значения?

Ответы [ 3 ]

15 голосов
/ 22 сентября 2010

<$> (он же fmap) является членом класса Functor следующим образом:

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

Так, что бы f ни было параметризованным типом с одним аргументом типа. Списки являются одним из таких типов, когда они записаны в префиксной форме [] ([] a совпадает с [a]). Таким образом, экземпляр для списков:

instance Functor [] where
  -- fmap :: (a -> b) -> [] a -> [] b 
  fmap = map

Пары также можно записать в виде префикса: (,) a b совпадает с (a, b). Итак, давайте рассмотрим, что мы делаем, если нам нужен экземпляр Functor, включающий пары. Мы не можем объявить instance Functor (,), потому что конструктор пары (,) принимает два типа - и они могут быть разных типов! Что мы можем сделать, так это объявить экземпляр для (,) a - это тип, которому нужен только еще один тип:

instance Functor ( (,) a ) where
  -- fmap :: (b -> c) -> (,) a b -> (,) a c
  fmap f (x, y) = (x, f y)

Надеюсь, вы видите, что определение fmap - единственное разумное определение, которое мы можем дать. Ответ относительно того, почему экземпляр functor работает со вторым элементом в паре, заключается в том, что тип для второго элемента является последним в списке! Мы не можем легко объявить экземпляр функтора, который работает с первым элементом в паре. Между прочим, это обобщается на более крупные кортежи, например четверка (,,,) a b c d (он же (a, b, c, d)) также может иметь экземпляр Functor для последнего элемента:

instance Functor ( (,,,) a b c) where
  -- fmap :: (d -> e) -> (,,,) a b c d -> (,,,) a b c e
  fmap f (p, q, r, s) = (p, q, r, f s)

Надеюсь, это поможет объяснить все это!

3 голосов
/ 22 сентября 2010

Рассмотрим определение класса типов Functor:

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

Очевидно, что f имеет вид * -> *.Таким образом, вы можете объявить экземпляры только для типа данных, который имеет вид * -> *.То, что вы можете сделать, - это делать что-то вроде этого:

instance Functor (,) where
  fmap :: (a -> b) -> (,) a -> (,) b

Это будет работать на партиальных аппликациях и действительно неудобно.Итак, кто-то определил экземпляр следующим образом:

instance Functor ((,) a) where
  fmap :: (b -> c) -> (,) a b -> (,) a c
  fmap f (x,y) = (x,f y)

В двух словах: в простом Haskell 98 (хотя я считаю, что для этого есть расширение синтаксиса) определить экземпляр как

То, что вы можете сделать, это определить свой собственный кортеж:

data T a = T a a

instance Functor T where
  fmap f (T a b) = T (f a) (f b)

Тогда вы можете делать все, что захотите.Видите ли, потому что вид * -> * вместо * -> * -> *, все в порядке.

2 голосов
/ 22 сентября 2010

Полагаю, кортеж не должен быть однородным, я имею в виду, что оба типа могут быть разными. Если вам нужен однородный кортеж, вы можете использовать список, и тогда будет работать fmap.

Как вы ожидаете, что (+1) ("Hello", 2) будет работать?

Prelude> import Control.Applicative
Prelude Control.Applicative> (+1) <$> ("hello",2)
("hello",3)

Это просто работа, но нет особого поведения, когда оба типа одинаковы. Кстати, я не знаю, почему второе значение не используется, а не первое, но в любом случае вы можете использовать только одно значение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...