Лифтинг - обобщение
Мне нужно использовать тяжелую функцию подъема, например.
k = myFunc
<$> someFunctionName 1
<*> someFunctionName 2
<*> someFunctionName 3
<*> someFunctionName 4
<*> someFunctionName 5
<*> someFunctionName 6
<*> someFunctionName 8
<*> someFunctionName 9
-- ...
Который не предусмотрен для больших функций (около 20 аргументов) в Prelude. Есть ли умный способ сделать такой подъем без явной цепочки тех ap
s? Я ищу что-то вроде
k = magic (map someFunctionName [1,2,3,4,5,6,8,9]) myFunc
Мне может быть трудно угадать тип magic
так как это зависит от количества аргументов поднятой функции. Конечно, невозможно использовать map
в списке здесь (или это?), я ставлю это только в качестве точки зрения.
Я думаю, что ищу что-то, что могло бы быть хорошо решено зависимыми типами, которые не включены в Haskell, но, возможно, есть какой-то хитрый способ обойти это (TemplateHaskell?)
У вас есть идеи, как сделать его более элегантным и гибким?
редактировать: в моем случае типы связанных функций все одинаковы.
1 ответ
Лифт конструкторы
Используя классы типов, мы можем определить обобщенную версию liftA
/ap
, Сложная задача - определить, когда прекратить подъем и вернуть результат. Здесь мы используем тот факт, что конструкторы являются карри-функциями с таким количеством аргументов, сколько у них есть полей, и тип результата не является функцией.
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
import Text.Read
-- apF
-- :: Applicative f
-- => (i -> f a)
-- -> (a -> a -> ... -> x) -- constructor type
-- -> (i -> i -> ... -> f x) -- lifted function
class Applicative f => ApF f i a s t where
apF :: (i -> f a) -> f s -> t
-- Recursive case
-- s ~ (a -> ...)
-- t ~ (i -> ...)
instance (a ~ a', t ~ (i -> t'), ApF f i a s' t') => ApF f i a (a' -> s') t where
apF parseArg fconstr i = apF parseArg (fconstr <*> parseArg i)
-- Base case
-- s ~ x -- x assumed not to be a function type (not (y -> z) for any y and z)
-- t ~ f x
instance {-# OVERLAPPABLE #-} (t ~ f x, Applicative f) => ApF f i a x t where
apF _ fconstr = fconstr
liftF :: ApF f i a s t => (i -> f a) -> s -> t
liftF parseArg constr = apF parseArg (pure constr)
main = do
let lookup :: Int -> Maybe Integer
lookup i =
case drop i [2,3,5,7,11,13] of
[] -> Nothing
a : _ -> Just a
print $ liftF lookup (,,) 0 2 5
Более родственные записи и дженерики
- Сообщение в блоге о представлении записей как данных с более высоким родом
- общая документация
- Суть этого ответа
Другое решение состоит в том, чтобы сначала параметризовать записи с помощью функции типа, обертывающей каждое поле, чтобы мы могли помещать вещи различных других связанных типов. Это позволяет нам создавать и потреблять фактические записи, обходя эти производные структуры с использованием Haskell Generics.
data UserF f = User
{ name :: f @@ String
, age :: f @@ Int
} deriving G.Generic
type User = UserF Id
Функции типа определяются с использованием семейства типов (@@)
(HKD
в сообщении в блоге, указанном выше). Для этого ответа важны тождество и постоянные функции.
type family s @@ x
type instance Id @@ x = x
type instance Cn a @@ x = a
data Id
data Cn (a :: *)
Например, мы можем собрать индексы, используемые для анализа CSV, в UserF (Cn Int)
:
userIxes = User { name = 0, age = 2 } :: UserF (Cn Int)
Учитывая такой параметризованный тип записи (p = UserF
) и запись показателей (ixes :: p (Cn Int)
) мы можем разобрать запись CSV (r :: [String]
) с parseRec
ниже. Здесь используется generics-sop.
parseRec :: _
=> p (Cn Int) -> [String] -> Maybe (p Id)
parseRec ixes r =
fmap to .
hsequence .
htrans (Proxy :: Proxy ParseFrom) (\(I i) -> read (r !! i)) .
from $
ixes
Давайте разберем код снизу вверх. generics-sop предоставляет комбинаторы для преобразования записей единообразным способом, аналогичным использованию списков. Лучше всего следовать надлежащему руководству, чтобы понять основные детали, но для демонстрации мы представим, что середина конвейера между from
а также to
на самом деле трансформирует списки, используя динамический тип Field
печатать разнородные списки.
from
превращает запись в ее неоднородный список полей, но так как они всеInt
список пока действительно однороденfrom :: p (Cn Int) -> [Int]
,Здесь с помощью
(!!)
а такжеread
мы получаем и анализируем каждое поле по заданному индексуi
,htrans Proxy
в основномmap
:(Int -> Maybe Field) -> [Int] -> [Maybe Field]
,hsequence
в основномsequence :: [Maybe Field] -> Maybe [Field]
,to
превращает список полей в запись с совместимыми типами полей,[Field] -> p Id
,
Последний шаг без усилий:
parseUser :: Record -> Maybe User
parseUser = parseRec $ User { name = 0, age = 2 }