Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heterogeneous array creation #109

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 179 additions & 1 deletion Data/Primitive/Array.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{-# LANGUAGE CPP, MagicHash, UnboxedTuples, DeriveDataTypeable, BangPatterns #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE KindSignatures #-}

-- |
-- Module : Data.Primitive.Array
Expand All @@ -17,7 +18,7 @@ module Data.Primitive.Array (
Array(..), MutableArray(..),

newArray, readArray, writeArray, indexArray, indexArrayM,
freezeArray, thawArray, runArray,
freezeArray, thawArray, runArray, runArrays, runArraysOf, runHetArraysOf,
unsafeFreezeArray, unsafeThawArray, sameMutableArray,
copyArray, copyMutableArray,
cloneArray, cloneMutableArray,
Expand Down Expand Up @@ -798,3 +799,180 @@ instance (Typeable s, Typeable a) => Data (MutableArray s a) where
toConstr _ = error "toConstr"
gunfold _ _ = error "gunfold"
dataTypeOf _ = mkNoRepType "Data.Primitive.Array.MutableArray"

-- | Create any number of arrays of the same type within an arbitrary
-- 'Traversable' context. This will often be useful with traversables
-- like @(c,)@, @'Either' e@, @'Compose' (c,) ('Either' e)@, and
-- @'Compose' ('Either' e) (c,)@. To use an arbitrary traversal
-- function, see 'runArraysOf'. To create arrays of varying types,
-- see 'runHetArraysOf'.
runArrays
:: Traversable t
=> (forall s. ST s (t (MutableArray s a)))
-> t (Array a)
runArrays m = runST $ m >>= traverse unsafeFreezeArray

-- | Just like 'runArrays', but takes an arbitrary (potentially
-- type-changing) traversal function instead of requiring a 'Traversable'
-- constraint. To produce arrays of varying types, use 'runHetArraysOf'.
--
-- @
-- 'runArrays' m = runArraysOf traverse m
-- @
runArraysOf
:: (forall s1 s2.
(MutableArray s1 a -> ST s2 (Array a)) -> t (mut s1 x) -> ST s2 u)
-> (forall s. ST s (t (mut s x)))
-> u
-- See notes below
runArraysOf trav m = runST $ m >>= trav unsafeFreezeArray

{-
Why do I believe 'runArraysOf' is safe? The key safety property is
that we must never modify an array after it is frozen. The first
thing we do is run the given action, producing something of type

t (mut s x)

and passing it to trav. We need to make sure that trav just applies
its function argument (unsafeFreezeArray) to any MutableArrays that
may contain/produce, and doesn't modify them in any other ways. Consider
the type of trav:

trav :: forall s1 s2.
(MutableArray s1 a -> ST s2 (Array a))
-> t (mut s1 x) -> ST s2 u

trav operates in the state thread labeled s2. We don't let it know that
the mutable arrays it handles live in the same thread! They're off in
s1, a whole different universe. So trav can only apply the freeze it's
passed, or perform whatever actions may ride in on t (mut s x). Can
the latter happen? Imagine something like

data T :: Type -> Type where
T :: ST s (MutableArray s x) -> T (MutableArray s x)

Can trav pull this open and run the action? No! The state thread in
T matches the array in T, but it doesn't match the state thread trav
lives in, so trav can't do anything whatsoever with it.

-----

It's annoying that @t@ takes a @mut s1 x@ argument instead
of just an @s1@ argument, but this allows 'runArraysOf' to be used directly
with 'traverse'. The cleaner version can be implemented efficiently on
top in the following rather disgusting manner:

runArraysOf'
:: (forall s1 s2.
(MutableArray s1 a -> ST s2 (Array a)) -> t s1 -> ST s2 u)
-> (forall s. ST s (t s))
-> u
runArraysOf' trav m = runArraysOf ((. unBar) #. trav) (coerce m)

newtype Bar t x = Bar {unBar :: t (Yuck x)}
type family Yuck x where
Yuck (_ s _) = s

-------

I initially thought we'd need a function like

runArraysOfThen
:: (forall s1 s2.
(MutableArray s1 a -> Compose (ST s2) q r) -> t (MutableArray s1 a) -> Compose (ST s2) q u)
-> (Array a -> q r)
-> (forall s. ST s (t (MutableArray s a)))
-> q u

to allow users to traverse over the generated arrays. But in fact,
one could just write

runArraysOfThen trav post m = getConst $
runArraysOf (\f -> coerce . getCompose . (trav (Compose . fmap post . f))) m

Perhaps such a function *should* be added for convenience, but it's
clearly not necessary.
-}

-- | Create arbitrarily many arrays that may have different types.
-- For a simpler but less general version, see 'runArrays' or
-- 'runArraysOf'.
--
-- === __Examples__
--
-- ==== @'runArrays'@
--
-- @
-- newtype Ha t a v = Ha {unHa :: t (v a)}
-- runArrays m = unHa $ runHetArraysOf (\f (Ha t) -> Ha <$> traverse f t) (Ha <$> m)
-- @
--
-- ==== @unzipArray@
--
-- @
-- unzipArray :: Array (a, b) -> (Array a, Array b)
-- unzipArray ar =
-- unPair $ runHetArraysOf traversePair $ do
-- xs <- newArray sz undefined
-- ys <- newArray sz undefined
-- let go k
-- | k == sz = pure (Pair (xs, ys))
-- | otherwise = do
-- (x,y) <- indexArrayM ar k
-- writeArray xs k x
-- writeArray ys k y
-- go (k + 1)
-- go 0
-- where sz = sizeofArray ar
--
-- data Pair ab v where
-- Pair :: {unPair :: (v a, v b)} -> Pair (a,b) v
--
-- traversePair :: Applicative h => (forall x. f x -> h (g x)) -> Pair ab f -> h (Pair ab g)
-- traversePair f (Pair (xs, ys)) = liftA2 (\x y -> Pair (x,y)) (f xs) (f ys)
--
-- ==== Produce a container of arrays and traverse over them
--
-- @
-- runHetArraysOfThen
-- :: (forall s1 s2.
-- ( (forall x. MutableArray s1 x -> Compose (ST s2) q (r x))
-- -> t (mut s1) -> Compose (ST s2) q u))
-- -- ^ A rank-2 traversal
-- -> (forall x. Array x -> q (r x))
-- -- ^ A function to traverse over the container of 'Array's
-- -> (forall s. ST s (t (mut s)))
-- -- ^ An 'ST' action producing a rank-2 container of 'MutableArray's.
-- -> q u
-- runHetArraysOfThen trav post m = getConst $
-- runHetArraysOf (\f -> coerce . getCompose . trav (Compose . fmap post . f)) m
-- @
runHetArraysOf
:: (forall s1 s2.
((forall x. MutableArray s1 x -> ST s2 (Array x)) -> t (mut s1) -> ST s2 u))
-- ^ A rank-2 traversal
-> (forall s. ST s ((t :: (* -> *) -> *) (mut s)))
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

With PolyKinds, this can be t :: (j -> k) -> *. Maybe we can do that once we drop support for 7.4; until then I think it's probably a bit too much CPP for what it offers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

(Remember that mut doesn't necessarily have to be used at all; it's just there to make the type match that of htraverse. So if someone wants to use a container of MutableArray#, say, they can still do that with a bit of type trickery.)

-- ^ An 'ST' action producing a rank-2 container of 'MutableArray's.
-> u
runHetArraysOf trav m = runST $ m >>= trav unsafeFreezeArray

{-
This alternative version is arguably prettier, but it's not compatible
with the traversal functions from rank2types or compdata for the same reason
that the prettier version of 'runArraysOf' isn't compatible with 'traverse'.
It can be implemented with a bit of ugliness.

runHetArraysOf'
:: (forall s1 s2.
((forall x. MutableArray s1 x -> ST s2 (Array x)) -> t s1 -> ST s2 u))
-- ^ A rank-2 traversal
-> (forall s. ST s (t s))
-- ^ An 'ST' action producing a rank-2 container of 'MutableArray's.
-> u
runHetArraysOf' trav m = runHetArraysOf (\f -> trav f . unBaz) (coerce m)

type family Gross ms where
Gross (_ s) = s
newtype Baz t ms = Baz {unBaz :: t (Gross ms)}
-}
94 changes: 94 additions & 0 deletions Data/Primitive/SmallArray.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE KindSignatures #-}

-- |
-- Module : Data.Primitive.SmallArray
Expand Down Expand Up @@ -52,6 +53,9 @@ module Data.Primitive.SmallArray
, unsafeFreezeSmallArray
, thawSmallArray
, runSmallArray
, runSmallArrays
, runSmallArraysOf
, runHetSmallArraysOf
, unsafeThawSmallArray
, sizeofSmallArray
, sizeofSmallMutableArray
Expand Down Expand Up @@ -940,3 +944,93 @@ smallArrayFromListN n l = SmallArray (Array.fromListN n l)
-- | Create a 'SmallArray' from a list.
smallArrayFromList :: [a] -> SmallArray a
smallArrayFromList l = smallArrayFromListN (length l) l

-- | Create any number of arrays of the same type within an arbitrary
-- 'Traversable' context. This will often be useful with traversables
-- like @(c,)@, @'Either' e@, @'Compose' (c,) ('Either' e)@, and
-- @'Compose' ('Either' e) (c,)@. To supply an arbitrary traversal
-- function, use 'runSmallArraysOf'. To produce arrays of varying types,
-- use 'runHetSmallArraysOf'.
runSmallArrays
:: Traversable t
=> (forall s. ST s (t (SmallMutableArray s a)))
-> t (SmallArray a)
runSmallArrays m = runST $ m >>= traverse unsafeFreezeSmallArray

-- | Just like 'runSmallArrays', but takes an arbitrary (potentially
-- type-changing) traversal function instead of requiring a 'Traversable'
-- constraint. To produce arrays of varying types, use 'runHetSmallArraysOf'.
--
-- @ 'runSmallArrays' m = runSmallArraysOf traverse m @
runSmallArraysOf
:: (forall s1 s2.
(SmallMutableArray s1 a -> ST s2 (SmallArray a))
-> t (mut s1 x) -> ST s2 u)
-> (forall s. ST s (t (mut s x)))
-> u
runSmallArraysOf trav m = runST $ m >>= trav unsafeFreezeSmallArray

-- | Create arbitrarily many arrays that may have different types. For
-- a simpler but less general version, see 'runSmallArrays' or
-- 'runSmallArraysOf'.
--
--
-- === __Examples__
--
-- ==== @'runSmallArrays'@
--
-- @
-- newtype Ha t a v = Ha {unHa :: t (v a)}
-- runSmallArrays m = unHa $ runHetSmallArraysOf (\f (Ha t) -> Ha <$> traverse f t) (Ha <$> m)
-- @
--
-- ==== @unzipSmallArray@
--
-- @
-- unzipSmallArray :: SmallArray (a, b) -> (SmallArray a, SmallArray b)
-- unzipSmallArray ar =
-- unPair $ runHetSmallArraysOf traversePair $ do
-- xs <- newSmallArray sz undefined
-- ys <- newSmallArray sz undefined
-- let go k
-- | k == sz = pure (Pair (xs, ys))
-- | otherwise = do
-- (x,y) <- indexSmallArrayM ar k
-- writeSmallArray xs k x
-- writeSmallArray ys k y
-- go (k + 1)
-- go 0
-- where sz = sizeofSmallArray ar
--
-- data Pair ab v where
-- Pair :: {unPair :: (v a, v b)} -> Pair (a,b) v
--
-- traversePair :: Applicative h => (forall x. f x -> h (g x)) -> Pair ab f -> h (Pair ab g)
-- traversePair f (Pair (xs, ys)) = liftA2 (\x y -> Pair (x,y)) (f xs) (f ys)
-- @
--
-- ==== Create arrays, then traverse over them
--
-- @
-- runHetSmallArraysOfThen
-- :: (forall s1 s2.
-- ( (forall x. SmallMutableArray s1 x -> Compose (ST s2) q (r x))
-- -> t (mut s1) -> Compose (ST s2) q u))
-- -- ^ A rank-2 traversal
-- -> (forall x. SmallArray x -> q (r x))
-- -- ^ A function to traverse over a container of 'SmallArray's
-- -> (forall s. ST s (t (mut s)))
-- -- ^ An 'ST' action producing a rank-2 container of 'SmallMutableArray's.
-- -> q u
-- runHetSmallArraysOfThen trav post m = getConst $
-- runHetSmallArraysOf (\f -> coerce . getCompose . trav (Compose . fmap post . f)) m
-- @
runHetSmallArraysOf
:: (forall s1 s2.
((forall x. SmallMutableArray s1 x -> ST s2 (SmallArray x))
-> t (mut s1) -> ST s2 u))
-- ^ A rank-2 traversal
-> (forall s. ST s ((t :: (* -> *) -> *) (mut s)))
-- ^ An 'ST' action producing a rank-2 container of 'MutableArray's.
-> u
runHetSmallArraysOf f m = runST $ m >>= f unsafeFreezeSmallArray
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
* Fix the broken `Functor`, `Applicative`, and `Monad` instances for
`Array` and `SmallArray`.

* Add `runarray`, `runArrays`, `runArraysOf`, `runHetArraysOf`, and
equivalent functions for `SmallArray`s.

## Changes in version 0.6.3.0

* Add `PrimMonad` instances for `ContT`, `AccumT`, and `SelectT` from
Expand Down
3 changes: 2 additions & 1 deletion primitive.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ Library
Default-Language: Haskell2010
Other-Extensions:
BangPatterns, CPP, DeriveDataTypeable,
MagicHash, TypeFamilies, UnboxedTuples, UnliftedFFITypes
MagicHash, TypeFamilies, UnboxedTuples, UnliftedFFITypes,
KindSignatures, RankNTypes

Exposed-Modules:
Control.Monad.Primitive
Expand Down