Using custom instance when deriving an instance via GeneralizedNewtypeDeriving

193 views Asked by At

Suppose that we have a typeclass class (A a, B a) => C a where. Using newtype will allow us to clone a data type and then automatically derive the instances via the GeneralizedNewtypeDeriving language extension (See how to write a derivable class? and Handling multiple types with the same internal representation and minimal boilerplate?).

QUESTION : Is it possible to get ghc to automatically derive A and C, but to use our own specified implementation of B in deriving C?

For example the following code (where A = Planet, B = Lives, C = Description) does not work as expected :

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
module Main (main) where

data Cat = Cat String
newtype Dolphin = Dolphin Cat deriving (Planet)

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

class Planet a where
  planet :: a -> String

class Lives a where
  lives :: a -> String

class (Planet a, Lives a) => Description a where
  description :: a -> String

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

instance Planet Cat where
  planet _ = "lives on planet earth,"

instance Lives Cat where
  lives _ = "lives on land"

instance Description Cat where
  description a = (planet a) ++ (lives a)

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

instance Lives Dolphin where
  lives _ = "lives in the sea"

--want the following derivation to use the instance of 
--"Lives" for "Dolphin" above
deriving instance Description Dolphin

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

main = do
  print $ description (Cat "test")
  -- > "lives on planet earth,lives on land"
  -- OK
  print $ description (Dolphin (Cat "test"))
  -- > "lives on planet earth,lives on land"
  -- NOT OK. Want "lives on planet earth,lives in the sea"

What I was expecting/wanted was for the Dolphin instance of Lives to be invoked in the derivation of Description.

Obviously the following program works, but it requires one to explicitly instantiate Description for Dolphin :

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
module Main (main) where

data Cat = Cat String
newtype Dolphin = Dolphin Cat deriving (Planet)

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

class Planet a where
  planet :: a -> String

class Lives a where
  lives :: a -> String

class (Planet a, Lives a) => Description a where
  description :: a -> String

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

instance Planet Cat where
  planet _ = "lives on planet earth,"

instance Lives Cat where
  lives _ = "lives on land"

instance Description Cat where
  description a = (planet a) ++ (lives a)

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

instance Lives Dolphin where
  lives _ = "lives in the sea"

instance Description Dolphin where
  description a = (planet a) ++ (lives a)

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

main = do
  print $ description (Cat "test")
  -- > "lives on planet earth,lives on land"
  --[OK]
  print $ description (Dolphin (Cat "test"))
  -- > "lives on planet earth,lives in the sea"
  --[OK]

p.s. What is puzzling is that if (in the first program) I do not declare :

instance Lives Dolphin where
  lives _ = "lives in the sea"

Then ghc complains :

Main.hs:36:1:
    No instance for (Lives Dolphin)
      arising from the superclasses of an instance declaration
    In the instance declaration for ‘Description Dolphin’

Seems strange that ghc would complain about the absence of instance Lives Dolphin where if it is not using it in the (automatic) derivation of Description for Dolphin.

2

There are 2 answers

2
MathematicalOrchid On BEST ANSWER

Consider the following:

newtype ProcessID = PID Int deriving Eq

What this does is write an instances that looks like

instance Eq PID where
  (PID x) == (PID y)    =    x == y

In other words, when you call == on a PID, it unwraps it into an ordinary Int, and then executes == on that.

I imagine the deriving instance Description Dolphin is doing exactly the same; unwrapping a Dolphine into a Cat, and then calling the description method on that. Which isn't what you want at all!

Question: If the definition of description is always the same, why does it need to be a class at all? Why can't you just define a regular function that does this?

(Or is this a simplification of some more complicated problem you want to solve?)

0
Iceland_jack On

This is now possible with DerivingVia. First you define a newtype wrapper

newtype Describe a = Describe a
 deriving
 newtype (Planet, Lives)

instance (Planet a, Lives a) => Description (Describe a) where
  description :: Describe a -> String
  description a = planet a ++ lives a

Then you can derive an instance of Dolphin via Describe Dolphin

{-# Language DerivingVia #-}

newtype Dolphin = Dolphin Cat
 deriving
 newtype Planet

 deriving Description
 via Describe Dolphin

Now:

>> main
"lives on planet earth,lives on land"
"lives on planet earth,lives in the sea"