Можно ли получить Data.Vector.Unbox с помощью общего производного GHC? - PullRequest
3 голосов
/ 27 июня 2019

Можно получить 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.

1 Ответ

3 голосов
/ 27 июня 2019

Скажем, у нас есть некоторый класс 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

...