Почему экземпляр Functor 2-кортежа применяет эту функцию только к второму элементу?
import Control.Applicative
main = print $ fmap (*2) (1,2)
производит (1,4)
. Я ожидаю, что он будет производить (2,4)
, но вместо этого функция применяется только ко второму элементу кортежа.
Обновление Я в основном понял это почти сразу. Я отправлю свой ответ через минуту.
Ответы
Ответ 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
для работы с парами напрямую.