Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slightly generalizing unfold

Tags:

haskell

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?

like image 544
dfeuer Avatar asked Jul 01 '14 21:07

dfeuer


2 Answers

(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.

like image 115
Will Ness Avatar answered Sep 23 '22 10:09

Will Ness


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.

like image 2
Boyd Stephen Smith Jr. Avatar answered Sep 21 '22 10:09

Boyd Stephen Smith Jr.