Data.List
defines
unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
unfoldr f b = case f b of
Just (a,new_b) -> a : unfoldr f new_b
Nothing -> []
There are a number of functions that can almost be defined using unfoldr
, but have trouble at the very end of the list. A simple "fix" is
unfoldr' :: (b -> Either (a,b) [a]) -> b -> [a]
unfoldr' f b = case f b of
Left (a, new_b) -> a : unfoldr' f new_b
Right r -> r
Does this function have a standard name? Does it have nice properties and interact well with foldr
?
(this is more of a comment, with some code so it doesn't fit) Consider the test case that you mention in the comments,
f xs -- = zip (map reverse $ inits xs) (tails xs)
= unfoldr g (Just ([],xs))
where
g (Just (acc,xs@[])) = Just ( (acc,xs), Nothing)
g (Just (acc,xs@(x:t))) = Just ( (acc,xs), Just (x:acc, t) )
g Nothing = Nothing
the perceived problem is with the one extra processing beat to end the list, which forces us to use the nested Maybe
. Indeed it's easier with your function (there's no need for the nested Maybe
):
= unfoldr' h ([],xs)
where
h (acc,xs@[]) = Right [ (acc,xs) ] -- last tail
h (acc,xs@(x:t)) = Left ( (acc,xs), (x:acc,t) )
But we could also simplify the g
code another way:
= unfoldr (fmap g') (Just ([],xs))
where
g' (acc,xs@[]) = ( (acc,xs), Nothing) -- last element
g' (acc,xs@(x:t)) = ( (acc,xs), Just (x:acc, t) )
and use this as a skeleton for such functions, with the standard unfoldr
still. Or maybe one more Nothing
line of code in the definition of g
is, well, nothing to be concerned about.
But of course it's no replacement in the case where you really need to put a special tail into your list, not just one more element.
Similar to elgot from recursion-schemes, I think.
Expanding and looking again, it might just be apo
from the same package. That function has type (Unfoldable t, Foldable t) => (a -> Base t (Either t a)) -> a -> t
. Renaming a
to b
and taking t
as [a]
we get (b -> Base [a] (Either [a] b)) -> b -> [a]
.
Looking at the Base [a] (Either [a] b)
part requires referencing the source code, but gives us a data family with only a few constructors: Cons a (Left [a])
, Cons a (Right b)
, and Nil
. Now your type, Either (a, b) [a]
also has only a few constructors: Left (a, b)
, Right (a:[a])
, and Right []
. I think you can see that there is an isomorphism between the two types. Here's one side:
e2pe :: Either (a, b) [a] -> Prim [a] (Either [a] b)
e2pe (Left (x,y)) = Cons x $ Right y
e2pe (Right (x:xs)) = Cons x $ Left xs
e2pe (Right []) = Nil
My (unproven) claim is that unfoldr' f
= apo $ e2pe . f
.
Here's some tests, after defining your unfoldr'
and my e2pe
:
GHCi> let f n = case compare n 0 of { EQ -> Right []; LT -> Left (n, succ n); GT -> Left (n, pred n); }
GHCi> unfoldr' f 5
[5,4,3,2,1]
GHCi> apo (e2pe . f) 5
[5,4,3,2,1]
GHCi> unfoldr' f (-5)
[-5,-4,-3,-2,-1]
GHCi> apo (e2pe . f) (-5)
[-5,-4,-3,-2,-1]
GHCi> let f n = case compare n 0 of { EQ -> Right [0]; LT -> Left (n, succ n); GT -> Left (n, pred n); }
GHCi> unfoldr' f 5
[5,4,3,2,1,0]
GHCi> unfoldr' f (-5)
[-5,-4,-3,-2,-1,0]
GHCi> apo (e2pe . f) 5
[5,4,3,2,1,0]
GHCi> apo (e2pe . f) (-5)
[-5,-4,-3,-2,-1,0]
If my claim is correct, you've reinvented the apomorphism.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With