-
DerivingVia -
- Подразумевает:
- С:
-
8.6.1
Это позволяет deriving экземпляр класса для типа, указав другой тип, который уже является экземпляром этого класса. Это имеет смысл только в том случае, если методы имеют одинаковое представление во время выполнения в том смысле, что coerce (см. ограничение Coercible) может преобразовать существующую реализацию в желаемую реализацию. В противном случае сгенерированный код будет отклонен с ошибкой типа.
DerivingVia указывается с помощью стратегии вывода via. via требует указания другого типа (тип via) для coerce через. Например, этот код:
{-# LANGUAGE DerivingVia #-}
import Numeric
newtype Hex a = Hex a
instance (Integral a, Show a) => Show (Hex a) where
show (Hex a) = "0x" ++ showHex a ""
newtype Unicode = U Int
deriving Show
via (Hex Int)
-- >>> euroSign
-- 0x20ac
euroSign :: Unicode
euroSign = U 0x20ac
Генерирует следующий экземпляр
instance Show Unicode where
show :: Unicode -> String
show = Data.Coerce.coerce
@(Hex Int -> String)
@(Unicode -> String)
show
Это расширение обобщает GeneralizedNewtypeDeriving. Чтобы вывести Num Unicode с GND (deriving newtype Num) он должен повторно использовать экземпляр Num Int. С DerivingVia, мы можем явно указать тип представления Int:
newtype Unicode = U Int
deriving Num
via Int
deriving Show
via (Hex Int)
euroSign :: Unicode
euroSign = 0x20ac
Дублирование кода часто встречается в объявлениях экземпляров. Обычный паттерн — это поднятие операций над функтором Applicative. Вместо того, чтобы иметь универсальные экземпляры для f a, которые перекрываются со всеми другими подобными экземплярами, как это:
instance (Applicative f, Semigroup a) => Semigroup (f a) .. instance (Applicative f, Monoid a) => Monoid (f a) ..
Мы можем вместо этого создать новый тип App (где App f a и f a представлены одинаково в памяти) и использовать DerivingVia для явного включения использования этого паттерна:
{-# LANGUAGE DerivingVia, DeriveFunctor, GeneralizedNewtypeDeriving #-}
import Control.Applicative
newtype App f a = App (f a) deriving newtype (Functor, Applicative)
instance (Applicative f, Semigroup a) => Semigroup (App f a) where
(<>) = liftA2 (<>)
instance (Applicative f, Monoid a) => Monoid (App f a) where
mempty = pure mempty
data Pair a = MkPair a a
deriving stock
Functor
deriving (Semigroup, Monoid)
via (App Pair a)
instance Applicative Pair where
pure a = MkPair a a
MkPair f g <*> MkPair a b = MkPair (f a) (g b)
Обратите внимание, что тип via не должен быть newtype. Единственное ограничение заключается в том, что он поддается преобразованию с исходным типом данных. Это означает, что может быть произвольное вложение новых типов, как в следующем примере:
newtype Kleisli m a b = Kleisli (a -> m b)
deriving (Semigroup, Monoid)
via (a -> App m b)
Здесь мы используем экземпляр Monoid ((->) a).
Когда используется в сочетании с StandaloneDeriving мы меняем порядок экземпляра, на котором мы основываем вывод, и экземпляра, который мы определяем, например:
deriving via (a -> App m b) instance Monoid (Kleisli m a b)