#haskell #type-families #deriving #derivingvia
Вопрос:
У меня есть ситуация, когда у меня есть несколько похожих newtypes
, которые все должны быть примерами Random
Arbitrary
, и много других вещей. Все они нуждаются в одинаковой пользовательской реализации функций randomR
, random
, и т.д. arbitrary
Поэтому я поместил все эти реализации в класс.
Вот упрощенный пример, который просто справляется Random
.
{-# LANGUAGE ConstrainedClassMethods #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
import qualified System.Random as SR
-- Numbers that are restricted to a narrower range
class Narrow t where
type BaseType t
-- Unsafe constructor for the instance type
bless :: BaseType t -> t
-- Safe constructor for the instance type
narrow :: (Ord t, Bounded t) => BaseType t -> t
narrow x | x' < (minBound :: t) = error "too small"
| x' > (maxBound :: t) = error "too big"
| otherwise = x'
where x' = bless x :: t
-- Deconstructor for the instance type
wide :: t -> BaseType t
-- Random
randomR
:: (Ord t, Bounded t, SR.Random (BaseType t), SR.RandomGen g)
=> (t, t) -> g -> (t, g)
randomR (a, b) g = (narrow x, g')
where (x, g') = SR.randomR (wide a, wide b) g
random
:: (Ord t, Bounded t, SR.Random t, SR.RandomGen g)
=> g -> (t, g)
random = SR.randomR (minBound, maxBound)
Вот пример одного из типов, которые мне нужны.
-- | A number on the unit interval
newtype UIDouble = UIDouble Double
deriving (Eq, Ord)
instance Bounded UIDouble where
minBound = UIDouble 0
maxBound = UIDouble 1
instance Narrow UIDouble where
type BaseType UIDouble = Double
bless = UIDouble
wide (UIDouble x) = x
Я хочу, чтобы это был пример Random
. В идеале я хотел бы написать что-то вроде:
deriving ?strategy? instance SR.Random UIDouble
и пусть компилятор знает, как использовать методы, определенные в Narrow
для реализации Random
. Но вместо этого я должен написать
instance SR.Random UIDouble where
randomR = randomR
random = random
Это не проблема сделать это для нескольких методов, но сделать это для Num
, Fractional
, RealFrac
, Floating
Serialize
, и т. Д. для каждого из моих типов это немного утомительно.
Альтернатива, которую я исследовал, состоит в том, чтобы написать
instance (Narrow t) => SR.Random t where
randomR = randomR
random = random
потому что мне нужно было бы написать это только один раз для класса, а не повторять это для каждого типа. Но это ведет к UndecidableInstances
тому, что, как я понимаю, плохо. Я TemplateHaskell
уверен , что мог бы сделать это с помощью. Но мне интересно, есть ли какая-нибудь причудливая языковая прагма или магия программирования на уровне типов, которая упростит это?
Комментарии:
1. «` Неразрешимые ситуации», которые, как я понимаю, плохи » — это распространенное заблуждение. Существует риск, что вы сделаете цикл компилятора, если ваши определения будут циклическими. (Твои-нет.) При условии, что компилятор завершит работу, объектный код будет типобезопасным и счастливым. (Если запускает циклы, это не из-за
UndecidableInstances
.)
Ответ №1:
Сначала вы определяете новый тип и даете ему нужный экземпляр раз и навсегда:
newtype UseNarrow a = UN a
instance Narrow a => SR.Random (UseNarrow a) where
randomR (UN lo, UN hi) g = (UN v, g) where v = randomR (lo, hi) g
random g = (UN v, g) where v = random g
Затем во всех местах, где вы хотите использовать этот экземпляр, вы пишете:
deriving via (UseNarrow UIDouble) instance SR.Random UIDouble
Возможно, у меня немного не в порядке с синтаксисом, так как я не проверял вышесказанное. Но у тебя должна быть идея.
Для дальнейшего чтения обратитесь к DerivingVia
Руководству пользователя GHC.
Комментарии:
1. Я думал, что для этого
DerivingVia
требуется указать тип, к которомуUIDouble
будет применено принуждение. Так не окажется лиminBound
иmaxBound
в конечном итоге тем, что относится к принудительному типу?2. @mhwombat я… так не думаю? Обратите внимание , что
randomR
в RHS определения метода в экземпляре, который я показываю здесь, вызывается типa
, а неUseNarrow a
! (На самом деле контекст экземпляра даже не требует достаточных ограничений, чтобы гарантировать существование типаminBound
/maxBound
forUseNarrow a
.)3. @mhwombat Вы говорите о типе via, который есть
UseNarrow UIDouble
. Даниэль использовал автономный вывод, но вы также можете написать его традиционно:newtype UIDouble .. deriving SR.Random via UseNarrow UIDouble
.4. Вы можете подумать о
SR.Random (UseNarrow UIDouble)
SR.Random UIDouble
том, что словарь вводится в словарь принудительно5. @Iceland_jack Ну, я использовал автономное получение, потому что вопрос использовал автономное получение…