Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is there no `-XDeriveApplicative` extension?

GHC has several useful language extensions for mechanically deriving various common Haskell typeclasses (-XDeriveFunctor, -XDeriveFoldable, -XDeriveTraversable). It seems that Applicative is another class which is often needed and frequently easily derived. For a simple record containing slots of type a, e.g.,

data SimpleRecord a = Simple a a a

the Applicative instance is trivially derived,

instance Applicative SimpleRecord where
    pure x = Simple x x x
    Simple a1 b1 c1 <*> Simple a2 b2 c2 = Simple (a1 a2) (b1 b2) (c1 c2)

Even in the slightly harder case where some a values are buried in other applicative functors, e.g.,

data MyRecord f a = MyRecord (f a) a

a reasonable instance is easily written,

instance (Applicative f) => Applicative (MyRecord f) where
    pure x = MyRecord (pure x) x
    MyRecord a1 b1 <*> MyRecord a2 b2 = MyRecord (a1 <*> a2) (b1 b1)

Why is it that a -XDeriveApplicative extension implementing these sorts of mechanical instances does not exist? Even the derive and generic-derive packages apparently lack Applicative support. Is there a theoretical issue precluding these instances from being usually valid (beyond those reasons that might also threaten the Functor, Foldable, or Traversable extensions)?

like image 556
bgamari Avatar asked Sep 17 '13 22:09

bgamari


3 Answers

There is at most one instance of Functor for a given data type that follows the functor laws. For example, map is the only lawful implementation of fmap for lists:

fmap id      == id
fmap (f . g) == fmap f . fmap g

But there can be more than one law-abiding instance of Applicative, which isn’t necessarily obvious.

pure id <*> v              == v
pure (.) <*> u <*> v <*> w == u <*> (v <*> w)
pure f <*> pure x          == pure (f x)
u <*> pure y               == pure ($ y) <*> u

For lists, <*> can behave like \fs xs -> concatMap (\f -> map f xs) fs or like zipWith ($), and it isn’t clear which one the compiler should choose.

like image 83
Jon Purdy Avatar answered Nov 09 '22 21:11

Jon Purdy


To echo others, there's no good reason I know of why we can't have -XDeriveApplicative, we simply happen not to. There are typically more than one lawful instance of Foldable and Traversable, and we have a flag to derive those. For a while we had no real good story for traversable laws, but now we have some. Similarly we still have no Foldable laws (but I think we can, see here).

Among the different 'obvious' applicatives, such as forwards and backwards ones (vis a vis <*> itself, and even permuted vis a vis the multiple a in an f a if there are such), then building applicatives as suggested here, with traversal in syntactic order, seems legitimate. However, for recursive types such as lists, or even types with multiple constructors, our choice of valid applicatives blossoms in interesting ways. For listlike regular recursive types, the obvious applicative choice is naturally the "zipLike" once, since it generalizes the nonrecursive case in the natural way, matching structure with structure. For sum types with multiple constructors, the "obvious" choice is much harder to define.

It seems to me that an entirely reasonable derivation of applicative would work as suggested here, in syntactic order on types (including recursive ones) with only one constructor. In cases of multiple constructors, failure seems legitimate.

On the other hand, while Foldable and Traversable seem to come up frequently in their "obvious" forms, its not too clear to me how many times we wish to define "obvious" applicatives, as compared to interesting ones. My intuition tells me this feature would be seldom-exercised, and perhaps simply not frequently useful.

like image 7
sclv Avatar answered Nov 09 '22 20:11

sclv


Next release of GHC's base will include

  • newtype Generically a = Generically a
  • newtype Generically1 f a = Generically1 (f a)

in GHC.Generics.

This allows exactly those instances to be derived out of the box

{-# Language DeriveGeneric #-}
{-# Language DerivingVia #-}
{-# Language DerivingStrategies #-}
..

import GHC.Generics

data SimpleRecord a = Simple a a a
 deriving
 stock Generic1

 deriving (Functor, Applicative)
 via Generically1 SimpleRecord

data MyRecord f a = MyRecord (f a) a
 deriving
 stock Generic1

 deriving (Functor, Applicative)
 via Generically1 (MyRecord f)

This only works for product types, Applicative for sum types is a lot more diverse since we need a coherent way to mediate between the constructors. This requires a function that preserves the Applicative structure (Applicative morphism) called Idiom in this package I wrote:

https://hackage.haskell.org/package/idiomatic

This package allows deriving Applicative for sum types with a type level configuration of Applicative morphisms.

like image 3
Iceland_jack Avatar answered Nov 09 '22 19:11

Iceland_jack