Domanda

Secondo la Typeclassopedia (tra le altre fonti), Applicative appartiene logicamente tra Monad e Pointed (e quindi Functor) nella gerarchia di classi di tipi, quindi idealmente avremmo qualcosa del genere se il preludio di Haskell fosse scritto oggi:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

class Functor f => Pointed f where
    pure :: a -> f a

class Pointed f => Applicative f where
    (<*>) :: f (a -> b) -> f a -> f b

class Applicative m => Monad m where
    -- either the traditional bind operation
    (>>=) :: (m a) -> (a -> m b) -> m b
    -- or the join operation, which together with fmap is enough
    join :: m (m a) -> m a
    -- or both with mutual default definitions
    f >>= x = join ((fmap f) x)
    join x = x >>= id
    -- with return replaced by the inherited pure
    -- ignoring fail for the purposes of discussion

(Dove quelle definizioni predefinite sono state riscritte da me dalla spiegazione su Wikipedia , gli errori sono miei, ma se ci sono errori è almeno in linea di principio possibile.)

Dato che le librerie sono attualmente definite, abbiamo:

liftA :: (Applicative f) => (a -> b) -> f a -> f b
liftM ::       (Monad m) => (a -> b) -> m a -> m b

e

(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
ap    ::       (Monad m) => m (a -> b) -> m a -> m b

Nota la somiglianza tra questi tipi all'interno di ciascuna coppia.

La mia domanda è: sono liftM (distinti da liftA) e ap (distinti da <*>), semplicemente il risultato della realtà storica che <=> non è stato progettato con <= > e <=> in mente? Oppure sono in qualche altro modo comportamentale (potenzialmente, per alcune <=> definizioni legali) distinti dalle versioni che richiedono solo un <=> contesto?

Se sono distinti, potresti fornire un semplice insieme di definizioni (obbedendo alle leggi richieste di <=>, <=>, <=> e <=> definizioni descritte nella Typeclassopedia e altrove ma non applicate dal tipo di sistema) per cui <=> e <=> si comportano diversamente?

In alternativa, se non sono distinti, potresti provare la loro equivalenza usando le stesse leggi delle premesse?

È stato utile?

Soluzione

liftA, liftM, fmap e . dovrebbero avere tutti la stessa funzione, e devono esserlo se soddisfano la legge del funzione :

fmap id = id

Tuttavia, questo non è verificato da Haskell.

Ora per Applicativo. È possibile che ap e <*> siano distinti per alcuni funzioni semplicemente perché potrebbe esserci più di un'implementazione che soddisfa i tipi e le leggi. Ad esempio, Elenco ha più di un'istanza Applicative possibile. Potresti dichiarare un applicativo come segue:

instance Applicative [] where
  (f:fs) <*> (x:xs) = f x : fs <*> xs
  _      <*> _      = []
  pure              = repeat

La funzione liftM2 id verrebbe comunque definita come Monad, che è l'istanza [] fornita gratuitamente con ogni newtype ZipList a = ZipList [a]. Ma qui hai un esempio di un costruttore di tipi con più di un'istanza ZipList, che soddisfano entrambe le leggi. Ma se le tue monadi e i tuoi agenti applicativi non sono d'accordo, è considerato una buona forma avere tipi diversi per loro. Ad esempio, la <=> istanza sopra non è d'accordo con la monade per <=>, quindi dovresti davvero dire <=> e quindi creare la nuova istanza per <=> anziché <=>.

Altri suggerimenti

Loro possono differire, ma non dovrebbero .

Possono differire perché possono avere implementazioni diverse: una è definita in instance Applicative mentre l'altra è definita in instance Monad. Ma se effettivamente differiscono, direi che il programmatore che ha scritto quelle istanze ha scritto un codice fuorviante.

Hai ragione: le funzioni esistono come fanno per ragioni storiche. Le persone hanno idee forti su come avrebbero dovuto essere le cose.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top