通过GeneralizedNewtypeDeriving派生实例时使用自定义实例

时间:2014-12-19 18:17:36

标签: haskell ghc typeclass

假设我们有一个类型类class (A a, B a) => C a where。使用newtype将允许我们克隆数据类型,然后通过GeneralizedNewtypeDeriving语言自动派生实例 扩展(请参阅how to write a derivable class?Handling multiple types with the same internal representation and minimal boilerplate?)。

问题:是否可以让ghc自动派生AC,但是在派生{{B时使用我们自己指定的C实现1}}?

例如,以下代码(其中A = PlanetB = LivesC = Description)不起作用预期:

{-# 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"

我期望/想要的是在Dolphin的派生中调用Lives Description实例。

显然以下程序有效,但需要为Description显式实例化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。令人费解的是,如果(在第一个程序中)我没有声明:

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

然后ghc抱怨:

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

如果instance Lives Dolphin where Description Dolphin的{​​{1}}的(自动)推导使用它,那么ghc会抱怨{{1}}没有{}}。

2 个答案:

答案 0 :(得分:2)

请考虑以下事项:

newtype ProcessID = PID Int deriving Eq

这样做是写一个看起来像

的实例
instance Eq PID where
  (PID x) == (PID y)    =    x == y

换句话说,当您在==上致电PID时,会将其解包为普通的Int,然后对其执行==

我想deriving instance Description Dolphin做的完全一样;将Dolphine展开到Cat,然后在其上调用description方法。这根本不是你想要的!

问题:如果description的定义始终相同,为什么它必须是一个类?你为什么不能定义一个这样做的常规函数​​?

(或者这是你要解决的一些更复杂的问题的简化?)

答案 1 :(得分:0)

现在可以使用 DerivingVia。首先你定义一个新的包装器

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

然后你可以通过 Dolphin

派生出一个 Describe Dolphin 的实例
{-# Language DerivingVia #-}

newtype Dolphin = Dolphin Cat
 deriving
 newtype Planet

 deriving Description
 via Describe Dolphin

现在:

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