Ticket #54 (reopened enhancement)

Opened 2 years ago

Last modified 3 months ago

Missing NFData instances for vector types

Reported by: hvr Owned by:
Priority: blocker Milestone: 0.10
Version: 0.7 Keywords:
Cc: hvr@…, v.dijk.bas@…, PHO

Description

Please provide NFData instances for the various Data.Vector types

Attachments

nfdata.dpatch (6.3 kB) - added by basvandijk 17 months ago.

Change History

  Changed 2 years ago by hvr

Fyi, In my code I'm currently using

instance VU.Unbox a => NFData (VU.Vector a)

instance NFData a => NFData (V.Vector a) where
  rnf v = V.foldl' (\x y -> y `deepseq` x) () v

(of which I hope that it's correct)

follow-up: ↓ 6   Changed 2 years ago by choener

Doesn't the first one crash during runtime, if you actually call rnf on an unboxed vector?

follow-up: ↓ 5   Changed 2 years ago by rl

  • priority changed from major to minor

I'd like to do this very much but can't, at least not yet. The problem is that vector is a boot library for GHC (because DPH depends on it) and deepseq isn't so introducing a dependency on deepseq isn't a trivial thing to do. I'll revisit this question after GHC 7.2 is released.

  Changed 2 years ago by choener

Maybe we could introduce a vector-extras library with stuff like that?

I see this sometimes myself for rnf (data = data Bla {... unboxed vector bla}) stuff. Note that the rnf instance for unboxed vectors would do anything...

in reply to: ↑ 3   Changed 21 months ago by hvr

  • cc hvr@… added

Replying to rl:

I'd like to do this very much but can't, at least not yet. The problem is that vector is a boot library for GHC (because DPH depends on it) and deepseq isn't so introducing a dependency on deepseq isn't a trivial thing to do. I'll revisit this question after GHC 7.2 is released.

Fyi, GHC 7.4 is going to ship w/ deepseq as GHC-boot package (see http://hackage.haskell.org/trac/ghc/ticket/5468 )

in reply to: ↑ 2 ; follow-up: ↓ 7   Changed 21 months ago by hvr

Replying to choener:

Doesn't the first one crash during runtime, if you actually call rnf on an unboxed vector?

why should it? The first one uses the default rnf implementation which corresponds to something like rnf x = seq x ()

in reply to: ↑ 6   Changed 21 months ago by choener

Replying to hvr:

Replying to choener:

Doesn't the first one crash during runtime, if you actually call rnf on an unboxed vector?

why should it? The first one uses the default rnf implementation which corresponds to something like rnf x = seq x ()

Yeah... :-( I did not remember that there was a default instance... *sigh* The second one seems ok, since everything else in a vector is unpacked+banged.

Someone provided a library: http://hackage.haskell.org/package/vector-strategies with parallel evaluation as well. Yours?

  Changed 17 months ago by hvr

@basvandijk, you might want to update attachment:nfdata.dpatch to take into account that GHC-7.4.0 is currently shipping with deepseq-1.3.0

Changed 17 months ago by basvandijk

  Changed 17 months ago by basvandijk

Done. @hvr thanks for the notification.

  Changed 16 months ago by rl

  • priority changed from minor to blocker
  • milestone set to 0.10

  Changed 16 months ago by rl

  • status changed from new to closed
  • resolution set to fixed

Pushed, thanks.

  Changed 16 months ago by hvr

great =)

...what's the rough ETA for the next vector hackage release?

  Changed 16 months ago by rl

  • status changed from closed to reopened
  • resolution fixed deleted

Actually, I'm not sure that the NFData instance for boxed mutable vectors is safe. Compare this:

do
  write v 0 x
  rnf v `seq` return ()

with this:

do
  rnf v `seq` return ()
  write v 0 x

Assuming that the write can't fail, if rnf is pure then the two should be equivalent. But they clearly aren't.

It seems that for mutable vectors, you'd need a monadic version of rnf. Maybe that's something that the deepseq package should provide.

I'll comment out the instance in question and reopen the ticket for now.

  Changed 16 months ago by hvr

How would the definition of the monadic version, say mrnf look like? Just something like

mrnf :: (NFData a, Monad m) => a -> m ()
mrnf v = rnf v `seq` return ()

?

  Changed 16 months ago by basvandijk

  • cc v.dijk.bas@… added

follow-up: ↓ 17   Changed 16 months ago by rl

I think to be safe, it would have to be a separate type class. Perhaps something like this:

class Monad m => NFDataM m a where
  mrnf :: a -> m ()

I hope 0.10 will be out within the next month. I'm making a lot of changes at the moment, though.

in reply to: ↑ 16   Changed 16 months ago by hvr

Replying to rl:

I think to be safe, it would have to be a separate type class. Perhaps something like this:

...with a default instance for types already providing a pure NFData instances?

  Changed 16 months ago by rl

Ideally yes, but Haskell doesn't really support that. The closest is the new GHC extension:

class Monad m => NFDataM m a where
  mrnf :: a -> m ()

  default mrnf :: NFData a => a -> m ()
  mrnf x = rnf x `seq` return ()

But you'd still have to say

instance NFDataM m MyType

  Changed 16 months ago by hvr

What about the variant below? it compiles with GHC 7.4 and seems to work...

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}

import Control.DeepSeq

class Monad m => NFDataM m a where
    mrnf :: a -> m ()

instance (Monad m, NFData a) => NFDataM m a where
    mrnf x = rnf x `seq` return ()

  Changed 16 months ago by rl

But then you'd need overlapping instances if you wanted to define:

instance NFDataM IO (IOVector a)

  Changed 16 months ago by basvandijk

Oops! Well spotted Roman!

I think there should also be a functional dependency from a to m (or an associated type family) like:

class NFDataM a m | a -> m where
  rnfM :: a -> m ()

So that we can define:

instance NFDataM  (MVector s a) (ST s) where
  rnfM (MVector i n arr) = force i
        where
          force !ix | ix < n    = do x <- readArray arr ix
                                     rnf x `seq` force (ix+1)
                    | otherwise = return ()

follow-up: ↓ 23   Changed 16 months ago by rl

That's the question. Having the dependency (or a type function) probably makes more sense for things that actually live in monads. But as hvr points out, you might also want to have something like this:

instance NFDataM [a] m

That doesn't work with the dependency.

Then there is also the question of what to do with monad transformers. This seems like a not entirely trivial design question, perhaps it's worth discussing on the libraries list.

in reply to: ↑ 22   Changed 16 months ago by basvandijk

Replying to rl:

That's the question. Having the dependency (or a type function) probably makes more sense for things that actually live in monads. But as hvr points out, you might also want to have something like this: {{{ instance NFDataM [a] m }}}

Why should you want to do that? If you want to force immutable data use rnf, if you want to force mutable data in its associated monad use rnfM.

Then there is also the question of what to do with monad transformers. This seems like a not entirely trivial design question, perhaps it's worth discussing on the libraries list.

Yes, I think that's a good idea.

BTW my last example wasn't fully correct and general enough. It should be:

instance (NFData a, PrimMonad m, s ~ PrimState m) => NFDataM  (MVector s a) m where
  rnfM (MVector i n arr) = force i
        where
          force !ix | ix < n    = do x <- readArray arr ix
                                     rnf x `seq` force (ix+1)
                    | otherwise = return ()

(Note that this requires UndecidableIntances)

  Changed 3 months ago by PHO

  • cc PHO added
Note: See TracTickets for help on using tickets.