Более краткий способ отображения функций на поля алгебраического типа данных?

У меня есть тип данных с большим количеством полей:

data ManyFields a b c d .. = MF { f1 :: a, f2 :: b, f3 :: c .. }

Проблема 1

Как сопоставить функцию на каждом поле, избегая при этом реализации функции map для каждого из них. Например, это выглядит очень утомительно и неидиоматично:

-- | Note I am explicitly constructing ManyField after mapping a function onto the field
-- | This looks bad
mapf1 :: (a -> a1) -> ManyFields a b c ..  -> ManyFields a1 b c ..
mapf1 g mf = MF (g . f1 $ mf) (f2 mf) ..

-- | Repeat for each field
mapf2 :: (b -> b1) -> ManyFields a b c .. -> ManyFields a b1 c ...

Я думаю, что какая-то функция более высокого порядка, абстрагирующая шаблон constructor . (mapfunction f), сократит шаблон, но есть ли лучшее решение?

Проблема вторая

Если я хочу заархивировать много ManyFields вместе и сопоставить функцию произвольной arity на каждом поле, похоже ли это, что это может быть экземпляр некоторого класса типа?

Случай использования:

(==) `mapFunction` mf1 `pairWiseZipField` mf2 

Это выглядит как аппликативный для меня, но опять же я не уверен, как реализовать fmap для этого типа.

Ответы

Ответ 1

Вы не можете сделать это со стандартной функцией. Наилучший подход - иметь функцию отображения для каждого поля. К счастью, вы можете сгенерировать их автоматически с помощью небольшого шаблона haskell из библиотеки lens. Он будет выглядеть примерно так:

data ManyFields a b c d = MF { _f1 :: a, _f2 :: b, _f3 :: c, _f4 :: d }

makeLenses ''ManyFields

Это создает объектив для каждого поля ManyFields. Объектив - это простая конструкция, которая позволяет вам как получить доступ, так и изменить значение там - изменения могут быть даже полиморфными, как и карта! Обратите внимание, что каждое поле имеет префикс с подчеркиванием: объектив имеет то же имя, что и поле, минус подчеркивание.

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

> foo = MF 'a' "b" 3 False
> foo^.f1
'a'

Вы можете установить значения с помощью оператора set. При использовании с объективом он создает функцию сеттера:

> :t set f1
set f1 :: a' -> ManyFields a b c d -> ManyFields a' b c d

Чтобы использовать его, вы можете сделать это:

> set f1 () foo
MF () "b" 3 False

Поскольку у вас есть геттер и сеттер, написание функции карты довольно тривиально. К счастью, нам даже не нужно этого делать: библиотека предоставляет функцию под названием over:

> :t over f1 
over f1 :: (a -> a') -> ManyFields a b c d -> ManyFields a' b c d

Если вам больше нужны операторы infix, set также можно назвать .~, а over можно назвать %~. (У последнего есть мнемоника: % - мода или "модификация": P.) Это также полезно для оператора &, который просто $ перевернут. Таким образом, следующие две версии равны:

> over f1 ord foo
MF 97 "b" 3 False
> foo & f1 %~ ord
MF 97 "b" 3 False

Я лично считаю, что операторов немного. Если вы не будете использовать объективы повсюду, я буду придерживаться set и over.

Я не знаю хорошего решения для функций zip, как вы описали. Но просмотрите всю библиотеку объективов - она ​​довольно большая, и вы никогда не знаете, что найдете.