Подпись типа
dispatch :: (Storable a) => AList -> SV.Vector a
гласит «дай мне AList
, и я дам тебе SV.Vector a
для любого a
, сколько хочешь, так долго»как это экземпляр Storable
".Это не правильно!Для любого заданного значения вы можете указать только один a
, и вы выберите его, а не код вызова.Проблема может быть легче увидеть, если вы явно добавите квантификатор:
dispatch :: forall a. (Storable a) => AList -> SV.Vector a
Что вы действительно хотите сказать, это «дайте мне AList
, и я дам вам SV.Vector a
для некоторые a
, которые я выберу, но я обещаю, что это будет экземпляр Storable
".Для этого нам понадобится экзистенциальный тип:
data SomeSVVector = forall a. (Storable a) => SomeSVVector (SV.Vector a)
dispatch :: AList -> SomeSVVector
dispatch (I x) = SomeSVVector x
dispatch (S x) = SomeSVVector x
(для этого вам понадобится {-# LANGUAGE ExistentialQuantification #-}
.)
Это дает SomeVVector
тип:
SomeVVector :: (Storable a) => SV.Vector a -> SomeVVector
Затем можно вывести SV.Vector
из результата dispatch
с помощью case dispatch x of SomeSVVector vec -> ...
.Тем не менее, это, вероятно, не так уж и полезно: поскольку экзистенциал может содержать вектор с элементами любого экземпляра Storable
, операции only , которые вы сможете выполнитьна данных внутри те, которые предлагаются Storable
класса.Если вы хотите что-то, что пользовательский код может анализировать и «отправлять» по типу, вам понадобится теговое объединение - это именно то, что ваш AList
тип уже имеет.
Если вы действительно хотите перейтивниз по экзистенциальному маршруту, тогда я бы предложил определить свой собственный класс типов как подкласс Storable
, который содержит все операции, которые вы, возможно, захотите выполнить над значениями внутри.По крайней мере, вы, вероятно, захотите добавить Integral
к ограничению SomeSVVector
.
Как вы упоминали в комментариях, если вы не возражаете против AList
изInt32
с и AList
из CChar
с разными типами, вы можете использовать GADT:
data AList a where
I :: {-# UNPACK #-} !(SV.Vector Int32) -> AList Int32
S :: {-# UNPACK #-} !(SV.Vector CChar) -> AList CChar
dispatch :: AList a -> SV.Vector a
dispatch (I x) = x
dispatch (S x) = x
Здесь AList
по сути является версией SV.Vector
, которая работает только наопределенные типы элементов.Однако dispatch
на самом деле не так уж полезен - нет реального способа «вернуться» к AList
после того, как вы вынули его с помощью dispatch
, потому что предложения по сопоставлению с образцом GADT для унификации типов работают только с явное сопоставление с образцом;Вы не можете сказать, что результатом dispatch
является SV.Vector Int32
или SV.Vector CChar
.Что-то вроде
dispatch :: (SV.Vector Int32 -> r) -> (SV.Vector CChar -> r) -> AList a -> r
будет работать, но это эквивалентно (и более неудобно, чем) сопоставлению с образцом в вашей оригинальной версии с тегами union.
Не думаю, что есть разумный способопределить полезное dispatch
;Я бы предложил использовать явное сопоставление с образцом в исходном определении AList
.