Почему мы не можем иметь случайные экземпляры классов, производные для перечислений в Haskell?
Я написал это сегодня:
data Door = A | B | C
deriving (Eq,Bounded,Enum)
instance Random Door where
randomR (lo,hi) g = (toEnum i, g')
where (i,g') = randomR (fromEnum lo, fromEnum hi) g
random = randomR (minBound,maxBound)
И я понял, что это примерно копипастабильно для любого перечисления. Я попытался вставить слово "Рэндом" в выводящий пункт, но это не помогло.
Затем я поискал в интернете и нашел это:
Пожалуйста, предоставьте пример для (перечисление a, ограниченный a) для случайного #21
Несколько цитат, которые, кажется, отвечают на мой вопрос, но я их не совсем понимаю:
Какой экземпляр вы имеете в виду, instance (Bounded a, Enum a) => Случайный, где ...? Не может быть такого примера, так как это было бы перекрываются с каждым другим экземпляром.
Это предотвратит любые пользовательские производные экземпляры. ...
Почему это не может быть автоматизировано, либо с помощью производящего предложения, либо с помощью реализации по умолчанию, по крайней мере.
Почему это не сработает?
instance (Bounded a, Enum a) => Random a where
randomR (lo,hi) g = (toEnum i, g')
where (i,g') = randomR (fromEnum lo, fromEnum hi) g
random = randomR (minBound,maxBound)
1 ответ:
Комментарии ссылаются на то, что в Haskell (фактически в Haskell с расширением
FlexibleInstances) сопоставление экземпляров выполняется путем сопоставления типа без учета ограничений. После того, как типовое соответствие успешно выполнено, ограничения затем проверяются и генерируют ошибки, если они не выполняются. Итак, если вы определяете:instance (Bounded a, Enum a) => Random a where ...Вы фактически определяете экземпляр для каждого типа
a, а не только для типовa, которые имеют экземплярыBoundedиEnum. Это как если бы ... вы написали:instance Random a where ...Это будет потенциально конфликтовать с любыми другими экземплярами, определенными библиотекой или пользователем, такими как:
Есть способы обойти это, но в конечном итоге все становится довольно запутанным. Кроме того, это может привести к некоторым загадочным сообщениям об ошибках типа компиляции, как отмечено ниже.newtype Gaussian = Gaussian Double instance Random Gaussian where ...В частности, если вы поместите в модуль следующее:
module RandomEnum where import System.Random instance (Bounded a, Enum a) => Random a where randomR (lo,hi) g = (toEnum i, g') where (i,g') = randomR (fromEnum lo, fromEnum hi) g random = randomR (minBound,maxBound)Вы обнаружите, что вам нужно расширение
FlexibleInstances, чтобы разрешить ограничения на экземпляры. Это прекрасно, но если вы добавите это, то увидите, что вам нужно расширениеUndecidableInstances. Это, возможно, менее хорошо, но если вы добавите это, то обнаружите, что вы получите ошибку при вызовеrandomRна RHS вашего определенияrandomR. GHC определил, что экземпляр, который вы определили, теперь перекрывается со встроенным экземпляром дляInt. (На самом деле это совпадение, чтоIntявляется одновременноBoundedиEnum- он также перекрывается со встроенным экземпляром дляDouble, который является ни.)В любом случае, вы можете обойти это, сделав ваш экземпляр перекрывающимся, так что следующее:
{-# LANGUAGE FlexibleInstances, UndecidableInstances #-} module RandomEnum where import System.Random instance {-# OVERLAPPABLE #-} (Bounded a, Enum a) => Random a where randomR (lo,hi) g = (toEnum i, g') where (i,g') = randomR (fromEnum lo, fromEnum hi) g random = randomR (minBound,maxBound)Будет фактически компилироваться.
Это в основном нормально, но вы можете закончить с некоторыми странными сообщениями об ошибках. Обычно используется следующая программа:
main = putStrLn =<< randomIOБудет генерировать разумное сообщение об ошибке:
No instance for (Random String) arising from a use of `randomIO'Но с приведенным выше примером он становится:
No instance for (Bounded [Char]) arising from a use of ‘randomIO’Потому что ваш экземпляр совпадает с
Как бы то ни было, в целом сообщество Haskell избегало помещать подобные экземпляры catch-all в стандартную библиотеку. Тот факт, что они нуждаются в расширенииString, но GHC не может найтиBounded Stringограничение.UndeciableInstancesи прагмахOVERLAPPABLEи потенциально вводят кучу нежелательных экземпляров в программу, все это оставляет неприятный привкус во рту людей. Таким образом, хотя технически возможно добавить такой экземпляр кSystem.Random, этого никогда не произойдет.Точно так же это было бы возможно разрешить
Randomавтоматически выводиться для любых типов, которые являютсяEnumиBounded, но сообщество неохотно добавляет дополнительные механизмы автоматического вывода, особенно для классов типов, таких какRandom, которые просто не так часто используются (по сравнению сShowилиEq). Так что, опять же, этого никогда не случится.Вместо этого стандартный способ разрешить удобные экземпляры по умолчанию-определить некоторые вспомогательные функции, которые могут использоваться в явном экземпляре определение, и это то, что предлагается в нижней части предложения, которое вы связали. Например, в
System.Randomможно определить следующие функции:defaultEnumRandomR :: (Enum a, RandomGen g) => (a, a) -> g -> (a, g) defaultEnumRandomR (lo,hi) g = (toEnum i, g') where (i,g') = randomR (fromEnum lo, fromEnum hi) g defaultBoundedRandom :: (Random a, Bounded a, RandomGen g) => g -> (a, g) defaultBoundedRandom = randomR (minBound, maxBound)И люди напишут:
Это единственное решение, которое имеет шанс превратить его вinstance Random Door where randomR = defaultEnumRandomR random = defaultBoundedRandomSystem.Random.Если это так, и вам не нравится определять явные экземпляры, вы можете придерживаться:
instance {-# OVERLAPPABLE #-} (Bounded a, Enum a) => Random a where randomR = defaultEnumRandomR random = defaultBoundedRandomВ вашем собственном коде.