Skip to content

Add iso-deriving for Unboxed instances - #378

Merged
Shimuuar merged 6 commits into
haskell:masterfrom
Shimuuar:iso-deriving
May 26, 2021
Merged

Add iso-deriving for Unboxed instances#378
Shimuuar merged 6 commits into
haskell:masterfrom
Shimuuar:iso-deriving

Conversation

@Shimuuar

Copy link
Copy Markdown
Contributor

Another take on use of DerivingVia for defining Unbox instances. This should largely subsume TH-based deriving from th-vector-unbox. Verbosity is about same and there's no TH which is frequently breaks with each GHC release

P.S. It's still too verbose. I think use of data family was a mistake. They couldn't be derived and have to be defined for each type. On top of it we only have handful of possible representations. Type family + newtype wrapper for injectivity would've worked out much better.

@lehinslehins left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love it!

It is similar to what I was suggesting here: #315 (comment) but better

Comment on lines +259 to +284
class Isomorphic a b where
-- | Convert value into it representation in unboxed vector.
toURepr :: a -> b
-- | Convert value representation in unboxed vector back to value.
fromURepr :: b -> a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about adding a default implementation that uses Coercible?

Suggested change
classIsomorphicabwhere
--| Convert value into it representation in unboxed vector.
toURepr::a->b
--| Convert value representation in unboxed vector back to value.
fromURepr::b->a
classIsomorphicabwhere
--| Convert value into it representation in unboxed vector.
toURepr::a->b
default toURepr ::Coercibleab=>a->b
toURepr = coerce
--| Convert value representation in unboxed vector back to value.
fromURepr::b->a
default fromURepr ::Coercibleba=>b->a
fromURepr = coerce

We do already have an ability to use GND for newtypes, but I don't think coercible would hurt here.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder are there cases when one could coerce but not GND? I need to think about this. Obvious downside it prevents other possible implementations

Also most important use case should be converting between product types and tuples. I'll try to cook up something using generics to see whether its possible

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started writing pessimistic reply about how we can't have good default instance... And then come up with interesting idea. We should coerce between generic representations and not between values themselves. As and illustration:

λ> data Foo = Foo Int Char deriving (Show,Generic)
λ> to . coerce . from $ Foo 1 'c' :: (Int,Char)
(1,'c')
λ> to . coerce . from $ Foo 1 'c' :: (Sum Int,Char)
(Sum {getSum = 1},'c')
λ> to . coerce . from $ (Sum (1::Int), 'c') :: Foo
Foo 1 'c'

It's even able to coerce between fields! This is definition I propose (sans INLINE):

classIsoUnboxabwhere--| Convert value into it representation in unboxed vector.toURepr::a->bdefault toURepr :: (Generica, Genericb, Coercible (Repa()) (Repb())) =>a->b
toURepr = to . idU . coerce . idU . from
--| Convert value representation in unboxed vector back to value.fromURepr::b->adefault fromURepr :: (Generica, Genericb, Coercible (Repb()) (Repa())) =>b->a
fromURepr = to . idU . coerce . idU . from
idU::f()->f()
idU =id

Only question is whether GHC guarantees that data types with same shape will have same shape. Think (a :*: b) :*: c vs a :*: (b :*: c). I think they do but documentation is silent about this.

Comment threadvector/src/Data/Vector/Unboxed/Base.hs Outdated
@Shimuuar

Copy link
Copy Markdown
ContributorAuthor

I added generic-based instance for IsoUnbox. It looks very nice. Only question is performance. I think it should be fine since we don't do anything except calling to and from

This should largely subsume TH-based deriving from th-vector-unbox. Verbosity is
about same and there's no TH which is frequently breaks with each GHC release
It works by coercing between Generic representations of data types.
Before we used prefixes that in scope in given module
Namely that optimizer is able to fully eliminate generics
@Shimuuar

Copy link
Copy Markdown
ContributorAuthor

I've added inspection tests for iso-deriving. GHC is able to opmitise waya both generics and even data type constructors. Syntax is bearable although a bit too verbose:

