Список типов типов, ограниченных классом типов

Я пытаюсь кодировать список элементов, которые имеют типы, ограниченные экземплярами какого-либо типа:

{-# 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!

И главное, что вы теряете использование всех стандартных функций списка. Обычно я использую такое решение, когда мои потребности в обработке списков крайне ограничены; С другой стороны, складка написана достаточно быстро... Ваш пробег будет меняться, но я не знаю, на что вы действительно хотите использовать этот список.