nicolas
nicolas

Reputation: 9805

deriving Applicative in haskell

Why wouldn't GHC derive Applicative for KO ?

#!/usr/bin/env stack
-- stack --resolver lts-17.10 script

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

newtype KO a = KO a deriving (Functor, Applicative) -- doesn't derive Applicative

newtype OK f a = OK (f a) deriving (Functor, Applicative) -- that's ok

main :: IO ()
main = print "hello"

• Can't make a derived instance of ‘Applicative KO’ (even with cunning GeneralizedNewtypeDeriving): cannot eta-reduce the representation type enough • In the newtype declaration for ‘KO’typecheck

Upvotes: 2

Views: 376

Answers (3)

Daniel Wagner
Daniel Wagner

Reputation: 152837

GHC has four ways of creating instances:

  • stock: write new implementations of each of the class's methods from scratch via pattern matching and the like
  • newtype: when declaring a newtype that wraps a type that already has an instance, reuse that instance, inserting and removing newtype wrappers at the appropriate moments
  • anyclass: declare an instance with no method definitions (hence using default implementations for every method, if there are any)
  • via: a generalization of newtype, it lets you inherit the instance from any other type with visibly the same representation, again by inserting/removing newtype wrappers at appropriate moments

In your code snippet, the stock method is used to derive Functor because you have turned on DeriveFunctor. There is currently no stock derivation for Applicative, though. The via method only ever fires when explicitly requested, and you have not turned on DeriveAnyClass, so the only option left is newtype, so GHC attempts to inherit an instance from the wrapped type. Then it runs into trouble, because Applicative is supposed to be for container types, and the contained type isn't one, so it complains.

This explains the Functor vs Applicative difference for your first snippet. For the first vs second snippet difference for Applicative, we need only observe that after unwrapping the second one, we do have a container type; hence you get an Applicative instance for OK any time its f argument has one.

See also the documentation on deriving strategies.

Upvotes: 5

nicolas
nicolas

Reputation: 9805

To illustrate how DerivingVia is more explicit about each choice, here's a translation of each choice

#!/usr/bin/env stack
-- stack --resolver lts-17.10 script

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE StandaloneDeriving #-}

import Control.Monad.Identity
import Control.Monad.Trans.Maybe

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- Legacy
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

-- Endows with a Functor instance
newtype FStock a = FStock {unFStock :: a} deriving (Functor)

-- >>>  unFStock $ (+ 1) <$> FStock 6
-- 7

-- Delegates the functor instance to f
newtype HFDel f a = HFDel {unHFDel :: f a} deriving (Functor)

-- >>> unHFDel $ (+1) <$> HFDel (Just 6)
-- Just 7

-- bad = unHFDel $ (+ 1) <$> HFDel 6 -- error nothing to delegate to

deriving instance Applicative f => Applicative (HFDel f)

-- >>> unHFDel $ (HFDel (Just (+1))) <*> HFDel (Just 6)
-- Just 7

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- Deriving Via
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

newtype F a = F {unF :: a}

deriving via Identity instance Functor F

deriving via Identity instance Applicative F

-- Endows with instances, by explicitely delegating to the Identity
-- >>> unF $ (+ 1) <$> F 6
-- 7
-- >>> unF $ F (+ 1) <*> F 6
-- 7

newtype G f a = G {unG :: f a}

-- Delegates explicitely the instances to f
deriving via (f :: * -> *) instance (Functor f) => Functor (G f)

deriving via (f :: * -> *) instance (Applicative f) => Applicative (G f)

-- >>> unG $ (+1) <$> G (Just 6)
-- Just 7

-- bad = unG $ (+1) <$> G 6 -- error nothing to delegate to

-- >>> unG $ G (Just (+1)) <*> G (Just 6)
-- Just 7

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

main :: IO ()
main = do
  -- legacy
  print $ unFStock $ (+ 1) <$> FStock 6 -- 7
  print $ unHFDel $ (+ 1) <$> HFDel (Just 6) -- Just 7
  --print $ unHFDel $ (+ 1) <$> HFDel 6 -- error nothing to delegate to
  print $ unHFDel $ (HFDel (Just (+ 1))) <*> HFDel (Just 6) -- Just 7
  -- deriving via
  print $ unF $ (+ 1) <$> F 6 -- 7
  print $ unF $ F (+ 1) <*> F 6 -- 7
  print $ unG $ (+ 1) <$> G (Just 6) -- Just 7
  -- print $ unG $ (+ 1) <$> G 6 -- error nothing to delegate to
  print $ unG $ G (Just (+ 1)) <*> G (Just 6) -- Just 7

Upvotes: 0

nicolas
nicolas

Reputation: 9805

Additionally to the answer, here's some more detailed sample highlighting the two behaviors.

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- deriving Functor
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

newtype F a = F {unF :: a} deriving (Functor)

-- translates to

newtype F' a = F' a

deriving instance Functor F' -- F' endowed with instance -- (stock strategy)

-- whereas

newtype HF f a = HF {unHF :: f a} deriving (Functor)

-- delegates to
newtype HF' f a = HF' (f a)

deriving instance Functor f => Functor (HF' f) -- HF' f delegates to f

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- deriving Applicative
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

-- -- commented for typecheck
-- newtype G a = G a deriving (Functor, Applicative)

-- -- translates to pb
-- newtype G' a = G' a

-- deriving instance Functor G'

-- deriving instance Applicative G' -- G' not endowed with instance

-- -- whereas

newtype HG f a = HG (f a) deriving (Functor)

-- delegates

newtype HG' f a = HG' (f a)

deriving instance Functor f => Functor (HG' f) -- delegates to f

deriving instance Applicative f => Applicative (HG' f) -- delegates to f

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

---

-- Endows with a Functor instance
-- >>>  unF $ (+ 1) <$> F 6
-- 7

-- Delegates the functor instance to f
-- >>> unHF $ (+1) <$> HF (Just 6)
-- Just 7

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

Upvotes: 0

Related Questions