2014-12-19 3 views
3

Предположим, что у нас есть класс 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?).Использование пользовательского экземпляра при получении экземпляра через генерализованныйNewtypeDeriving

ВОПРОС: Можно ли получить GHC автоматически получить A и C, но использовать нашу собственную заданную реализацию B при выводе C?

Например, следующий код (где A = Planet, B = Lives, C = 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’ 

Кажется странным, что GHC будет жаловаться на отсутствие instance Lives Dolphin where если не используя его в (автоматический) вывод Description для Dolphin.

ответ

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 всегда одно и то же, почему он должен быть классом вообще? Почему вы не можете просто определить регулярную функцию, которая делает это?

(Или это упрощение некоторых более сложной проблемы, которую вы хотите решить?)

+0

'разворачивания Dolphine в Кота, а затем вызвать метод описания на that': Если это происходит, то GHC будет не использовать 'instance Lives Dolphin where' (но вместо этого будет использовать' instance Lives Cat where').Итак, почему ghc жалуется, если этот «экземпляр Lives Dolphin where» отсутствует в первой программе? – artella

+0

'Вопрос: Если определение описания всегда одно и то же, почему он должен быть классом вообще? Почему вы не можете просто определить правильную функцию, которая это делает? »- это просто упрощение ситуации, которую я имел в виду. Фактически я представлял себе сложную иерархию типа-типа и хочу изменить поведение (для данного типа) одного экземпляра в середине иерархии этого типа, не объявляя новый тип и не обновляя все экземпляры, только один экземпляр модифицирован. В идеале я бы только объявил измененный экземпляр. – artella

Смежные вопросы