Conversation
2012c45 to
a295889
Compare
| 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.
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.
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.
In this case, though, this ought to be a simpler foldMap do we have foldMap?
There was a problem hiding this comment.
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.
Or did you mean a foldMap on Pull arrays? @aspiwack
There was a problem hiding this comment.
I meant foldMap on lists. But you are right that there is some tension in the types. Let's forget about this for now.
| -- 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.
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.
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.
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.
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.
I like it because it's a tiny bit more DRY even though the chance of changing the implementation is close to zero.
| 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.
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.
| 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.
In this case, though, this ought to be a simpler foldMap do we have foldMap?
| 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.
| | 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.
Base has an stimes function for this. Maybe we should have one too?
There was a problem hiding this comment.
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.
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.
| -- 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.
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.
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.
| -- 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.
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.
| 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.
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.
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.