-
Notifications
You must be signed in to change notification settings - Fork 141
Export unstreamPrimM & unsafeUnstreamPrimM #544
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -164,7 +164,7 @@ module Data.Vector.Generic ( | |
-- * Fusion support | ||
|
||
-- ** Conversion to/from Bundles | ||
stream, unstream, unstreamM, streamR, unstreamR, | ||
stream, unstream, unstreamM, unstreamPrimM, unsafeUnstreamPrimM, streamR, unstreamR, | ||
|
||
-- ** Recycling support | ||
new, clone, | ||
|
@@ -2621,23 +2621,51 @@ unstreamM s = do | |
xs <- MBundle.toList s | ||
return $ unstream $ Bundle.unsafeFromList (MBundle.size s) xs | ||
|
||
|
||
-- | Load a monadic stream bundle into a newly allocated vector. It | ||
-- makes writes to a single buffer and copies it when finished. Note | ||
-- for monads that encode nondeterminism result may be different | ||
-- from `unstreamM`. | ||
-- | ||
-- @since NEXT_VERSION | ||
unstreamPrimM :: (PrimMonad m, Vector v a) => MBundle m u a -> m (v a) | ||
{-# INLINE_FUSED unstreamPrimM #-} | ||
unstreamPrimM s = M.munstream s >>= unsafeFreeze | ||
unstreamPrimM s = M.munstream s >>= freeze | ||
|
||
-- | Load a monadic stream bundle into a newly allocated vector. This | ||
-- function create mutable buffer, writes to it and then | ||
-- 'unsafeFreeze's it. | ||
-- | ||
-- This function is unsafe. For monads that encode nondeterminism | ||
-- (e.g. @ListT@) it allows to break referential transparency. More | ||
-- precisely if 'unsafeFreeze' is called more than once we will | ||
-- perform writes into buffer which is considered immutable. | ||
Comment on lines
+2639
to
+2642
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you have an example of using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're talking about ListT done right; see This ListT satisfies:
What else would you require? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The whole reason why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is obviously satisfied by
Well, I'm sure you're meaning something stronger, but I don't know how to formalize your idea. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we can't rely on a function like this to be safe: generateArray :: PrimMonad m => Int -> (Int -> m a) -> m (Array a)
generateArray n
| n <= 0 = pure emptyArray
| otherwise = do
marr <- newArray n undefined
let go i = when (i < n) $ f i >>= writeArray marr i >> go (i + 1)
go 0
unsafeFreezeArray marr Then I am more than sure there is a lot of code that is unsafe out there! So, if you think there is an issue here, then we need to tighten requirements on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should raise a ticket on the
If this issue is solved at the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. This is not only vector's problem. I'm planning to do that tomorrow. Hopefully it would be possible to create reasonable design. Also I wonder whether it's possible to violate referential transparency using ContT monad. It allows basically anything but I couldn't construct such example. I didn't try very hard though There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Answering my own question. Of course it is. Everything is possible with ContT. One just need to encode some backtracking: import Control.DeepSeq
import Control.Monad.Trans.Cont
import Control.Monad.IO.Class
import Data.Vector.Unboxed qualified as VU
import Data.Vector.Unboxed.Mutable qualified as VUM
liftList :: Monad m => [a] -> ContT [r] m a
liftList xs = ContT $ \cont ->
concat <$> traverse cont xs
continuedHorror :: IO [([Int], VU.Vector Int)]
continuedHorror = flip runContT (pure . pure) $ do
mv <- VUM.generateM 2 $ \i -> liftList [i, i+5]
v <- VU.unsafeFreeze mv
let !i = force $ VU.toList v
pure (i,v) It returns:
and of course replacing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lehins I created haskell/primitive#431 Since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Shimuuar Very nice minimal reproducer for the problem 💪 |
||
-- | ||
-- In particular it's certainly unsafe to use this function when | ||
-- type of monad is polymorphic but it's safe to use for monads such | ||
-- as @IO@, @ST@, @Reader@, @Writer@, @State@. | ||
-- | ||
-- @since NEXT_VERSION | ||
unsafeUnstreamPrimM :: (PrimMonad m, Vector v a) => MBundle m u a -> m (v a) | ||
{-# INLINE_FUSED unsafeUnstreamPrimM #-} | ||
unsafeUnstreamPrimM s = M.munstream s >>= unsafeFreeze | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned in my other comment I don't think there is anything unsafe about this function. I would love to be proven wrong with an example of using some known |
||
|
||
-- FIXME: the next two functions are only necessary for the specialisations | ||
unstreamPrimM_IO :: Vector v a => MBundle IO u a -> IO (v a) | ||
{-# INLINE unstreamPrimM_IO #-} | ||
unstreamPrimM_IO = unstreamPrimM | ||
unstreamPrimM_IO = unsafeUnstreamPrimM | ||
|
||
unstreamPrimM_ST :: Vector v a => MBundle (ST s) u a -> ST s (v a) | ||
{-# INLINE unstreamPrimM_ST #-} | ||
unstreamPrimM_ST = unstreamPrimM | ||
unstreamPrimM_ST = unsafeUnstreamPrimM | ||
|
||
{-# RULES | ||
|
||
"unstreamM[IO]" unstreamM = unstreamPrimM_IO | ||
"unstreamM[ST]" unstreamM = unstreamPrimM_ST #-} | ||
"unstreamM[IO]" unstreamM = unstreamPrimM_IO | ||
"unstreamM[ST]" unstreamM = unstreamPrimM_ST | ||
"unstreamPrimM[IO]" unstreamPrimM = unstreamPrimM_IO | ||
"unstreamPrimM[ST]" unstreamPrimM = unstreamPrimM_ST | ||
#-} | ||
|
||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible? Can the results be different? I mean, I believed that the results are always the same; I thought I just can't prove it mathematically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure myself. But they are different operationally:
unstreamM
uses immutable data structures andunstreamPrimM
and in principle can observe mutations.ListT
mutates elements in particular order so results are always same.Perhaps some lawless variant of
OmegaT
built on top https://hackage-content.haskell.org/package/control-monad-omega-0.3.3/docs/Control-Monad-Omega.html will work. Or some tricks withContT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't this is the case at all and IMHO
unstreamPrimM
is absolutely safe withunsafeFreeze
instead offreeze
at the end.If this operation is unsafe for some transformer that would mean its instance for
PrimMonad
is incorrect.