在正确方向迈出的一步
让我困惑的是getNextFile
。和我一起进入一个简化的世界,我们还没有处理 IO。类型是Maybe DataFile -> Maybe DataFile
。在我看来,这应该只是DataFile -> Maybe DataFile
,并且我将在这种调整是可能的假设下进行操作。和that看起来是一个不错的候选人unfoldr
。我要做的第一件事是制作我自己的 Expandr 简化版本,它不太通用,但使用起来更简单。
import Data.List
-- unfoldr :: (b -> Maybe (a,b)) -> b -> [a]
myUnfoldr :: (a -> Maybe a) -> a -> [a]
myUnfoldr f v = v : unfoldr (fmap tuplefy . f) v
where tuplefy x = (x,x)
现在的类型f :: a -> Maybe a
火柴getNextFile :: DataFile -> Maybe DataFile
getFiles :: String -> [DataFile]
getFiles = myUnfoldr getNextFile . getFirstFile
很漂亮,对吧?unfoldr
很像iterate
,除非一旦击中Nothing
,它终止列表。
现在,我们遇到了问题。IO
。我们怎样才能做同样的事情IO
扔在那里?甚至不think关于不可命名的函数。我们需要一个增强的展开器来处理这个问题。幸运的是,展开器的来源可供我们使用。
unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
unfoldr f b =
case f b of
Just (a,new_b) -> a : unfoldr f new_b
Nothing -> []
现在我们需要什么?健康剂量IO
. liftM2 unfoldr
almost为我们提供了正确的类型,但这次不会完全削减它。
一个实际的解决方案
unfoldrM :: Monad m => (b -> m (Maybe (a, b))) -> b -> m [a]
unfoldrM f b = do
res <- f b
case res of
Just (a, b') -> do
bs <- unfoldrM f b'
return $ a : bs
Nothing -> return []
这是一个相当简单的转变;我想知道是否有一些组合器可以完成同样的任务。
有趣的事实:我们现在可以定义unfoldr f b = runIdentity $ unfoldrM (return . f) b
让我们再次定义一个简化的myUnfoldrM
,我们只需撒上liftM
在那里:
myUnfoldrM :: Monad m => (a -> m (Maybe a)) -> a -> m [a]
myUnfoldrM f v = (v:) `liftM` unfoldrM (liftM (fmap tuplefy) . f) v
where tuplefy x = (x,x)
现在我们都准备好了,就像以前一样。
getFirstFile :: String -> IO DataFile
getNextFile :: DataFile -> IO (Maybe DataFile)
getFiles :: String -> IO [DataFile]
getFiles str = do
firstFile <- getFirstFile str
myUnfoldrM getNextFile firstFile
-- alternatively, to make it look like before
getFiles' :: String -> IO [DataFile]
getFiles' = myUnfoldrM getNextFile <=< getFirstFile
顺便说一下,我用以下命令对所有这些进行了类型检查data DataFile = NoClueWhatGoesHere
,以及类型签名getFirstFile
and getNextFile
,其定义设置为undefined
.
[编辑] 已更改myUnfoldr
and myUnfoldrM
表现得更像iterate
,包括结果列表中的初始值。
[编辑]关于展开的更多见解:
如果你很难把头转过来,科拉兹序列这可能是最简单的例子之一。
collatz :: Integral a => a -> Maybe a
collatz 1 = Nothing -- the sequence ends when you hit 1
collatz n | even n = Just $ n `div` 2
| otherwise = Just $ 3 * n + 1
collatzSequence :: Integral a => a -> [a]
collatzSequence = myUnfoldr collatz
记住,myUnfoldr
是“下一个种子”和“当前输出值”相同的情况的简化展开,就像 collatz 的情况一样。这种行为应该很容易看出myUnfoldr
的简单定义为unfoldr
and tuplefy x = (x,x)
.
ghci> collatzSequence 9
[9,28,14,7,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1]
更多,大多是不相关的想法
其余的与这个问题完全无关,但我就是忍不住沉思。我们可以定义myUnfoldr
按照myUnfoldrM
:
myUnfoldr f v = runIdentity $ myUnfoldrM (return . f) v
看起来熟悉?我们甚至可以抽象这个模式:
sinkM :: ((a -> Identity b) -> a -> Identity c) -> (a -> b) -> a -> c
sinkM hof f = runIdentity . hof (return . f)
unfoldr = sinkM unfoldrM
myUnfoldr = sinkM myUnfoldrM
sinkM
应该致力于“下沉”(与“提升”相反)形式的任何功能
Monad m => (a -> m b) -> a -> m c
.
自从Monad m
在这些功能中可以与Identity
单子约束sinkM
。然而,我什么也没看到 that sinkM
实际上会有用。