Skip to content

Add a constant time 'freeze' function to mutable arrays and vectors#205

Merged
utdemir merged 3 commits intomasterfrom
ud/array-to-vector
Sep 24, 2020
Merged

Add a constant time 'freeze' function to mutable arrays and vectors#205
utdemir merged 3 commits intomasterfrom
ud/array-to-vector

Conversation

@utdemir
Copy link
Copy Markdown
Contributor

@utdemir utdemir commented Sep 23, 2020

As suggested in #180 (comment), this PR adds a constant time freeze method to unlifted and lifted mutable arrays and mutable vectors.

As usual, it is implemented with unsafe primitives in unlifted arrays, and the rest of the collections make use of that implementation. I hope I got the MVector's parameters right, as far as I could understand its first parameter is the starting index and the second index is the length. Because of that starting index, we can not have a constant time thaw operation (which would be unsafe anyway).

I am not entirely sure about the name freeze. I was also thinking of names like toVector or toImmutableVector, but they are likelier to be confused with either of our Vector's. Let me know if you prefer something else.

Some simple tests pass, and I hope I'm not violating any invariant of the vector package.

Relevant: #185

@utdemir utdemir requested a review from aspiwack September 23, 2020 10:47
Copy link
Copy Markdown
Member

@aspiwack aspiwack left a comment

Choose a reason for hiding this comment

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

You say that you have freezing primitives to mutable vectors (freeze would be a rather bad name for them, maybe share?) but I don't see any. It's fine, we don't need them yet, I can't say that I have a use case for such, so it can wait after the first release. You can make an issue if you think they may be important.

Let's not worry about thaw for the time being (I don't agree with your assessment that it can't be done in O(1) though, but we can discuss it elsewhere).

I have a slight change in interface in mind below, let's consider it.

(# _, ret #) -> ret : go (i Prelude.+ 1) len arr

-- | /O(1)/ Convert an 'Array#' to an immutable 'Vector' (from 'vector' package).
freeze :: forall a. Array# a #-> Ur (Vector.Vector a)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it would have been natural, here, to convert to an unlifted immutable array. And post-convert to Vector in the unlifted case instead.

See unsafeFreezeArray#

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

MVector's constructor is as follows:

data MVector s a = MVector {-# UNPACK #-} !Int
                           {-# UNPACK #-} !Int
                           {-# UNPACK #-} !(MutableArray s a)
        deriving ( Typeable )

That MutableArray is from the primitive package:

-- | Mutable boxed arrays associated with a primitive state token.
data MutableArray s a = MutableArray
  { marray# :: MutableArray# s a }
  deriving ( Typeable )

So, that forces us to go through MutableArray. The Vector constructor work as you mentioned:

data Vector a = Vector {-# UNPACK #-} !Int
                       {-# UNPACK #-} !Int
                       {-# UNPACK #-} !(Array a)
        deriving ( Typeable )

And it'd be ideal to use it, however that constructor is not exported.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is quite unfortunate indeed. It's the one type in vector which isn't exported.

Let me formulate a plan: do freeze to Array# here. To implement the conversion Array# -> Vector, use unsafeThaw# together with your present implementation based on MVector. But move that code to the lifted array module.

Then, next week, let us propose some kind of PR to the vector library so that we can use the Vector constructor directly in the future.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I now understand why you are asking for this, and I agree. Freezing an unlifted mutable array to an unlifted immutable array makes sense. I did the change.

Which caused unlifted arrays freeze function to have a different signature:

freeze :: (GHC.Array# a -> b) -> Array# a #-> Ur b

Because I couldn't put an unlifted type inside a Ur. We are using the same pattern on unArray# function too, so I think it is fine.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, it's safe. Ur a is isomorphic to forall k. (a -> k) #-> k (exercise!).

Ultimately, we probably want to expose an unlifted unrestricted thing. There is no obstacle except time and syntax.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Here is the corresponding issue, by the way: https://gitlab.haskell.org/ghc/ghc/-/issues/18490 .

@utdemir
Copy link
Copy Markdown
Contributor Author

utdemir commented Sep 23, 2020

You say that you have freezing primitives to mutable vectors but I don't see any.

It's here:

-- | /O(1)/ Convert a 'Vector' to an immutable 'Vector' (from 'vector' package).
freeze :: Vector a #-> Ur (Vector.Vector a)
freeze (Vec sz arr) =
Array.freeze arr
& \(Ur vec) -> Ur (Vector.take sz vec)

freeze would be a rather bad name for them, maybe share?

Why do you think freeze is a bad name? Since we are essentially taking a mutable object and giving back an immutable one with referential transparency.

But I'm happy to rename it to share if you still tihnk it's a better choice. Do you prefer if we do the same to Array's too, or does freeze work well for them?

@aspiwack
Copy link
Copy Markdown
Member

You misread me. But I also misread you. So let's drop this line of conversation altogether.

Comment on lines +233 to +235
-- We only need to do above because 'Vector' constructor is hidden.
-- Once it is exposed, we should be able to replace it with something
-- safer like: `go arr = Vector 0 (sizeof arr) arr`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's make an issue in our tracker to fix this. And we'll contribute upstream some way. After milestone 1.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I opened: #214

Copy link
Copy Markdown
Contributor

@Divesh-Otwani Divesh-Otwani left a comment

Choose a reason for hiding this comment

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

This is really cool 😃

where
go arr = unsafeDupablePerformIO $ do
mut <- Prim.unsafeThawArray (Prim.Array arr)
let mv = MVector.MVector 0 (Prim.sizeofMutableArray mut) mut
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 think this is correct because of the source of the MVector instance for MVector.

@utdemir utdemir merged commit fbe9e87 into master Sep 24, 2020
@utdemir utdemir deleted the ud/array-to-vector branch September 24, 2020 23:29
Sign up for free to 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