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

import Control.Applicative

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

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

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


person Peter Hall    schedule 18.11.2012    source источник


Ответы (3)


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

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

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

person Landei    schedule 18.11.2012

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

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

Класс Functor выглядит так:

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

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

person dfeuer    schedule 05.01.2016
comment
Почему -- fmap :: (a -> b) -> (y,x,a) -> (y,x,b) не работает для трех кортежей? - person Dmitri Zaitsev; 26.12.2016
comment
@DmitriZaitsev Было бы, если бы вы определили экземпляр Functor для ((,,) x y). (x,y) и (x,y,z) являются очень разными (семействами) типами, так как не существует единого типа кортежа (семейства). - person chepner; 01.06.2018
comment
Поэтому на самом деле невозможно написать экземпляр Functor, делающий что-то еще. А как насчет fmap :: (a -> b) -> (a,x) -> (b,x)? - person Mateen Ulhaq; 15.10.2019
comment
@MateenUlhaq, это просто не тот тип, который должен иметь fmap в соответствии с определением класса Functor. - person dfeuer; 15.10.2019
comment
Ах я вижу. Теоретически можно ли определить instance Functor ((*, x))? (Не уверен, что правильное обозначение для непримененного типа, поэтому я использовал *.) - person Mateen Ulhaq; 15.10.2019
comment
@MateenUlhaq, немного сложно догадаться, что вы имеете в виду, но иногда бывает полезен один тип — newtype Flip f a b = Flip { unFlip :: f b a }. - person dfeuer; 15.10.2019

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

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

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

 type T2 a = (a,a)

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

Приведенный выше ограниченный синоним из двух кортежей логически совпадает с типом:

data T2 a = T2 a a

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

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

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

person Peter Hall    schedule 18.11.2012
comment
На самом деле, это полезно как экземпляр класса функтора. Например, я могу определить дерево как type Tree a = Free T2 a. На самом деле, большинство применений этого типа (в качестве функтора) включают ветвление или параллелизм того или иного рода. - person Gabriel Gonzalez; 18.11.2012
comment
Стоит отметить, что если вы хотите указать, какую часть кортежа отображать, вы можете использовать Control.Lens. Setter подобен экземпляру Functor, который вы указываете явно, поэтому over _1 (+1) (5,3) ==> (6,3); over _2 (*2) ("funny,2) ==› ("funny",4); over both length ("hi","there") ==› (2,5); over (both._1) (*10) ((1,2),(3,4)) ==› ((10,2),(30,4)). - person shachaf; 19.11.2012
comment
Вы также можете использовать стрелки, если хотите запустить его на первом или втором элементе: first и second. - person CMCDragonkai; 04.05.2015
comment
@CMCDragonkai, в данном случае я бы предпочел более простые Bifunctor версии этих функций; его легче понять и обобщить в том, что, как мне кажется, является, вероятно, более распространенным направлением. - person dfeuer; 05.01.2016