Я хочу создать специальный интеллектуальный конструктор для Data.Map с определенным ограничением на типы отношений пары ключ/значение. Это ограничение, которое я пытался выразить:
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, DataKinds #-}
data Field = Speed | Name | ID
data Value = VFloat Float | VString ByteString | VInt Int
class Pair f b | f -> b where
toPair :: f -> b -> (f, b)
toPair = (,)
instance Pair Speed (VFloat f)
instance Pair ID (VInt i)
для каждого поля, существует только один тип значения, с которым он должен быть связан. В моем случае для поля Speed
не имеет смысла отображать a ByteString
. Поле A Speed
должно однозначно отображать a Float
Но я получаю следующую ошибку типа:
Kind mis-match
The first argument of `Pair' should have kind `*',
but `VInt' has kind `Value'
In the instance declaration for `Pair Speed (VFloat f)'
с помощью -XKindSignatures
:
class Pair (f :: Field) (b :: Value) | f -> b where
toPair :: f -> b -> (f, b)
toPair = (,)
Kind mis-match
Expected kind `OpenKind', but `f' has kind `Field'
In the type `f -> b -> (f, b)'
In the class declaration for `Pair'
Я понимаю, почему я получаю неправильное совпадение вида, но как я могу выразить это ограничение, так что это ошибка времени компиляции, чтобы использовать toPair
для несоответствия Field
и Value
.
Мне предложили #haskell использовать GADT
, но я еще не смог это выяснить.
Цель состоит в том, чтобы иметь возможность писать
type Record = Map Field Value
mkRecord :: [Field] -> [Value] -> Record
mkRecord = (fromList .) . zipWith toPair
чтобы я мог сделать безопасным Map
, где соблюдаются инварианты ключа/значения.
Итак, это должно проверять тип
test1 = mkRecord [Speed, ID] [VFloat 1.0, VInt 2]
но это должна быть ошибка времени компиляции
test2 = mkRecord [Speed] [VInt 1]
EDIT:
Я начинаю думать, что мои конкретные требования невозможны. Используя мой оригинальный пример
data Foo = FooInt | FooFloat
data Bar = BarInt Int | BarFloat Float
Чтобы обеспечить ограничение на Foo
и Bar
, должен быть какой-то способ разграничения между FooInt
и FooFloat
на уровне типа и аналогичным образом для Bar
. Таким образом, мне вместо этого нужны два GADT
data Foo :: * -> * where
FooInt :: Foo Int
FooFloat :: Foo Float
data Bar :: * -> * where
BarInt :: Int -> Bar Int
BarFloat :: Float -> Bar Float
теперь я могу написать экземпляр для Pair
, который выполняется только тогда, когда теги Foo
и Bar
отмечены тем же типом
instance Pair (Foo a) (Bar a)
и у меня есть свойства, которые я хочу
test1 = toPair FooInt (BarInt 1) -- type-checks
test2 = toPair FooInt (BarFloat 1) -- no instance for Pair (Foo Int) (Bar Float)
но я теряю возможность писать xs = [FooInt, FooFloat]
, потому что для этого потребуется гетерогенный список. Более того, если я попытаюсь сделать синоним Map
type FooBar = Map (Foo ?) (Bar ?)
, я застрял с Map
только типов Int
или только типов Float
, чего я не хочу. Это выглядит довольно безнадежным, если только у вас нет мощного колдовства типа типа, о котором я не знаю.