dataFooa=FooIntaderiving (Show,Generic)
instanceVU.IsoUnbox (Fooa) (Int,a) wherenewtypeinstanceVU.MVectors (Fooa) =MV_Int (VU.MVectors (Int, a))
newtypeinstanceVU.Vector (Fooa) =V_Int (VU.Vector (Int, a))
instanceVU.Unboxa=>VU.Unbox (Fooa)
derivingvia (Fooa `VU.As` (Int, a)) instanceVU.Unboxa=>VGM.MVectorVU.MVector (Fooa)
derivingvia (Fooa `VU.As` (Int, a)) instanceVU.Unboxa=>VG.VectorVU.Vector (Fooa)

Core is quite nice too.

@Shimuuar
Shimuuar merged commit bea4cf4 into haskell:masterMay 26, 2021
@Shimuuar
Shimuuar deleted the iso-deriving branch May 26, 2021 13:20
netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this pull request Aug 19, 2022
# Changes in version 0.13.0.0
* `mkType` from `Data.Vector.Generic` is deprecated in favor of
`Data.Data.mkNoRepType`
* The role signatures on several `Vector` types were too permissive, so they
have been tightened up:
* The role signature for `Data.Vector.Mutable.MVector` is now
`type role MVector nominal representational` (previously, both arguments
were `phantom`). [#224](haskell/vector#224)
* The role signature for `Data.Vector.Primitive.Vector` is now
`type role Vector nominal` (previously, it was `phantom`).
The role signature for `Data.Vector.Primitive.Mutable.MVector` is now
`type role MVector nominal nominal` (previously, both arguments were
`phantom`). [#316](haskell/vector#316)
* The role signature for `Data.Vector.Storable.Vector` is now
`type role Vector nominal` (previous, it was `phantom`), and the signature
for `Data.Vector.Storable.Mutable.MVector` is now
`type role MVector nominal nominal` (previous, both arguments were
`phantom`). [#235](haskell/vector#235)
We pick `nominal` for the role of the last argument instead of
`representational` since the internal structure of a `Storable` vector is
determined by the `Storable` instance of the element type, and it is not
guaranteed that the `Storable` instances between two representationally
equal types will preserve this internal structure. One consequence of this
choice is that it is no longer possible to `coerce` between
`Storable.Vector a` and `Storable.Vector b` if `a` and `b` are nominally
distinct but representationally equal types. We now provide
`unsafeCoerce{M}Vector` and `unsafeCast` functions to allow this (the onus
is on the user to ensure that no `Storable` invariants are broken when
using these functions).
* Methods of type classes `Data.Vector.Generic.Mutable.MVector` and
`Data.Vector.Generic.Vector` use concrete monads (`ST`, etc) istead of being
polymorphic (`PrimMonad`, etc). [#335](haskell/vector#335).
This makes it possible to derive `Unbox` with:
* `GeneralizedNewtypeDeriving`
* via `UnboxViaPrim` and `Prim` instance
* via `As` and `IsoUnbox` instance: [#378](haskell/vector#378)
* Add `MonadFix` instance for boxed vectors: [#312](haskell/vector#312)
* Re-export `PrimMonad` and `RealWorld` from mutable vectors:
[#320](haskell/vector#320)
* Add `maximumOn` and `minimumOn`: [#356](haskell/vector#356)
* The functions `scanl1`, `scanl1'`, `scanr1`, and `scanr1'` for immutable
vectors are now defined when given empty vectors as arguments,
in which case they return empty vectors. This new behavior is consistent
with the one of the corresponding functions in `Data.List`.
Prior to this change, applying an empty vector to any of those functions
resulted in an error. This change was introduced in:
[#382](haskell/vector#382)
* Change allocation strategy for `unfoldrN`: [#387](haskell/vector#387)
* Remove `CPP` driven error reporting in favor of `HasCallStack`:
[#397](haskell/vector#397)
* Remove redundant `Storable` constraints on to/from `ForeignPtr` conversions:
[#394](haskell/vector#394)
* Add `unsafeCast` to `Primitive` vectors: [#401](haskell/vector#401)
* Make `(!?)` operator strict: [#402](haskell/vector#402)
* Add `readMaybe`: [#425](haskell/vector#425)
* Add `groupBy` and `group` for `Data.Vector.Generic` and the specialized
version in `Data.Vector`, `Data.Vector.Unboxed`, `Data.Vector.Storable` and
`Data.Vector.Primitive`. [#427](haskell/vector#427)
* Add `toArraySlice` and `unsafeFromArraySlice` functions for conversion to and
from the underlying boxed `Array`: [#434](haskell/vector#434)
msk pushed a commit to msk/pkgsrc that referenced this pull request May 11, 2026
# Changes in version 0.13.0.0
* `mkType` from `Data.Vector.Generic` is deprecated in favor of
`Data.Data.mkNoRepType`
* The role signatures on several `Vector` types were too permissive, so they
have been tightened up:
* The role signature for `Data.Vector.Mutable.MVector` is now
`type role MVector nominal representational` (previously, both arguments
were `phantom`). [#224](haskell/vector#224)
* The role signature for `Data.Vector.Primitive.Vector` is now
`type role Vector nominal` (previously, it was `phantom`).
The role signature for `Data.Vector.Primitive.Mutable.MVector` is now
`type role MVector nominal nominal` (previously, both arguments were
`phantom`). [#316](haskell/vector#316)
* The role signature for `Data.Vector.Storable.Vector` is now
`type role Vector nominal` (previous, it was `phantom`), and the signature
for `Data.Vector.Storable.Mutable.MVector` is now
`type role MVector nominal nominal` (previous, both arguments were
`phantom`). [#235](haskell/vector#235)
We pick `nominal` for the role of the last argument instead of
`representational` since the internal structure of a `Storable` vector is
determined by the `Storable` instance of the element type, and it is not
guaranteed that the `Storable` instances between two representationally
equal types will preserve this internal structure. One consequence of this
choice is that it is no longer possible to `coerce` between
`Storable.Vector a` and `Storable.Vector b` if `a` and `b` are nominally
distinct but representationally equal types. We now provide
`unsafeCoerce{M}Vector` and `unsafeCast` functions to allow this (the onus
is on the user to ensure that no `Storable` invariants are broken when
using these functions).
* Methods of type classes `Data.Vector.Generic.Mutable.MVector` and
`Data.Vector.Generic.Vector` use concrete monads (`ST`, etc) istead of being
polymorphic (`PrimMonad`, etc). [#335](haskell/vector#335).
This makes it possible to derive `Unbox` with:
* `GeneralizedNewtypeDeriving`
* via `UnboxViaPrim` and `Prim` instance
* via `As` and `IsoUnbox` instance: [#378](haskell/vector#378)
* Add `MonadFix` instance for boxed vectors: [#312](haskell/vector#312)
* Re-export `PrimMonad` and `RealWorld` from mutable vectors:
[#320](haskell/vector#320)
* Add `maximumOn` and `minimumOn`: [#356](haskell/vector#356)
* The functions `scanl1`, `scanl1'`, `scanr1`, and `scanr1'` for immutable
vectors are now defined when given empty vectors as arguments,
in which case they return empty vectors. This new behavior is consistent
with the one of the corresponding functions in `Data.List`.
Prior to this change, applying an empty vector to any of those functions
resulted in an error. This change was introduced in:
[#382](haskell/vector#382)
* Change allocation strategy for `unfoldrN`: [#387](haskell/vector#387)
* Remove `CPP` driven error reporting in favor of `HasCallStack`:
[#397](haskell/vector#397)
* Remove redundant `Storable` constraints on to/from `ForeignPtr` conversions:
[#394](haskell/vector#394)
* Add `unsafeCast` to `Primitive` vectors: [#401](haskell/vector#401)
* Make `(!?)` operator strict: [#402](haskell/vector#402)
* Add `readMaybe`: [#425](haskell/vector#425)
* Add `groupBy` and `group` for `Data.Vector.Generic` and the specialized
version in `Data.Vector`, `Data.Vector.Unboxed`, `Data.Vector.Storable` and
`Data.Vector.Primitive`. [#427](haskell/vector#427)
* Add `toArraySlice` and `unsafeFromArraySlice` functions for conversion to and
from the underlying boxed `Array`: [#434](haskell/vector#434)
jperkin pushed a commit to TritonDataCenter/pkgsrc that referenced this pull request May 14, 2026
# Changes in version 0.13.0.0
* `mkType` from `Data.Vector.Generic` is deprecated in favor of
`Data.Data.mkNoRepType`
* The role signatures on several `Vector` types were too permissive, so they
have been tightened up:
* The role signature for `Data.Vector.Mutable.MVector` is now
`type role MVector nominal representational` (previously, both arguments
were `phantom`). [#224](haskell/vector#224)
* The role signature for `Data.Vector.Primitive.Vector` is now
`type role Vector nominal` (previously, it was `phantom`).
The role signature for `Data.Vector.Primitive.Mutable.MVector` is now
`type role MVector nominal nominal` (previously, both arguments were
`phantom`). [#316](haskell/vector#316)
* The role signature for `Data.Vector.Storable.Vector` is now
`type role Vector nominal` (previous, it was `phantom`), and the signature
for `Data.Vector.Storable.Mutable.MVector` is now
`type role MVector nominal nominal` (previous, both arguments were
`phantom`). [#235](haskell/vector#235)
We pick `nominal` for the role of the last argument instead of
`representational` since the internal structure of a `Storable` vector is
determined by the `Storable` instance of the element type, and it is not
guaranteed that the `Storable` instances between two representationally
equal types will preserve this internal structure. One consequence of this
choice is that it is no longer possible to `coerce` between
`Storable.Vector a` and `Storable.Vector b` if `a` and `b` are nominally
distinct but representationally equal types. We now provide
`unsafeCoerce{M}Vector` and `unsafeCast` functions to allow this (the onus
is on the user to ensure that no `Storable` invariants are broken when
using these functions).
* Methods of type classes `Data.Vector.Generic.Mutable.MVector` and
`Data.Vector.Generic.Vector` use concrete monads (`ST`, etc) istead of being
polymorphic (`PrimMonad`, etc). [#335](haskell/vector#335).
This makes it possible to derive `Unbox` with:
* `GeneralizedNewtypeDeriving`
* via `UnboxViaPrim` and `Prim` instance
* via `As` and `IsoUnbox` instance: [#378](haskell/vector#378)
* Add `MonadFix` instance for boxed vectors: [#312](haskell/vector#312)
* Re-export `PrimMonad` and `RealWorld` from mutable vectors:
[#320](haskell/vector#320)
* Add `maximumOn` and `minimumOn`: [#356](haskell/vector#356)
* The functions `scanl1`, `scanl1'`, `scanr1`, and `scanr1'` for immutable
vectors are now defined when given empty vectors as arguments,
in which case they return empty vectors. This new behavior is consistent
with the one of the corresponding functions in `Data.List`.
Prior to this change, applying an empty vector to any of those functions
resulted in an error. This change was introduced in:
[#382](haskell/vector#382)
* Change allocation strategy for `unfoldrN`: [#387](haskell/vector#387)
* Remove `CPP` driven error reporting in favor of `HasCallStack`:
[#397](haskell/vector#397)
* Remove redundant `Storable` constraints on to/from `ForeignPtr` conversions:
[#394](haskell/vector#394)
* Add `unsafeCast` to `Primitive` vectors: [#401](haskell/vector#401)
* Make `(!?)` operator strict: [#402](haskell/vector#402)
* Add `readMaybe`: [#425](haskell/vector#425)
* Add `groupBy` and `group` for `Data.Vector.Generic` and the specialized
version in `Data.Vector`, `Data.Vector.Unboxed`, `Data.Vector.Storable` and
`Data.Vector.Primitive`. [#427](haskell/vector#427)
* Add `toArraySlice` and `unsafeFromArraySlice` functions for conversion to and
from the underlying boxed `Array`: [#434](haskell/vector#434)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Shimuuar@klapaucius@lehins