2
votes

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

3
if you :i Applicative you'll see this: type Applicative :: (* -> *) -> Constraint - so a type can be applicative if it's kind is * -> * (the container is applicative - like [] or Maybe - not [a], Maybe a) - that's why it is not working - your newtype is a wrapper for any type - if you'd make an instance for OK you would do Applicative f => instance Applicative (OK f) (Ok f : * -> *) - try to write an instance for KO yourself - there is one (basically Identity - look it up) - but it's not what you would expect from a newtype wrapperRandom Dev
If the Identity behaviour is what you want, you could use DerivingVia and say newtype KO a = KO a deriving (Functor, Applicative) via Identitykosmikus
deriving via is more regular and less surprisenicolas
@Carsten, fwiw, :i Functor also shows type Functor :: (* -> *) -> Constraint. Why doesn't Functor pose the same problem, then?Enlico
@Enlico Functor doesn't pose the same problem because it is using a different instance generation method. My answer has some details and a link to the full documentation.Daniel Wagner

3 Answers

5
votes

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.

0
votes

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

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

0
votes

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