Способы программирования общего типа данных могут быть использованы для преобразования всех полей записи в какой-либо «однородный» способ.
Возможно, все поля в записи реализуют некоторый класс, который мы хотим использовать (типичный пример: Show
). Или, может быть, у нас есть еще одна запись «аналогичной» формы, которая содержит функции, и мы хотим применить каждую функцию к соответствующему полю исходной записи.
Для этих видов использования библиотека generics-sop является хорошим вариантом. Он расширяет функциональные возможности Generics по умолчанию GGC с дополнительным оборудованием уровня, которое обеспечивает аналоги функций, таких как sequence
или ap
, но которые работают во всех полях записи.
Используя generics-sop, я попытался создать немного менее подробную версию вашего merge
funtion. Некоторый предварительный импорт:
{-# language TypeOperators #-}
{-# language DeriveGeneriC#-}
{-# language TypeFamilies #-}
{-# language DataKinds #-}
import Control.Applicative (liftA2)
import qualified GHC.Generics as GHC
import Generics.SOP
helper функция, которая поднимает бинарную операцию в виде полезные функциями дженерик-подачка:
fn_2' :: (a -> a -> a) -> (I -.-> (I -.-> I)) a -- I is simply an Identity functor
fn_2' = fn_2 . liftA2
Общая функция слияния, которая принимает вектор операторов и работ на любой одной конструктор записи, производный Generic
:
merge :: (Generic a, Code a ~ '[ xs ]) => NP (I -.-> (I -.-> I)) xs -> a -> a -> a
merge funcs reg1 reg2 =
case (from reg1, from reg2) of
(SOP (Z np1), SOP (Z np2)) ->
let npResult = funcs `hap` np1 `hap` np2
in to (SOP (Z npResult))
Code
тип семьи, который возвращает тип уровня Li st списков, описывающих структуру типа данных. Внешний список для конструкторов, внутренние списки содержат типы полей для каждого конструктора.
Часть ограничения указывает, что «тип данных может иметь только один конструктор», требуя, чтобы внешний список имел ровно один элемент.
Образец шаблона (SOP (Z _)
извлекает вектор (разнородный) значений полей из общего представления записи. SOP
означает «сумма продуктов».
Конкретный пример:
data Person = Person
{
name :: String
, age :: Int
} deriving (Show,GHC.Generic)
instance Generic Person -- this Generic is from generics-sop
mergePerson :: Person -> Person -> Person
mergePerson = merge (fn_2' (++) :* fn_2' (+) :* Nil)
В Nil
и :*
конструкторы используются для построения вектора операторов (тип называется NP
, из п-арной продукта). Если вектор не совпадает с количеством полей в записи, программа не будет компилироваться.
Обновление. Учитывая, что типы в вашей записи являются очень равномерными, альтернативным способом создания вектора операций является определением экземпляров вспомогательного класса типов для каждого типа поля, а затем использовать hcpure
функции:
class Mergeable a where
mergeFunc :: a -> a -> a
instance Mergeable String where
mergeFunc = (++)
instance Mergeable Int where
mergeFunc = (+)
mergePerson :: Person -> Person -> Person
mergePerson = merge (hcpure (Proxy :: Proxy Mergeable) (fn_2' mergeFunc))
hcliftA2
функция (которая объединяет hcpure
,и hap
) может быть использована для упрощения дальнейших действий.
Нужно ли записывать поля записей (может ли 'Foo' быть чем-то другим, кроме записи)? – Alec
Этот запах проблемы XY. Возможно, решение состоит в том, чтобы вообще не иметь такого «Foo», но чтобы сделать это суждение, мы должны предоставить информацию о проблеме, которую «Foo» должен решить. – Bakuriu
Похоже, что это вариант использования для программирования типа данных, возможно, с использованием библиотеки генериков, например, generics-sop. – danidiaz