haskell / primitive

This package provides various primitive memory-related operations.
Other
114 stars 58 forks source link

Consider making distinct types for pinned and unpinned arrays #405

Open Rotaerk opened 9 months ago

Rotaerk commented 9 months ago

There are operations that can't support unpinned arrays, but currently the types don't actually prevent such arrays from being provided. There are constructive operations that should be able to produce either pinned or unpinned arrays, but currently only exist for unpinned arrays, such as primArrayFromList (there's no pinnedPrimArrayFromList).

One potential option would be to make a type parameter that is a data kind, indicating Pinned or Unpinned. Then operations that apply to both can just leave that type parameter unspecified...

andrewthad commented 9 months ago

I don't see a way to do this without breaking the API, and API stability is important for this library. I've thought before about what you suggest. Something like this (ignoring element polymorphism):

data Pinnedness = Pinned | Unpinned
data ByteArray :: Pinnedness -> Type
indexByteArray :: ByteArray p -> Int -> Word8
pin :: ByteArray p -> ByteArray 'Pinned -- copies an unpinned array or aliases a pinned one, detecting with `isByteArrayPinned#`
byteArrayContents :: ByteArray 'Pinned -> Ptr Word8
newByteArray :: Int -> ST s (MutableByteArray p s)

This is a fine API, but I don't see how to introduce this without breaking a bunch of programs that already use this library.

konsumlamm commented 9 months ago

Perhaps this would best fit into a separate library that provides thin wrappers over Data.Primitive.ByteArray and Data.Primitive.PrimArray with type parameters for "pinnedness". Such a library should be very easy to maintain (in fact, I'd be willing to do so).

konsumlamm commented 9 months ago

Another reason against making this the default is that it disallows mixing arrays with different pinnedness, e.g.

arr = if makePinned then newPinnedByteArray 42 else newByteArray 42

This could be solved using existentials, but usually when using primitive, you care a lot about performance and existentials require additional boxing.

In most cases, you're only using unpinned arrays anyway (at least I never had the need to use pinned arrays), so this would probably be overkill.

However, I'd be interested to know if people feel like such a type would actually be useful to them.

konsumlamm commented 9 months ago

I created a topic on the Haskell Discourse about this: https://discourse.haskell.org/t/distinguishing-pinned-and-unpinned-arrays-via-a-type-parameter/8443.

konsumlamm commented 8 months ago

The Discourse discussion came to the conclusion that one can just use ByteString if one wants a byte array that's guaranteed to be pinned.

andrewthad commented 8 months ago

I think you intended to write "pinned" but wrote "unpinned" instead. The arrays from ByteString are guaranteed to be pinned.