-
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) ..
Вместо этого мы можем создать newtype 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 могут быть вложены произвольным образом, как в следующем примере:
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)