Можно ли получить Data.Vector.Unbox через общий вывод GHC?

Можно получить Storable с помощью универсального механизма получения GHC: http://hackage.haskell.org/package/derive-storablehttps://hackage.haskell.org/package/derive-storable-plugin для повышения производительности). Однако единственная библиотека, которую я могу найти для получения Data.Vector.Unbox, использует шаблон Haskell: http://hackage.haskell.org/package/vector-th-unbox. Это также требует от пользователя написания небольшого кода; это не совсем автомат.

Мой вопрос в том, может ли такая библиотека, как deriving-storable, существовать и для Unbox, или это невозможно из-за какого-то фундаментального отличия Unbox от Storable? Если второе, значит ли это, что также невозможно создать библиотеку, позволяющую автоматически получать Unbox для любого типа Storable, так как я не смог найти такую ​​библиотеку.

Я спрашиваю, потому что в идеале я хотел бы избежать шаблона Haskell и ручных аннотаций, необходимых для использования vector-th-unbox.


person LogicChains    schedule 27.06.2019    source источник
comment
Обратите внимание, что Data.Vector.Storable в самом пакете vector предоставляет векторы для любого сохраняемого типа, которые, конечно же, не упакованы. См. stackoverflow.com/questions/40176678/ для некоторых примечаний о том, когда они могут быть уместны. (Я думаю, что истерия фрагментации памяти будет необоснованной в большинстве приложений.)   -  person K. A. Buhr    schedule 27.06.2019
comment
@K.A.Buhr Если бы я просто использовал Data.Vector.Storable в то время, когда ссылка рекомендует Unbox, для удобства и во избежание TH, было бы это приемлемым компромиссом или это считалось бы плохим кодом?   -  person LogicChains    schedule 28.06.2019
comment
Я думаю, что большую часть времени Storable прекрасно справляется со своей задачей, хотя бы потому, что ее легче понять, но также и потому, что расположение памяти, возможно, часто является тем, что вам нужно в отношении локальности кеша. Если по какой-то причине он должен контролироваться GC, Primitive будет следующей альтернативой. Хотя векторная библиотека ставит Unboxed на видное место (даже говоря, что реализация неупакованных векторов для новых типов данных может быть очень простой). Я бы сказал, что это не так удобно для пользователя, как другие альтернативы.   -  person Li-yao Xia    schedule 28.06.2019


Ответы (1)


Скажем, у нас есть некоторый класс 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
comment
Я ожидаю, что большим камнем преткновения будет тот факт, что существуют семейства данных, которым нужны экземпляры. Но, возможно, я что-то упускаю. - person dfeuer; 28.06.2019