Список типов типов, ограниченных классом типов
Я пытаюсь кодировать список элементов, которые имеют типы, ограниченные экземплярами какого-либо типа:
{-# LANGUAGE RankNTypes, TypeSynonymInstances, LiberalTypeSynonyms #-}
module Test where
class Someable a where
some :: a -> String
data Some = Some String
type SomeGroup = forall a. Someable a => [a]
instance Someable Some where
some (Some v) = v
instance Someable SomeGroup where
some (x:xs) = (some x) ++ ", " ++ (some xs)
main = do
putStrLn $ show.some [Some "A", [Some "B", Some "C"]]
Но компиляция не выполняется с ошибкой:
Test.hs:14:10:
Illegal polymorphic or qualified type: SomeGroup
In the instance declaration for `Someable SomeGroup'
Кажется, мне даже не удалось определить экземпляр для синонима типа...
Я знаю разнородные коллекции wiki, но хочу знать, почему именно мой подход не работает - мне кажется естественным для определения типа путем ограничения коллекции только содержать элементы с типами, которые являются экземплярами некоторого типа.
Ответы
Ответ 1
Если я правильно понимаю вещи, должен быть тип данных, обертывающий экзистенциальный, чтобы там было место для хранения словаря типа вместе с каждым элемент.
Добавление некоторых оболочек делает работу:
{-# LANGUAGE ExistentialQuantification, TypeSynonymInstances #-}
module Test where
class Someable a where
some :: a -> String
data Some = Some String
data SomeWrapper = forall a. Someable a => SomeWrapper a
type SomeGroup = [SomeWrapper]
instance Someable Some where
some (Some v) = v
instance Someable SomeWrapper where
some (SomeWrapper v) = some v
instance Someable SomeGroup where
some (x:xs) = (some x) ++ ", " ++ (some xs)
main = do
putStrLn $ some [SomeWrapper (Some "A"), SomeWrapper [SomeWrapper (Some "B"), SomeWrapper (Some "C")]]
Конечно, это немного уродливо. К сожалению, я не знаю лучшего способа.
Ответ 2
Вы также можете приготовить что-то с помощью GADT. В некоторых случаях это может быть немного короче, и в нем указывается, какие словари типа доступны после сопоставления с образцом.
Вот небольшой вариант вашего примера:
{-# LANGUAGE GADTs #-}
class Someable a where
some :: a -> String
instance Someable Int where
some n = show n
data SomeString = SomeString String
instance Someable SomeString where
some (SomeString s) = s
data SomeGroup where
Nil :: SomeGroup
Cons :: Someable a => a -> SomeGroup -> SomeGroup
instance Someable SomeGroup where
some Nil = ""
some (Cons x Nil) = some x
some (Cons x xs) = some x ++ ", " ++ some xs
list = Cons (3::Int) (Cons (SomeString "abc") (Cons (42::Int) Nil))
main = print . some $ list
Несколько незначительных заметок:
- Вы забыли базовый регистр для рекурсии:)
-
putStrLn . show
совпадает с print
.
- Вы должны указать тип чисел явно как
Int
, потому что целочисленные литералы обрабатываются специально, то есть 42
переводится на fromInteger 42
типа Num a => a
. Немного неуклюже при построении списка напрямую, но большую часть времени он будет работать более плавно.
- Вы можете, конечно, определить свой собственный синтаксический сахар для элементов
Cons
-ing в списке, но синтаксис никогда не будет выглядеть так хорошо, как встроенный синтаксис Haskell!
И главное, что вы теряете использование всех стандартных функций списка. Обычно я использую такое решение, когда мои потребности в обработке списков крайне ограничены; С другой стороны, складка написана достаточно быстро... Ваш пробег будет меняться, но я не знаю, на что вы действительно хотите использовать этот список.