Аппликативный класс типов на основе двух разных функторов - PullRequest
3 голосов
/ 17 апреля 2019

Есть ли что-то похожее на класс типа Applicative, но где для каждой стороны приложения есть два функтора, которые различны?

, т. Е. (<*>) :: (Functor f, Functor g) => f (a -> b) -> g a -> f b

Ответы [ 4 ]

5 голосов
/ 17 апреля 2019

(Следуя предложению @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 = (,) <$> day1 <*> day2

А затем преобразовать Vec в 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 с (<*>). Можно работать умнее, оборачивая функторы в Lift для трансформаторов, чтобы получить более быстрые операции для простых «чистых» случаев.

2 голосов
/ 17 апреля 2019

Одним из общих понятий «тип последовательности» является свободный моноид.Поскольку вы смотрите на полиморфные типы последовательностей, мы можем опираться на 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
1 голос
/ 17 апреля 2019

Из вашего комментария, я думаю, вы, возможно, пытаетесь построить:

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]
> 
0 голосов
/ 17 апреля 2019

Я не знаю каких-либо общих 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 во втором примере.

...