Скажем, у нас есть некоторый класс Generic_
для преобразования между нашими собственными типами и некоторое единообразное представление, которое имеет экземпляр Unbox
(что составляет как экземпляры MVector
, так и Vector
для вариантов Unboxed
) :
class Generic_ a where
type Rep_ (a :: Type) :: Type
to_ :: a -> Rep_ a
from_ :: Rep_ a -> a
Затем мы можем использовать это для получения общих реализаций методов MVector
/ Vector
:
-- (auxiliary definitions of CMV and uncoercemv at the end of this block)
-- vector imports (see gist at the end for a compilable sample)
import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Unboxed.Mutable as UM
import Data.Vector.Generic.Mutable.Base (MVector(..))
-- MVector
gbasicLength :: forall a s. CMV s a => UM.MVector s a -> Int
gbasicLength = basicLength @UM.MVector @(Rep_ a) @s . coerce
gbasicUnsafeSlice :: forall a s. CMV s a => Int -> Int -> UM.MVector s a -> UM.MVector s a
gbasicUnsafeSlice i j = uncoercemv . basicUnsafeSlice @UM.MVector @(Rep_ a) @s i j . coerce
-- etc.
-- idem Vector
-- This constraints holds when the UM.MVector data instance of a is
-- representationally equivalent to the data instance of its generic
-- representation (Rep_ a).
type CMV s a = (Coercible (UM.MVector s a) (UM.MVector s (Rep_ a)), MVector UM.MVector (Rep_ a))
-- Sadly coerce doesn't seem to want to solve this correctly so we use
-- unsafeCoerce as a workaround.
uncoercemv :: CMV s a => UM.MVector s (Rep_ a) -> UM.MVector s a
uncoercemv = unsafeCoerce
Теперь, если у нас есть общий тип
data MyType = MyCons Int Bool ()
Мы можем определить общий экземпляр с его изоморфизмом в кортеж
instance Generic_ MyType where
type Rep_ MyType = (Int, Bool, ())
to_ (MyCons a b c) = (a, b, c)
from_ (a, b, c) = MyCons a b c
И оттуда, есть полностью общий рецепт, чтобы получить его экземпляр Unbox
, если у вас есть YourType
вместо его собственного экземпляра Generic_
, вы можете взять это и буквально заменить MyType
на YourType
.
newtype instance UM.MVector s MyType
= MVMyType { unMVMyType :: UM.MVector s (Rep_ MyType) }
instance MVector UM.MVector MyType where
basicLength = gbasicLength
basicUnsafeSlice = gbasicUnsafeSlice
-- etc.
-- idem (Vector U.Vector MyType)
-- MVector U.Vector & Vector UM.MVector = Unbox
instance Unbox MyType
Теоретически все эти шаблоны можно автоматизировать с помощью внутренних языковых функций (в отличие от TemplateHaskell или CPP). Но существуют различные проблемы, которые мешают в текущем положении вещей.
Во-первых, Generic_
по существу Generic
из GHC.Generics
. Тем не менее, единообразное представление, полученное с помощью GHC, не в терминах кортежей (,)
, а в терминах конструкторов типа ad-hoc (:+:
, :*:
, M1
и т. Д.), В которых отсутствует Unbox
экземпляры.
- Такие
Unbox
экземпляры могут быть добавлены для использования Generic
напрямую
- generics-eot имеет вариант
Generic
, основанный на кортежах, которые могут быть прямой заменой Generic_
здесь.
И во-вторых, MVector
и Vector
имеют довольно много методов. Чтобы избежать необходимости перечислять их все, можно рассчитывать на использование DerivingVia
(или GeneralizedNewtypeDeriving
), однако они неприменимы, поскольку существует пара полиморфных монадических методов, которые предотвращают принуждения (например, basicUnsafeNew
). На данный момент самый простой способ абстрагировать это макрос CPP. Фактически, векторный пакет использует эту технику для внутреннего использования, и он может быть как-то повторно использован. Я считаю, что правильное решение этих проблем требует глубокого изменения архитектуры Vector / MVector.
Суть (не полная, но компилируемая): https://gist.github.com/Lysxia/c7bdcbba548ee019bf6b3f1e388bd660