#haskell #functor #applicative
#haskell #функтор #аппликативный
Вопрос:
Есть ли что-то похожее на Applicative
класс type, но где для каждой стороны приложения есть два разных функтора?
т. е. (<*>) :: (Functor f, Functor g) => f (a -> b) -> g a -> f b
Комментарии:
1. Существуют ли конкретные конкретные типы, с которыми вы хотите это использовать? Эта функция не может быть получена только для любых двух функторов.
2. Или любые два аппликатива.
3. @4castle: Извините, я недостаточно хорошо все продумал. Я ищу обобщенный zip, который может автоматически обрабатывать и преобразовывать различные типы последовательностей.
4. Я думаю, вам нужно будет самостоятельно обеспечить естественное преобразование из одного функтора в другой; даже если предположить, что вы можете извлечь функцию из аппликатива
f
и применить ее кg a
, чтобы получить ag b
, вам все равно нужно что-то с типомg b -> f b
, чтобы получить конечный результат.5. (Вероятно, два естественных преобразования, другое для получения
g (a -> b)
изf (a -> b)
в первую очередь.)
Ответ №1:
(Следуя предложению от @dfeuer в комментариях.)
Существует конструкция, называемая сверткой дня, которая позволяет вам сохранять различие между двумя функторами при выполнении аппликативных операций и откладывать момент преобразования одного в другой.
Day
Тип — это просто пара функториальных значений вместе с функцией, которая объединяет их соответствующие результаты:
data Day f g a = forall b c. Day (f b) (g c) (b -> c -> a)
Обратите внимание, что фактические возвращаемые значения функторов являются экзистенциализированными; возвращаемое значение композиции — это значение функции.
Day
имеет преимущества перед другими способами объединения аппликативных функторов. В отличие Sum
от этого, композиция по-прежнему аппликативна. В отличие Compose
от этого , композиция «беспристрастна» и не навязывает порядок вложенности. В отличие Product
от этого, это позволяет нам легко комбинировать прикладные действия с различными типами возвращаемых данных, нам просто нужно предоставить подходящую функцию адаптера.
Например, вот два Day ZipList (Vec Nat2) Char
значения:
{-# LANGUAGE DataKinds #-}
import Data.Functor.Day -- from "kan-extensions"
import Data.Type.Nat -- from "fin"
import Data.Vec.Lazy -- from "vec"
import Control.Applicative
day1 :: Day ZipList (Vec Nat2) Char
day1 = Day (pure ()) ('b' ::: 'a' ::: VNil) (flip const)
day2 :: Day ZipList (Vec Nat2) Char
day2 = Day (ZipList "foo") (pure ()) const
( Nat2
из пакета fin, он используется для параметризации фиксированного размера Vec
из vec.)
Мы можем объединить их вместе просто отлично:
res :: Day ZipList (Vec Nat2) (Char,Char) res = (,) <
gt; day1 <*> day2
А затем преобразуйтеVec
в aZipList
и свернитеDay
:res' :: ZipList (Char,Char) res' = dap $ trans2 (ZipList . toList) res ghci> res' ZipList {getZipList = [('b','f'),('a','o')]}
Использование функций
dap
и.trans2
Возможный сбой производительности: когда мы поднимаем один из функторов до
Day
, другому присваивается фиктивноеpure ()
значение. Но это мертвый груз при объединенииDay
s с(<*>)
. Можно работать умнее, обернув функторы вLift
for transformers, чтобы получить более быстрые операции для простых «чистых» случаев.
Комментарии:
1. Практический пример
Day
: моя библиотека «потоковой передачи процессов» используетDay
внутренне для объединения обработчиков для stdin, stdout и stderr внешнего процесса в единыйApplicative
. hackage.haskell.org/package/process-streaming-0.9.3.0/docs /… Он также использует этотLift
трюк.
Ответ №2:
Одним из общих понятий «типа последовательности» является свободный моноид. Поскольку вы смотрите на полиморфные типы последовательностей, мы можем продолжить Traversable
.
class Semigroup1 t where
(<=>) :: t a -> t a -> t a
class Semigroup1 t => Monoid1 t where
mempty1 :: t a
Смотрите Примечание ниже.
class (Traversable t, Monoid1 t) => Sequence t where
singleton :: a -> t a
Как это тип последовательности? Очень неэффективно. Но мы могли бы добавить кучу методов с реализациями по умолчанию, чтобы сделать его эффективным. Вот некоторые основные функции:
cons :: Sequence t => a -> t a -> t a
cons x xs = singleton x <=> xs
fromList
:: (Foldable f, Sequence t)
=> f a -> t a
fromList = foldr cons mempty1
uncons :: Sequence t => t a -> Maybe (a, t a)
uncons xs = case toList xs of
y:ys -> Just (y, fromList ys)
[] -> Nothing
С помощью этих инструментов вы можете сжать любые две последовательности, чтобы создать третью.
zipApp :: (Foldable t, Foldable u, Sequence v) = t (a -> b) -> u a -> v b
zipApp fs xs = fromList $ zipWith ($) (toList fs) (toList xs)
Обратите внимание на последние версии GHC
Для расширяющегося GHC вы можете использовать QuantifiedConstraints
и RankNTypes
и ConstraintKinds
и определить
type Semigroup1 t = forall a. Semigroup (t a)
type Monoid1 t = forall a. Monoid (t a)
Выполнение этого таким образом позволит вам написать, например,
fromList = foldMap singleton
Ответ №3:
Из вашего комментария, я думаю, вы, возможно, пытаетесь построить:
import Data.Foldable
import Data.Traversable
foo :: (Traversable f, Foldable g) => f (a -> b) -> g a -> f b
foo f g = snd $ mapAccumR ((a:as) fab -> (as, fab a)) (toList g) f
Это позволяет, например:
> import qualified Data.Vector as V
> foo [( 1),( 2),( 3)] (V.fromList [5,6,7])
[8,8,8]
>
Комментарии:
1.
foo [( 1),( 2),( 3)] (V.fromList [5,6,7,8])
работает,foo [( 1),( 2),( 3)] (V.fromList [5,6])
бомбит"Non-exhaustive patterns in lambda"
.2. Да, я знал, что функция была частичной, но я не думаю, что есть четкая альтернатива. Вы могли
cycle
бы использовать список изg
, что кажется ужасной идеей, или вы могли бы ограничить функциюf
, которая изоморфна[]
(например, использовать ограничениеIsList f
).
Ответ №4:
Я не знаю ни одного общего я бы написал конкретную версию или, самое большее, обобщил по типам ввода. Вот примеры с fromList
. Vector
игнорированием того, что Data.Vector.zip
уже существует.
import qualified Data.Vector as V
import Data.Vector (Vector)
import Data.Foldable
import GHC.Exts (IsList(fromList))
zipV1 :: Vector (a -> b) -> Vector a -> Vector b
zipV1 fs as = V.fromList (zipWith ($) (V.toList fs) (V.toList as))
zipV2 :: (Foldable f, Foldable g, IsList (f b)) => f (a -> b) -> g a -> f b
zipV2 fs as = fromList (zipWith ($) (toList fs) (toList as))
Вы могли бы использовать IsList
вместо Foldable
во втором примере.
Комментарии:
1. Обратите внимание, что существует
fromList
fromIsList
, так что это сработает, и многие стандартные изоморфные списку последовательности будут поддерживать его (например,Vector
уже как экземпляр).