简体   繁体   中英

Sharing observer function code between mutable and frozen versions of a type

I was working on creating my own custom mutable/frozen data type that internally contains an MVector / Vector . It needs to be mutable for performance reasons so switching to an immutable data structure is not something I am considering.

It seems like implementing an observer function for one of the two versions should allow me to just steal that implementation for the other type. Here are the two options I am considering:

render :: Show a => MCustom s a -> ST s String
render mc = ...non trivial implementation...

show :: Show a => Custom a -> a
show c = runST $ render =<< unsafeThaw c

Where unsafeThaw calls Vector.unsafeThaw under the covers, which should be safe as that thawed vector is never mutated, only read. This approach feels the cleanest, the only downside is that render is strict, which forces show to be strict whereas a duplicate implementation could correctly stream the String without forcing it all at once.

The other option, which feels much more dirty but that I think is safe is to do this:

show :: Show a => Custom a -> a
show c = ...non trivial implementation that allows lazy streaming...

render :: Show a => MCustom s a -> ST s String
render mc = do
    s <- show <$> unsafeFreeze mc
    s `deepseq` pure s

Are either of these my best option? If not what else should I do?

To me it seemed most intuitive to build one version off of the other. But it seems like if I make the mutable version the base version then I will end up with a lot more strictness then I want, even if the implementations seem fairly clean and logical, just because ST necessitates strictness unless I throw in some unsafeInterleaveST calls, but these would only be safe when the mutable observer was called via an immutable object.

On the other hand if I make the immutable version the base version then I will end up with more dirty, deepseq code, and sometimes I would just have to reimplement things. For example all in place editing functions can be done on a frozen object pretty easily by just copying the frozen object and then calling unsafeThaw on it and modifying the copy in place before calling unsafeFreeze and returning it. But doing the opposite isn't really doable, as a copy modification that is used for the immutable version cannot be converted to an in place modification.

Should I perhaps write all modification functions alongside the mutable implementation, and all observer functions alongside the immutable implementation. And then have a file that depends on both that unifies everything via unsafeThaw and unsafeFreeze ?

How about having a pure function

show :: (StringLike s, Show a) => Custom a -> s

You can get both lazy and strict output with different instantiations of s , in which cons is either lazy or strict; eg String or Text :

class StringLike s where 
  cons :: Char -> s -> s 
  nil :: s 
  uncons :: s -> Maybe (Char, s) 

instance StringLike String where ...
instance StringLike Text where ...

You could use other methods, eg phantom types, or simply having two functions ( showString and showText ), to distinguish between lazy and strict output if you like. But if you look at types as specifications of a function's semantics, then the place to indicate laziness or strictness is in the return type of that operation. This removes the need for some sort of strict show for Custom inside of ST .

For the MCustom version, you probably do not export the String version, eg:

render :: MCustom s a -> ST s Text
render a = show <$> unsafeFreeze a

You can throw in a seq to force the result when the function runs but the entire Text would be forced when any character is used anyways.


But the simplest solution seems to just abstract the pattern of using a mutable structure in an immutable fashion, eg

atomically :: (NFData a) => (Custom x -> a) -> MCustom s x -> ST s a 
atomically f v = do
    r <- f <$> unsafeFreeze v
    r `deepseq` pure r

This saves you from using unsafeFreeze/deepseq all over your code, just as you have modify to do immutable operations on mutable vectors.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM