Spec-Zone.ru › Haskell 9

6.6.6. Выведение любого другого класса

DeriveAnyClass
Начиная с:

7.10.1

Разрешает использовать любой класс типов в предложениях deriving.

С помощью DeriveAnyClass можно вывести любой другой класс. Компилятор просто сгенерирует объявление экземпляра без явно определённых методов. Это особенно полезно для классов, у которых минимальный набор пуст, и в частности при написании обобщённых функций.

Рассмотрим, например, простой класс для красивой печати SPretty, который выводит красиво отформатированные строки:

{-# LANGUAGE DefaultSignatures, DeriveAnyClass #-}

class SPretty a where
  sPpr :: a -> String
  default sPpr :: Show a => a -> String
  sPpr = show

Если пользователь не предоставит реализацию sPpr вручную, то по умолчанию будет использоваться show. Теперь мы можем воспользоваться расширением DeriveAnyClass, чтобы легко реализовать экземпляр SPretty для нового типа данных:

data Foo = Foo deriving (Show, SPretty)

Приведённый выше код эквивалентен следующему:

data Foo = Foo deriving Show
instance SPretty Foo

Иными словами, будет создан экземпляр SPretty Foo с пустыми реализациями всех методов. Поскольку в этом примере мы используем DefaultSignatures, реализация sPpr по умолчанию подставляется автоматически.

Обратите внимание на следующие детали

  • Если вы попытаетесь вывести какой-либо класс для newtype и при этом включено расширение GeneralizedNewtypeDeriving, приоритет будет у DeriveAnyClass.
  • Контекст экземпляра определяется сигнатурами типов методов выводимого класса. Например, если класс имеет вид:

    class Foo a where
      bar :: a -> String
      default bar :: Show a => a -> String
      bar = show
    
      baz :: a -> a -> Bool
      default baz :: Ord a => a -> a -> Bool
      baz x y = compare x y == EQ
    

    и вы попытаетесь вывести его с помощью DeriveAnyClass:

    instance Eq   a => Eq   (Option a) where ...
    instance Ord  a => Ord  (Option a) where ...
    instance Show a => Show (Option a) where ...
    
    data Option a = None | Some a deriving Foo
    

    то выведенный экземпляр Foo будет иметь вид:

    instance (Show a, Ord a) => Foo (Option a)
    

    Поскольку сигнатуры типов по умолчанию для bar и baz требуют ограничений Show a и Ord a соответственно.

    Ограничения в сигнатурах типов не по умолчанию также могут влиять на вывод контекста экземпляра. Например, пусть у вас есть такой класс:

    class HigherEq f where
      (==#) :: f a -> f a -> Bool
      default (==#) :: Eq (f a) => f a -> f a -> Bool
      x ==# y = (x == y)
    

    и вы попробовали вывести для него экземпляр:

    instance Eq a => Eq (Option a) where ...
    data Option a = None | Some a deriving HigherEq
    

    Тогда возникнет ошибка примерно следующего содержания:

    No instance for (Eq a)
        arising from the 'deriving' clause of a data type declaration
    

    Это происходит потому, что для сигнатуры типа по умолчанию для (==#) требуется экземпляр Eq (Option a), который, в свою очередь, требует экземпляр Eq a, отсутствующий в текущей области видимости. Но если немного изменить определение HigherEq:

    class HigherEq f where
      (==#) :: Eq a => f a -> f a -> Bool
      default (==#) :: Eq (f a) => f a -> f a -> Bool
      x ==# y = (x == y)
    

    то вывести экземпляр HigherEq Option уже можно. Обратите внимание, что единственное отличие состоит в том, что теперь сигнатура типа не по умолчанию для (==#) добавляет ограничение Eq a. Ограничения из сигнатур типов не по умолчанию никогда не попадают непосредственно в контекст выводимого экземпляра, но могут выполнять требования, предъявляемые сигнатурами типов по умолчанию. В приведённом выше примере сигнатура типа по умолчанию требовала экземпляр Eq a, и сигнатура не по умолчанию удовлетворила это требование, поэтому выведенный экземпляр имеет простой вид:

    instance HigherEq Option
    
  • DeriveAnyClass можно использовать с частично применёнными классами, например:

    data T a = MKT a deriving( D Int )
    

    что генерирует

    instance D Int a => D Int (T a) where {}
    
  • DeriveAnyClass можно использовать для заполнения экземпляров по умолчанию для связанных семейств типов:

    {-# LANGUAGE DeriveAnyClass, TypeFamilies #-}
    
    class Sizable a where
      type Size a
      type Size a = Int
    
    data Bar = Bar deriving Sizable
    
    doubleBarSize :: Size Bar -> Size Bar
    doubleBarSize s = 2*s
    

    Выражение deriving( Sizable ) эквивалентно следующему:

    instance Sizeable Bar where {}
    

    после чего применяются обычные правила подстановки связанных типов из значения по умолчанию, в результате чего Size Bar становится равным Int.

© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/9.14.1/docs/users_guide/exts/derive_any_class.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API