Ticket #83 (new enhancement)

Opened 8 months ago

Last modified 7 months ago

PhaseChange class, Mutable data family

Reported by: illissius Owned by:
Priority: minor Milestone:
Version: Keywords:
Cc:

Description

Hello,

How receptive would you be towards a change to primitive and/or vector somewhere along the line of the following?

class PhaseChange a where
    data Mutable a :: * -> *
    unsafeThaw :: a -> ST s (Mutable a s)
    unsafeFreeze :: Mutable a s -> ST s a
    copy :: Mutable a s -> ST s (Mutable a s)

-- all of these ST s could be PrimMonad m (or MonadST m) instead
thaw :: PhaseChange a => a -> ST s (Mutable a s)
thaw = copy <=< unsafeThaw

freeze :: PhaseChange a => Mutable a s -> ST s a
freeze = unsafeFreeze <=< copy

...other functions, such as 'modify' from vector...

instance PhaseChange ByteArray where
    data Mutable ByteArray s = MutableByteArray (MutableByteArray# s)
    ...

type MutableByteArray = Mutable ByteArray

instance PhaseChange (Array a) where
    data Mutable (Array a) s = MutableArray (MutableArray# s a)
    ...

type MutableArray s a = Mutable (Array a) s

-- forall Vectors
instance PhaseChange (Vector a) where
    data Mutable (Vector a) s = MVector !(MutableArray s a) !Int !Int
    ...

type MVector s a = Mutable (Vector a) s

Advantages: There would be a uniform interface for interacting with types which can be converted between mutable and immutable forms, with operations available which can be defined purely in terms of it (such as 'modify'); the various Arrays and Vectors would implement it, but relevant third parties would also be able to.

Disadvantages: It would not be possible to partially apply the MutableArray and MVector types. This would totally break the MVector class and the Mutable type family, which would have to be redesigned to accomodate (presumably using the new Mutable data family instead). To be honest I didn't realize this when I started writing, and I wouldn't be surprised if it's a deal breaker. An other possibility would be to have a separate PhaseChange1 class for * -> * types, which I think would solve this problem, but didn't work so well when I last tried it for reasons I can't remember (albeit that was still with the MPTC formulation, see below).

Motivation: I have a phasechange package on Hackage[1], which implements the same concept as above except currently with an MPTC and TFs with bidirectional equality constraints (so, basically FDs). I realized that the whole thing would be a lot more simple and straightforward if instead of the whole TFs machinery I had a simple Mutable data family like above, but that would require instances to adapt to the class, instead of vice versa like now. Hence this ticket. If vector doesn't want to adapt then I would have to choose between the more appealing formulation and the Vector instances. If by chance it does want to adapt then I can work on preparing a patch. (Motivation for the phasechange package: I'm slowly working on bindings for Qt, and its various data types would be represented on the Haskell side in both mutable and immutable forms, which would implement this interface.)

[1] http://hackage.haskell.org/package/phasechange

Change History

Changed 8 months ago by rl

This is actually fairly similar to what vector already uses. If you get rid of the copy method (which seems wrong in that class to me), then you could make Mutable a type family rather than an associated data type.

Using unsafeThaw in thaw is wrong since unsafeThaw is not a noop for boxed arrays. It tells the compiler that the array might change and that the GC has to be prepared to handle such changes.

In general, I wouldn't be against extracting the Mutable/unsafeFreeze/unsafeThaw into a separate type class but alas, I can't change Mutable v s a to Mutable (v a) s.

Changed 7 months ago by illissius

Hmm, you're right about thaw/unsafeThaw. I actually knew what unsafeThaw does and forgot to turn my brain on. Conceptually though, the difference between a full freeze/thaw and an unsafe one is a copy, and that makes me want to factor out the copy. One way to solve it, in thaw, would be to unsafeFreeze it back before returning the copy: would that badly affect performance? But if we do need different copys for the different types, making four methods, then we might as well just put freeze/thaw themselves in the class...

...and you're quite right that if there's no copy method, then a data family isn't strictly necessary and a type family can also work. The other reason I want a data family is that whenever I encounter the pattern:

type family Foo a
data A = ...
data FooA = ...
type Foo A = FooA
data B = ...
data FooB = ...
type Foo B = FooB
...

it makes me want cut out the middleman:

data family Foo a
data A = ...
data instance Foo A = ...
data B = ...
data instance Foo B = ...
...

which besides cutting down on boilerplate just feels so much cleaner, and has the added benefits that Foo and Foo A are real, first-class types that you can do the same things with that you could any other other type (for example: put it in an instance head), type inference is improved (because it's a real type), and you no longer need two different ways to refer to the same thing. The drawback, of course, is that it requires the instance types to depend on the family, instead of allowing the reverse direction.

I think that even if you made no other changes, replacing

type family Mutable v :: * -> * -> *

with

data family Mutable v :: * -> * -> *
forall Vector. type MVector = Mutable Vector

would be an improvement to vector, but of course not a backwards compatible one.

Anyways, I'm getting convinced that since there's no way to reconcile the situation, and the Vector instances in my phasechange package aren't actually adding any value (they're mostly there for completeness), that the best choice for me is to part ways with those and along with them GHC's problematic unsafeFreeze/unsafeThaw primitives, and move over to the much simpler 'data family Mutable a' formulation.

Thanks for the input!

(Feel free to close.)

Note: See TracTickets for help on using tickets.