-
Notifications
You must be signed in to change notification settings - Fork 23
Unfoldable1 #19
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
Comments
I'm not too sure what the signature for this would be, it's something I've considered before too. Closest thing I thought of is it'd be like the reverse of a fold, where you pass a "zero element" in along with the function that actually unfolds. |
Actually, I guess the simplest way would be to just turn the class Unfoldable1 t where
unfoldr1 :: forall a b. (b -> Tuple a (Maybe b)) -> b -> t a So the step function has to always return a value, and just the next step becomes optional. The other thing I mentioned on Slack: class Unfoldable1 t where
unfoldr1 :: forall a b c. (b -> Tuple a c) -> (c -> Maybe (Tuple a c)) -> b -> t a Also works and potentially gives us a little more flexibility both in writing more efficient unfolds (when the thing unfolded is like instance unfoldable1NonEmpty :: Unfoldable f => Unfoldable1 (NonEmpty f) where
unfoldr1 f g b = case f b of Tuple a c -> NonEmpty a (unfoldr g c) Which I think is impossible with the other definition. The tradeoff being callers now have to provide two step functions rather than one. |
What is wrong with the more obvious (to me)? class Unfoldable1 t where
unfoldr1 :: forall a b. (b -> Maybe (Tuple a b)) -> b -> a -> t a
instance unfoldable1NonEmpty :: Unfoldable f => Unfoldable1 (NonEmpty f) where
unfoldr1 f b a = a :| unfoldr f b |
(presumably this is going to be used to convert among non-empty structures, so I assume that our |
I didn't like that approach as it puts more work on the caller to partially deconstruct the thing that is being unfolded from, rather than that being part of the process of unfolding. You could argue that if we were going to do something with that form, then there's not really a reason for class NonEmpty t where
mkNonEmpty :: forall f a. Foldable f => a -> f a -> t a |
I have some more thoughts that I'll write up later, but it looks like the turned-inside-out ( |
Cool, I too have some thoughts that I'll try to get down later. |
Also, glad to hear that the "inside-out" is the right way to go about things, as the alternative was at least esthetically a bit displeasing. |
spitballing here: module Main where
import Prelude
import Data.Maybe (Maybe(..))
import Data.Tuple (Tuple(..))
import Data.Unfoldable (class Unfoldable, unfoldr, none)
import Data.NonEmpty (NonEmpty, (:|))
class Unfoldable1 t where
unfoldr1 :: forall a b. (b -> Tuple a (Maybe b)) -> b -> t a
instance unfoldable1NonEmpty :: Unfoldable f => Unfoldable1 (NonEmpty f) where
unfoldr1 = unfoldr1NonEmpty
unfoldr1NonEmpty
:: forall a b f
. Unfoldable f
=> (b -> Tuple a (Maybe b)) -> b -> NonEmpty f a
unfoldr1NonEmpty f b = case f b of
Tuple a Nothing -> a :| none
Tuple a (Just b) -> a :| unfoldr g b
where
g :: b -> Maybe (Tuple a b)
g b' = case f b' of
Tuple a Nothing -> Nothing
Tuple a (Just b'') -> Just $ Tuple a b'' (Try PureScript is giving me "failed to create gist") |
I figured out a sensible instance unfoldable1NonEmpty :: Unfoldable f => Unfoldable1 (NonEmpty f) where
unfoldr1 f b =
case f b of
Tuple a Nothing -> NonEmpty a none
Tuple a (Just b') -> NonEmpty a (unfoldr (flip bind go) (Just b'))
where
go b' = case f b' of
Tuple a Nothing -> Just (Tuple a Nothing)
Tuple a (Just b'') -> Just (Tuple a (Just b'')) The reason I ended up preferring this version is that it admits reasonable instances for both NEL and Cofree: instance unfoldable1Cofree :: Alternative f => Unfoldable1 (Cofree f) where
unfoldr1 f = buildCofree \b -> maybe empty pure <$> f b The other versions I tried had problems with because either the ... Oh, I see you just posted, lemme read that before continuing 😉 |
The version of |
Ah, I have another one coming shortly that resolves that. I'll compare to yours. |
This should result in no dropped elems: unfoldr1NonEmpty
:: forall a b f
. Unfoldable f
=> (b -> Tuple a (Maybe b)) -> b -> NonEmpty f a
unfoldr1NonEmpty f b = case f b of Tuple a mb -> a :| unfoldr g mb
where
g :: Maybe b -> Maybe (Tuple a (Maybe b))
g Nothing = Nothing
g (Just b') = Just $ f b' |
Or, even simpler: unfoldr1NonEmpty
:: forall a b f
. Unfoldable f
=> (b -> Tuple a (Maybe b)) -> b -> NonEmpty f a
unfoldr1NonEmpty f b = case f b of Tuple a mb -> a :| unfoldr (map f) mb |
Golfing here, apologies for the noise, but here is a terser form: unfoldr1NonEmpty f b = uncurry (:|) $ unfoldr (map f) <$> f b |
Hmm, unfortunately I lost a bunch of my workings attempting to show the pitfalls of the various other definitions... but anyway, instead of pointing out the problems with them, I have another reason to like Unfortunately it's not actually The NonEmpty instance is nice 👍I think I just got too zoned in on whatever I was trying plus going through all kinds of permutations 😄 More golf: unfoldr1NonEmpty f = uncurry (:|) <<< map (unfoldr <$> f) <<< f 😆 |
Well, looks like we ended up basically at the same result through our golf. I actually rejected the point-free form in the end, but I could go either way. This is good! You going to do a PR? |
Yup, I'll put one together now. I think this library will just have the class and the instances will go elsewhere afterwards. |
Yes, I think that works given the dependency graph. Happy to start throwing some PRs around the other libs. |
As a counterpart to
Foldable1
, I believe anUnfoldable1
makes sense.The text was updated successfully, but these errors were encountered: