Скажем, у нас есть некоторый класс 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, представляет собой не кортежи (,)
, а несколько специальных конструкторов типов (:+:
, :*:
, M1
и т. д.), которым не хватает Unbox
экземпляров.
- Такие
Unbox
экземпляры могут быть добавлены для прямого использования Generic
- generics-eot имеет вариант
Generic
, основанный на кортежах, которые могут быть прямой заменой Generic_
здесь.
А во-вторых, у MVector
и Vector
довольно много методов. Чтобы не перечислять их все, можно было бы использовать DerivingVia
(или GeneralizedNewtypeDeriving
), однако они неприменимы, поскольку существует пара полиморфных монадических методов, предотвращающих приведения (например, basicUnsafeNew
). На данный момент самый простой способ абстрагироваться от этого — макрос CPP. На самом деле векторный пакет использует этот метод внутри себя, и его можно как-то повторно использовать. Я считаю, что для правильного решения этих проблем требуется глубокая переработка архитектуры Vector/MVector.
Gist (неполный, но компилируемый): https://gist.github.com/Lysxia/c7bdcbba548ee019bf6b3f1e388bd660 а>
person
Li-yao Xia
schedule
27.06.2019
Data.Vector.Storable
в самом пакетеvector
предоставляет векторы для любого сохраняемого типа, которые, конечно же, не упакованы. См. stackoverflow.com/questions/40176678/ для некоторых примечаний о том, когда они могут быть уместны. (Я думаю, что истерия фрагментации памяти будет необоснованной в большинстве приложений.) - person K. A. Buhr   schedule 27.06.2019Data.Vector.Storable
в то время, когда ссылка рекомендуетUnbox
, для удобства и во избежание TH, было бы это приемлемым компромиссом или это считалось бы плохим кодом? - person LogicChains   schedule 28.06.2019Storable
прекрасно справляется со своей задачей, хотя бы потому, что ее легче понять, но также и потому, что расположение памяти, возможно, часто является тем, что вам нужно в отношении локальности кеша. Если по какой-то причине он должен контролироваться GC,Primitive
будет следующей альтернативой. Хотя векторная библиотека ставитUnboxed
на видное место (даже говоря, что реализация неупакованных векторов для новых типов данных может быть очень простой). Я бы сказал, что это не так удобно для пользователя, как другие альтернативы. - person Li-yao Xia   schedule 28.06.2019