-
Notifications
You must be signed in to change notification settings - Fork 40
More generic push arrays 1/2 #287
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
Conversation
2012c45 to
a295889
Compare
src/Data/Array/Polarized.hs
Outdated
| transfer (Pull.Array f n) = Push.Array (\g -> DArray.fromFunction (\i -> g (f i))) n | ||
| transfer (Pull.Array f n) = | ||
| Push.Array | ||
| (\k -> Prelude.foldl (\m i -> m <> (k (f i))) mempty [0..(n-1)]) |
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, but in my experience foldl is rarely the most efficient fold. Probably I'd go with foldl'.
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 discussed, at some point somewhere, making foldl be the strict version, and having another name for the lazy fold. Probably we should just go for it.
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.
In this case, though, this ought to be a simpler foldMap do we have foldMap?
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 can't use a linear foldMap without changing the type of Push and Pull to hold Int %1-> a and (a %1-> m) -> m respectively. I will usePrelude.foldMap.
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.
Or did you mean a foldMap on Pull arrays? @aspiwack
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 meant foldMap on lists. But you are right that there is some tension in the types. Let's forget about this for now.
src/Data/Array/Polarized/Push.hs
Outdated
| -- must follow so that we can release a safe API. | ||
|
|
||
| emptyWriter :: ArrayWriter a | ||
| emptyWriter = ArrayWriter (Unsafe.toLinear (const ())) 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.
Unsafe call here assumes that we can just forget about the DArray. I think this is the case; but it might worth a comment here. (I think every unsafe call deserves a comment unless incredibly obvious.)
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.
There shouldn't be an Unsafe.toLinear in this module. If we are missing a way to consume an empty destination array, then let's add it to the destination array 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.
I'll make a safe empty DArray consumer with HasCallStack.
| (<>) x y = append x y | ||
|
|
||
| instance Semigroup (Array a) where | ||
| (<>) = append |
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.
This is a purely stylistic choice, so feel free to ignore this; but I'd just inline the append here, I don't see a much need for a separate function and the fewer names the better.
Same goes with mempty and the variants for ArrayWriter.
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 like it because it's a tiny bit more DRY even though the chance of changing the implementation is close to zero.
src/Data/Array/Polarized.hs
Outdated
| transfer (Pull.Array f n) = Push.Array (\g -> DArray.fromFunction (\i -> g (f i))) n | ||
| transfer (Pull.Array f n) = | ||
| Push.Array | ||
| (\k -> Prelude.foldl (\m i -> m <> (k (f i))) mempty [0..(n-1)]) |
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 discussed, at some point somewhere, making foldl be the strict version, and having another name for the lazy fold. Probably we should just go for it.
src/Data/Array/Polarized.hs
Outdated
| transfer (Pull.Array f n) = Push.Array (\g -> DArray.fromFunction (\i -> g (f i))) n | ||
| transfer (Pull.Array f n) = | ||
| Push.Array | ||
| (\k -> Prelude.foldl (\m i -> m <> (k (f i))) mempty [0..(n-1)]) |
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.
In this case, though, this ought to be a simpler foldMap do we have foldMap?
src/Data/Array/Polarized/Push.hs
Outdated
| make x n = Array (\k -> DArray.replicate (k x)) n | ||
| make :: HasCallStack => a -> Int -> Array a | ||
| make x n | ||
| | n < 0 = error "Making negative length push array" |
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.
| | n < 0 = error "Making negative length push array" | |
| | n < 0 = error "Making a negative length push array" |
| make :: HasCallStack => a -> Int -> Array a | ||
| make x n | ||
| | n < 0 = error "Making negative length push array" | ||
| | otherwise = Array (\makeA -> mconcat $ Prelude.replicate n (makeA x)) |
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.
Base has an stimes function for this. Maybe we should have one too?
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.
Array $ \makeA -> stimes (makeA x) might be more efficient if the stimes implementation for ArrayWriter could be more efficient. However, I don't want to change Monoid in 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.
As you wish, but I think it's fine to lazily add stuff to the library (though I'm sensitive to the argument that changing a type class is not a cheap action, and may need to be done carefully).
It was not really about efficiency (though it may be), but about clarity. We can do this in a separate PR.
src/Data/Array/Polarized/Push.hs
Outdated
| -- Remark. In order for the function above to work, consume must forcibly | ||
| -- evaluate both tuples. If it was lazy, then we might not actually perform | ||
| -- @k1@ or @k2@ and the unsafe IO won't get done. In general, this makes me | ||
| -- think we haven't spelled out the careful rules of what consuming functions | ||
| -- must follow so that we can release a safe API. |
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.
Sure, but, on the other hand, you cannot not consume them (try to forget consume or something). That's what linearity does for you. The unsafe IO is (if I haven't messed up) fully encapsulated in the DArray abstraction. So types guarantee that you can't get this wrong. Therefore, this comment is superfluous
| -- Remark. In order for the function above to work, consume must forcibly | |
| -- evaluate both tuples. If it was lazy, then we might not actually perform | |
| -- @k1@ or @k2@ and the unsafe IO won't get done. In general, this makes me | |
| -- think we haven't spelled out the careful rules of what consuming functions | |
| -- must follow so that we can release a safe API. |
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.
Yes, I see xD *facepalms himself*. There's no way to write ((),()) %1-> () without using something unsafe that doesn't force the evaluation of both tuples.
src/Data/Array/Polarized/Push.hs
Outdated
| -- must follow so that we can release a safe API. | ||
|
|
||
| emptyWriter :: ArrayWriter a | ||
| emptyWriter = ArrayWriter (Unsafe.toLinear (const ())) 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.
There shouldn't be an Unsafe.toLinear in this module. If we are missing a way to consume an empty destination array, then let's add it to the destination array module.
src/Data/Array/Destination.hs
Outdated
| dropEmpty :: HasCallStack => DArray a %1-> () | ||
| dropEmpty = Unsafe.toLinear unsafeDrop where | ||
| unsafeDrop :: DArray a -> () | ||
| unsafeDrop (DArray ds) | ||
| | MVector.length ds > 0 = error "Destination.dropEmpty on non-empty array." | ||
| | otherwise = () |
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.
You know what? I don't think it's safe. We at least need to seq ds like this:
| dropEmpty :: HasCallStack => DArray a %1-> () | |
| dropEmpty = Unsafe.toLinear unsafeDrop where | |
| unsafeDrop :: DArray a -> () | |
| unsafeDrop (DArray ds) | |
| | MVector.length ds > 0 = error "Destination.dropEmpty on non-empty array." | |
| | otherwise = () | |
| dropEmpty :: HasCallStack => DArray a %1-> () | |
| dropEmpty = Unsafe.toLinear unsafeDrop where | |
| unsafeDrop :: DArray a -> () | |
| unsafeDrop (DArray ds) | |
| | MVector.length ds > 0 = error "Destination.dropEmpty on non-empty array." | |
| | otherwise = ds `seq` () |
But generally speaking, I'm wondering if we shouldn't replace newtype DArray a = DArray (MVector …) with data DArray a where { DArray :: MVector … -> DArray a }. It adds an indirection. But it will save some Unsafe.toLinear presumably. Maybe most. This may considerably reduce the kernel of trust, here.
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'll make that ^ an issue for later.
This PR flushes out the design laid out in the
better-push-arraybranch. [In an effort to avoid plagiarism: note that all of these are @aspiwack's ideas.] We represent push arrays with this clever little rank-2 thing:that takes a polymorphic conversion to a monoid for some element, and creates a monoid. This way it represents the monoidal concatenation of a certain natural (including zero) number of
as, in some unknown order.We instantiate this with a function that takes an
aand makes it anArrayWriter a:which holds the ingredients needed to write some number of elements, without holding the space to do so.
This is part 1/2 because I still have to add the fold functions.