Я пошел в церковь и молился Церкви. И вот, я видел прекрасное видение. Мне открылись платонические формы сумм и типов продуктов. И я увидел, что это хорошо.
Все приведенные примеры являются простыми старыми типами сумм в натуральном выражении *
. Теперь Either
и (,)
являются простейшими суммами и типами продуктов, соответственно, и не следует удивляться тому, что их можно использовать для моделирования всех других сумм и видов продукции в натуральной форме *
. Посмотрите - вот ваши примеры, по модулю обертывание и разворачивание:
type A = Either B (C, D)
type B = String
type C = String
type D = Int
Вы можете сделать большие примеры, вкладывая в Either
сек и кортежи:
data IntOrBoolOrChar = Int Int | Bool Bool | Char Char
type IntOrBoolOrChar = Either Int (Either Bool Char)
data IntAndBoolAndChar = IBC Int Bool Char
type IntAndBoolAndChar = (Int, (Bool, Char))
Так что, если вы счастливы построить свой типы полностью из Either
и (,)
, мы можем запрограммировать в общих чертах эти два строительных блока.
instance HasString String where
getString = id
instance (HasString a, HasString b) => HasString (Either a b) where
getString (Left x) = getString x
getString (Right y) = getString y
Трудный вариант - это кортеж. Идея состоит в том, что если мы сможем найти строку в левом компоненте кортежа (т. Е. HasString a
), то мы вернем ее.В противном случае мы рассмотрим правильный компонент. Мы хотели бы написать так:
instance HasString a => HasString (a, b) where
getString = getString . fst
instance HasString b => HasString (a, b) where
getString = getString . snd
Haskell отклоняет эти объявления, потому что экземпляры являются дубликатами. При разрядке ограничения, такого как HasString (a, b)
, решатель выбирает наиболее конкретный экземпляр, который он может найти для этой главы, и пытается разрешить ограничения экземпляра, но не отбрасывает, если он не может этого сделать.
Спецификация, изложенная выше, является невыразимой в ванильном Haskell. Но есть well-known trick с использованием семейств типов, которые могут помочь нам здесь. Идея состоит в том, чтобы использовать результат семейства типов, чтобы рассказать нам, какую половину кортежа мы можем найти, чтобы найти String
дюйма. Это, оказывается, является одним из немногих случаев, когда булевы типа на самом деле полезны.
Итак, когда мы можем найти String
в виде?
-- with And and Or being the usual Boolean type families
type family HasString' a where
HasString' String = True
HasString' (a, b) = Or (HasString' a) (HasString' b)
HasString' (Either a b) = And (HasString' a) (HasString' b)
HasString' _ = False
Как указано на the wiki page I linked above, трюк заключается в добавлении нового класса с дополнительным параметром для результата HasString'
, который используется для отправки к нужному экземпляру. Вот почему имеет смысл использовать Booleans здесь: я выбор между двумя вариантами. Если бы у меня было три варианта выбора, я бы использовал трехзначный тип. Don't use Booleans to represent propositions.
data Proxy a = Proxy
class HasStringTup inLeft a b where
getStringTup :: Proxy inLeft -> (a, b) -> String
instance HasStringTup (HasString' a) a b => HasString (a,b) where
getString = getStringTup (Proxy :: Proxy (HasString' a))
-- HasString' was True for a, so we can get a string from it
instance HasString a => HasStringTup True a b where
getStringTup _ = getString . fst
-- HasString' was False for a, so look at b
instance HasString b => HasStringTup False a b where
getStringTup _ = getString . snd
Итак, да. Это большой взлом, и, вероятно, он не стоит сложностей. Особенно неприятно писать обход дважды, как класс и как семейство типов. Чтобы обойти нехватку Haskell в обратном направлении, нам пришлось вручную смотреть в будущее. Ну, он все еще работает:
ghci> getString ('b', (Right "foo" :: Either String String, 3))
"foo"
ghci> getString ("foo", "bar")
"foo"
Oh! Кстати. Если вы хотите использовать свои собственные типы, а не выражать все как кортежи и Either
s, вы все равно можете генерировать общее программирование, преобразовывая их в общее представление и из него. На это накладные расходы, но это расходы, которые вы платите.
Что-то вроде этого:
Вы можете использовать шаблон Haskell писать такого рода случаях для вас. Это более или менее дизайн большинства общих библиотек программирования, таких как GHC.Generics. В основном они имеют более сложный тип представления.
Все, что вы сделали, это поднять это до уровня типа. Проблема заключается в том, что, если вы также не кодируете некоторую информацию в своих типах данных на уровне типа, это бесполезно (поскольку вы не можете достоверно поднять значения до уровня уровня). – Alec
Чтобы быть более понятным, способ использования вашего 'HasString' требует, чтобы у вас была снятая версия' A', 'B',' C' и 'D', но теперь есть возможность перейти от значения -> типа в целом. – Alec
[Не использовать] (http://stackoverflow.com/questions/39002342/adding-an-ord-instance-to-singleton-package-generated-naturals/39003151#39003151) [использовать] (http: // stackoverflow .com/questions/33270639/so-whats-the-point) [type] (http://stackoverflow.com/questions/37923470/is-there-any-connection-between-ab-and-ab-true/37925725 # 37925725) - [уровень] (http://stackoverflow.com/questions/38290067/idiomatic-boolean-equality-usage-singletons/38293546#38293546) [Boolean] (http://stackoverflow.com/questions/38209817/ check-if-one-type-level-list-contains-another/38221146 # 38221146) предикаты! Сколько раз! –