Прикладной класс типов, основанный на двух разных функторах

#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 , чтобы получить a g 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 в a ZipList и сверните 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 from IsList , так что это сработает, и многие стандартные изоморфные списку последовательности будут поддерживать его (например, Vector уже как экземпляр).