Slightly generalizing unfold

為{幸葍}努か 提交于 2019-12-30 20:34:24

问题


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?


回答1:


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




回答2:


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.



来源:https://stackoverflow.com/questions/24519530/slightly-generalizing-unfold

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!