Подтвердить что ты не робот

Почему экземпляр Functor 2-кортежа применяет эту функцию только к второму элементу?

import Control.Applicative

main = print $ fmap (*2) (1,2)

производит (1,4). Я ожидаю, что он будет производить (2,4), но вместо этого функция применяется только ко второму элементу кортежа.

Обновление Я в основном понял это почти сразу. Я отправлю свой ответ через минуту.

4b9b3361

Ответ 1

Экземпляр Functor фактически состоит из модуля GHC.Base, который импортируется Control.Applicative.

Попытка написать экземпляр, который я хочу, я вижу, что это не сработает, учитывая определение кортежей; экземпляр требует только одного параметра типа, в то время как 2-кортеж имеет два.

Действительный экземпляр Functor, по крайней мере, должен быть на кортежах, (a,a), которые имеют один и тот же тип для каждого элемента, но вы не можете сделать ничего скрытого, например, определить экземпляр на:

 type T2 a = (a,a)

поскольку типы экземпляров не могут быть синонимами.

Вышеупомянутый ограниченный 2-кортежный синоним логически совпадает с типом:

data T2 a = T2 a a

который может иметь экземпляр Functor:

instance Functor T2 where
    fmap f (T2 x y) = T2 (f x) (f y)

Как заметил Габриэль в комментариях, это может быть полезно для ветвящихся структур или concurrency.

Ответ 2

Позвольте мне ответить на этот вопрос вопросом: какой результат вы ожидаете:

main = print $ fmap (*2) ("funny",2)

У вас может быть что-то, что вам нужно (используя data Pair a = Pair a a или так), но поскольку (,) может иметь разные типы в своем первом и втором аргументах, вам не повезло.

Ответ 3

Пары, по существу, определяются следующим образом:

data (,) a b = (,) a b

Класс Functor выглядит следующим образом:

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

Поскольку типы аргументов функции и результаты должны иметь вид * (т.е. они представляют собой значения, а не функции типа, которые могут применяться более или менее экзотическими), мы должны иметь a :: *, b :: * и, самое главное для наших целей, f :: * -> *. Так как (,) имеет вид * -> * -> *, он должен применяться к типу вида * для получения типа, подходящего для a Functor. Таким образом,

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

Таким образом, на самом деле нет способа написать экземпляр Functor, который делает что-то еще.


Один полезный класс, который предлагает больше способов работы с парами, - Bifunctor, от Data.Bifunctor.

class Bifunctor f where
  bimap :: (a -> b) -> (c -> d) -> f a c -> f b d
  bimap f g = first f . second g

  first :: (a -> b) -> f a y -> f b y
  first f = bimap f id

  second :: (c -> d) -> f x c -> f x d
  second g = bimap id g

Это позволяет вам писать такие вещи, как следующее (из Data.Bifunctor.Join):

  newtype Join p a =
    Join { runJoin :: p a a }

  instance Bifunctor p => Functor (Join p) where
    fmap f = Join . bimap f f . runJoin

Join (,) тогда по существу совпадает с Pair, где

data Pair a = Pair a a

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