-
Notifications
You must be signed in to change notification settings - Fork 48
WIP: Revive compact representation for NP/NS #129
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?
Conversation
107facc to
cd9f2ef
Compare
| @@ -1,5 +1,5 @@ | |||
| name: sop-core | |||
| version: 0.5.0.1 | |||
| version: 0.5.1.0 | |||
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.
FWIW, this is definitely not a non-breaking change.
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.
OTOH, staged-streams compiles fine. (Plenty of warnings about non-exhautive pattern matches on NS, but those are not fatal).
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.
Yeah, my hope was that with the pattern synonym it would be backwards compatible; and those warnings should even be absent from 8.10.
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.
The data structure is now stricter (spine strict?). This is contrived example, but still, the behavior changes observably:
Currently,
hd $ I 'x' :* (error "ay, caramba!" :: NP I '[])
I 'x'With this patch
*Data.SOP Data.SOP.NP> hd $ I 'x' :* (error "ay, caramba!" :: NP I '[])
I *** Exception: ay, caramba!
CallStack (from HasCallStack):
error, called at <interactive>:21:16 in interactive:Ghci1There 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.
Ah, fair enough. Hadn't thought terribly hard about strictness just yet :)
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 there a good reason why someone might rely on the construction of the NP being lazy? the idea of writing code which might only look at the first N elements of a NP seems pretty odd to me.
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.
FWIW, I think that NP and NS should be spine-strict, and not making them so from the beginning was a mistake. Before sop-core, I'd have said there's basically no reason not to make the change. Now, it's theoretically possible there are uses that aren't generic programming related which might have other requirements. But it may be worth just doing it.
| deriving instance All (Eq `Compose` f) xs => Eq (NS f xs) | ||
| deriving instance (All (Eq `Compose` f) xs, All (Ord `Compose` f) xs) => Ord (NS f xs) | ||
| instance All (Show `Compose` f) xs => Show (NS f xs) where | ||
| show ns @ (NS i _) = |
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.
Add TODO to implement this properly. To be backward compatible this needs to print Ss and Zs.
BTW. The space between @ and (NS ... will break on GHC-9.0 due ghc-proposals/ghc-proposals#229
src/Data/SOP/NS.hs:197:3: error:
Parse error in pattern: show
In a function binding for the ‘@’ operator.
Perhaps you meant an as-pattern, which must not be surrounded by whitespace
|
197 | show ns @ (NS i _) =
(Otherwise this patch is GHC-9.0 friendly)
|
I just came to repo to see if it would be possible to have a more compact representation of data NS (f :: k -> Type) (ts :: [k]) = NS Word AnyI’m less sure if it would have as much of a performance improvement as the rest of this PR, but it would definitely improve memory usage. |
|
Yes, that's certainly possible. The original branch from which I was working does in fact do that. For my current purposes however, it turns out this representation is still not quite good enough, so I'm not sure if this work will go anywhere right now. |
|
Ah great, I found the branch and it looks like it was a little more complicated than I expected (I had started playing with an implementation of the idea this afternoon but ran into some of the issues that the original branch ran into, such a making instances for Show, Eq etc. work) I would be really keen to see some benchmarks before and after with the original, your PR's work, the compact NS implementation, and both (though I understand if you aren't keen do the last two). |
|
Perhaps @kosmikus did some on the original compact representation branch? |
|
I did only do runtime benchmarking, and I wasn't too impressed. I think staging is the far superior approach for runtime performance. Compile-time is a different question though. If the goal is that any compact-representation branch would become the default at any point, it needs thorough benchmarking though. I don't want to replace a safe implementation with a potentially unsafe one for questionable / not noticeable gain. |
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 basically convinced we should finally do this. There's still a lingering question whether we should also offer the old inductive versions in sop-core. Advantages could be:
- somewhat easier to do performance comparisons if both are available in a single package
- other use cases of
sop-coremight need to use e.g.:*for construction in a now less efficient way
My remarks are basically points for discussion. I'm happy to make the changes myself, once we reach consensus.
The more important point is that this is only partial though, and we need to make changes to the other functions before merging this. While it should all be correct as it currently is, because the old constructors are faithfully reimplemented, many functions can and should be much more efficiently implemented in terms of the underlying compact representation.
| data NP :: (k -> Type) -> [k] -> Type where | ||
| Nil :: NP f '[] | ||
| (:*) :: f x -> NP f xs -> NP f (x ': xs) | ||
| newtype NP (f :: k -> *) (xs :: [k]) = NP (V.Vector Any) |
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 still unconvinced that Any is better than f Any here. Do you have any data points on this?
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 started out with just Any on my branch, despite having seen you use f Any, because I didn't see the point. Then later on I realized that actually having that f there is really rather helpful; often that f is concrete (say, an applicative functor or something) and having, say, f (Any -> Any) and f Any in hand is a lot more helpful than Any and Any.
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.
(Well, maybe not concrete concrete, but at least we know something about the structure.)
|
|
||
| #if __GLASGOW_HASKELL__ >= 802 | ||
| {-# COMPLETE Nil, (:*) #-} | ||
| #endif |
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.
We don't need this CPP anymore, as 8.0 is the oldest GHC version we support even now, but in practice, we're probably going to shift the lower bound to 8.6 soon.
| module Data.SOP.NP | ||
| ( -- * Datatypes | ||
| NP(..) | ||
| NP(.., Nil, (:*)) |
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.
Do we want to export the NP constructor, or do we want to move it to an Internal module?
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.
Moving it would make sense to me, otherwise there are bound to be uses of NP that use the patterns dictionary and they would now suddenly get much worse efficiency.
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 would definitely want to be able to access the NP constructor as a developer, but putting it in a .Internal modules makes sense as it doesn't provide a safe interface, which Nil and (:*) do. If I know what I'm doing and performance matters to me, I'd want to be able to work with the Vector directly and convince myself I did so safely.
| => forall x xs . (xs' ~ (x ': xs)) => f x -> NP f xs -> NP f xs' | ||
| pattern x :* xs <- (viewNP -> IsCons x xs) | ||
| where | ||
| x :* NP xs = NP (V.cons (unsafeCoerce x) xs) |
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.
The should be a documentation warning here that the complexity of this for construction is O(n).
| data NS :: (k -> Type) -> [k] -> Type where | ||
| Z :: f x -> NS f (x ': xs) | ||
| S :: NS f xs -> NS f (x ': xs) | ||
| data NS (f :: k -> *) (xs :: [k]) = NS !Int Any |
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.
Same remark as before. Why not f Any?
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.
We probably should add an UNPACK pragma for good measure.
| module Data.SOP.NS | ||
| ( -- * Datatypes | ||
| NS(..) | ||
| NS(.., Z, S) |
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.
Same remark as for NP regarding the export of the NS constructor.
| go (fs :* _ ) (Z xs ) = Z (ap_NP fs xs ) | ||
| go (_ :* fss) (S xss) = S (go fss xss) | ||
| go Nil x = case x of {} | ||
| go (NP nps) (NS i ns) = NS i (unsafeCoerce ap_NS (nps V.! i) ns) |
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.
Why not inline go here?
|
FWIW, I've started working on porting the rest of the old compact-representation branch. |
|
Is there any word on progress of this PR? |
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 noticed a missing optimisation based on the new representation. Is there any chance this PR will actually get merged? It's been stagnant for a few months now.
| unZ (NS _ x) = unsafeCoerce x | ||
|
|
||
| -- | Obtain the index from an n-ary sum. | ||
| -- |
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.
Commenting here for the index_NS definition below, but this PR should also have:
index_NS :: forall f xs . NS f xs -> Int
index_NS (NS i _) = i
This is an attempt to revive https://github.com/well-typed/generics-sop/tree/compact-representation .