шаг в правильном направлении
Что меня озадачивает, так это getNextFile
. Шагните в упрощенный мир со мной, где мы еще не имеем дело с IO. Тип Maybe DataFile -> Maybe DataFile
. На мой взгляд, это должно быть просто DataFile -> Maybe DataFile
, и я буду работать в предположении, что такая корректировка возможна. И , что выглядит хорошим кандидатом на unfoldr
. Первое, что я собираюсь сделать, - это сделать свою собственную упрощенную версию unfoldr, которая является менее общей, но более простой в использовании.
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
, брошенным туда? Даже не думайте о функции, которая не будет названа. Нам нужен расширенный код, чтобы справиться с этим. К счастью, источник для unfoldr доступен нам.
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
почти дает нам правильный тип, но на этот раз не совсем удастся.
Фактическое решение
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
и getNextFile
с их определениями, установленными на undefined
.
[править] изменил myUnfoldr
и myUnfoldrM
, чтобы вести себя больше как iterate
, включая начальное значение в списке результатов.
[править] Дополнительная информация о развертывании:
Если вам трудно сложить голову, развернувшаяся последовательность Collatz , возможно, является одним из самых простых примеров.
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
- это упрощенное разворачивание для случаев, когда «следующее начальное число» и «значение выходного тока» совпадают, как в случае с коллатцем. Такое поведение должно быть легко увидеть, учитывая простое определение myUnfoldr
в терминах unfoldr
и 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
. Однако, Я не вижу ничего , для которого sinkM
было бы на самом деле полезно.