3
votes

Classe de types applicative basée sur deux foncteurs différents

Y a-t-il quelque chose de similaire à la classe de type Applicative , mais où il y a deux foncteurs pour chaque côté de l'application qui sont différents?

c'est-à-dire (<*>) :: (Functor f, Functor g) => f (a -> b) -> g a -> f b


5 commentaires

Y a-t-il des types de béton spécifiques avec lesquels vous souhaitez l'utiliser? Cette fonction ne peut pas être dérivée pour n'importe quel deux foncteurs.


Ou deux applicatifs quelconques.


@ 4castle: Désolé, je n'ai pas assez bien réfléchi. Je recherche un zip généralisé capable de gérer et de convertir automatiquement différents types de séquences.


Je pense que vous auriez besoin de fournir vous-même la transformation naturelle d'un foncteur à l'autre; même en supposant que vous puissiez extraire la fonction de l'applicatif f et l'appliquer au ga pour obtenir un gb , vous avez toujours besoin de quelque chose avec le type gb -> fb pour obtenir votre résultat final.


(Probablement deux transformations naturelles, l'autre pour obtenir un g (a -> b) de f (a -> b) dans le premier endroit.)


4 Réponses :


1
votes

D'après votre commentaire, je pense que vous essayez peut-être de construire:

> import qualified Data.Vector as V
> foo [(+1),(+2),(+3)] (V.fromList [5,6,7])
[8,8,8]
> 

Cela permet, par exemple:

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


2 commentaires

toto [(+1), (+ 2), (+ 3)] (V.fromList [5,6,7,8]) fonctionne, toto [(+1), (+2), (+ 3)] (V.fromList [5,6]) bombes avec "Modèles non exhaustifs en lambda" .


Oui, je savais que la fonction était partielle, mais je ne pense pas qu'il y ait d'alternative claire. Vous pouvez parcourir la liste à partir de g , ce qui semble être une mauvaise idée, ou vous pouvez limiter la fonction à f qui sont isomorphes à [] (par exemple, utilisez la contrainte IsList f ).



0
votes

Je ne connais aucun fromList général. J'écrirais la version concrète, ou tout au plus généraliserais sur les types d'entrée. Voici des exemples avec Vector , en ignorant que Data.Vector.zip existe déjà.

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))

Vous pouvez utiliser IsList code> au lieu de Foldable dans le deuxième exemple.


1 commentaires

Notez qu'il y a fromList from IsList , donc cela fonctionnerait, et de nombreuses séquences standard isomorphes de liste le supportent (par exemple, Vector une instance).



2
votes

Un concept général de "type séquence" est un monoïde libre. Puisque vous regardez des types de séquences polymorphes, nous pouvons construire sur Traversable.

fromList = foldMap singleton

Voir la note ci-dessous.

type Semigroup1 t = forall a. Semigroup (t a)

type Monoid1 t = forall a. Monoid (t a)

En quoi est-ce un type de séquence? Très inefficace. Mais nous pourrions ajouter un tas de méthodes avec des implémentations par défaut pour le rendre efficace. Voici quelques fonctions de base:

zipApp :: (Foldable t, Foldable u, Sequence v) = t (a -> b) -> u a -> v b
zipApp fs xs = fromList $ zipWith ($) (toList fs) (toList xs)

Avec ces outils, vous pouvez compresser deux séquences quelconques pour en créer une troisième.

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

Remarque sur les versions récentes du GHC

Pour le GHC de pointe, vous pouvez utiliser QuantifiedConstraints et RankNTypes et ConstraintKinds et définissez

class (Traversable t, Monoid1 t) => Sequence t where
  singleton :: a -> t a

Le faire de cette façon vous permettrait d'écrire, par exemple,

class Semigroup1 t where
  (<=>) :: t a -> t a -> t a

class Semigroup1 t => Monoid1 t where
  mempty1 :: t a


0 commentaires

5
votes

(Suite à une suggestion de @dfeuer dans les commentaires.)

Il existe une construction appelée day convolution qui vous permet de conserver la distinction entre deux foncteurs lors de l'exécution d'opérations applicatives, et de retarder le moment de la transformation de l'un dans l'autre.

Le Day code> type est simplement une paire de valeurs fonctionnelles, avec une fonction qui combine leurs résultats respectifs:

res' :: ZipList (Char,Char)
res' = dap $ trans2 (ZipList . toList) res

ghci> res'
ZipList {getZipList = [('b','f'),('a','o')]}

Notez que les valeurs de retour réelles des foncteurs sont existencialisé; la valeur de retour de la composition est celle de la fonction.

Day présente des avantages par rapport aux autres façons de combiner des foncteurs applicatifs. Contrairement à Sum a>, la composition est toujours applicative. Contrairement à Compose a>, la composition est "impartiale" et n'impose pas d'ordre d'imbrication. Contrairement à Produit a>, cela nous permet de combiner facilement des actions applicatives avec différents types de retour, nous avons juste besoin de fournir une fonction d'adaptation appropriée.

Par exemple, voici deux Day ZipList (Vec Nat2) Char valeurs:

res :: Day ZipList (Vec Nat2) (Char,Char)
res = (,) <$> day1 <*> day2

( Nat2 provient de fin package, il est utilisé pour paramétrer un Vec de taille fixe à partir de vec .)

Nous pouvons très bien les compresser ensemble:

{-# 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

Et puis transformez le Vec en une ZipList et réduisez le Jour :

data Day f g a = forall b c. Day (f b) (g c) (b -> c -> a)

Utilisation de dap et trans2 code> .

Problème de performance possible: lorsque nous élevons l'un des foncteurs à Day , l'autre reçoit un mannequin pure () code> valeur. Mais c'est un poids mort lors de la combinaison de Day s avec () . On peut travailler plus intelligemment en enveloppant les foncteurs dans Lift pour les transformateurs, pour obtenir des opérations plus rapides pour les cas simples "purs".


1 commentaires

Un exemple pratique de Day : ma bibliothèque "process-streaming" utilise Day en interne pour combiner des gestionnaires pour les stdin, stdout et stderr d'un processus externe en un seul Applicatif . hackage.haskell. org / package / process-streaming-0.9.3.0 / docs /… Il utilise également l'astuce Lift .