Один из способов получить эффект, на который, похоже, нацелен ваш образец.
Сначала я хочу описать способ достижения того, к чему вы стремитесь. Давайте еще раз посмотрим на ваш последний пример кода:
-- Note, Frob is an instance of class Frobbable
getFrobbable :: (Frobbable a) => Frob -> a
getFrobbable x = x
По сути, это операция литья. Он принимает Frob
и просто забывает, что это такое, сохраняя только знание о том, что у вас есть экземпляр Frobbable
.
Для этого в Haskell есть идиома. Для этого требуется расширение ExistentialQuantification
в GHC. Вот пример кода:
{-# LANGUAGE ExistentialQuantification #-}
module Foo where
class Frobbable a where
getInt :: a -> Int
data Frob = Frob Int
instance Frobbable Frob where
getInt (Frob i) = i
data FrobbableWrapper = forall a . Frobbable a => FW a
instance Frobbable FrobbableWrapper where
getInt (FW i) = getInt i
Ключевой частью является структура данных FrobbableWrapper
. С его помощью вы можете написать следующую версию вашей функции приведения getFrobbable
:
getFrobbable :: Frobbable a => a -> FrobbableWrapper
getFrobbable x = FW x
Эта идиома полезна, если вы хотите иметь гетерогенный список, элементы которого имеют общий класс типов, даже если они могут не иметь общего типа. Например, в то время как Frobbable a => [a]
не позволит вам смешивать разные экземпляры Frobbable
, список [FrobbableWrapper]
определенно позволит.
Почему код, который вы разместили, не разрешен
Теперь, почему вы не можете написать свою операцию приведения как есть? Все дело в том, что можно было бы сделать, если бы вашей исходной функции getFrobbable
было разрешено ввести проверку.
Уравнение getFrobbable x = x
действительно следует рассматривать как уравнение. x
никак не модифицируется; таким образом, ни его тип. Это делается по следующей причине:
Давайте сравним getFrobbable
с другим объектом. Рассмотреть возможность
anonymousFrobbable :: Frobbable a => a
anonymousFrobbable = undefined
(Код, включающий undefined
, является отличным источником неловкого поведения, когда вы действительно хотите подтолкнуть свою интуицию.)
Теперь предположим, что кто-то приходит и вводит определение данных и функцию наподобие
data Frob2 = Frob2 Int Int
instance Frobbable Frob2 where
getInt (Frob2 x y) = y
useFrobbable :: Frob2 -> [Int]
useFrobbable fb2 = []
Если мы перейдем к ghci, мы сможем сделать следующее:
*Foo> useFrobbable anonymousFrobbable
[]
Нет проблем: подпись anonymousFrobbable
означает "Вы выбираете экземпляр Frobbable, а я притворяюсь, что я из этого типа".
Теперь, если мы попытаемся использовать вашу версию getFrobbable
, вызов типа
useFrobbable (getFrobbable someFrob)
приведет к следующему:
someFrob
должен иметь тип Frob
, так как он передается getFrobbable
.
(getFrobbable someFrob)
должен иметь тип Frob2
, так как он передается useFrobbable
- Но по уравнению
getFrobbable someFrob = someFrob
мы знаем, что getFrobbable someFrob
и someFrob
имеют один и тот же тип.
Таким образом, система делает вывод, что Frob
и Frob2
относятся к одному и тому же типу, даже если это не так. Следовательно, это рассуждение является необоснованным, что в конечном итоге является типом рационального обоснования того, почему опубликованная вами версия getFrobbable
не проверяет тип.
person
intoverflow
schedule
22.07.2010