2017-01-23 23 views
2

Чтобы проиллюстрировать точку с тривиальным примером, скажем, я реализовал filter:Как взаимодействовать с чистым алгоритмом в IO коде

filter :: (a -> Bool) -> [a] -> [a] 

И у меня есть предикат p, который взаимодействует с реальным миром:

p :: a -> IO Bool 

Как это сделать его работу с filter без написания отдельной реализации:

filterIO :: (a -> IO Bool) -> [a] -> IO [a] 

Предположительно, если я могу превратить p в p':

p': IO (a -> Bool) 

Тогда я могу сделать

main :: IO() 
main = do 
    p'' <- p' 
    print $ filter p'' [1..100] 

Но я не смог найти преобразование.

Отредактировано: Как человек указал в комментариях, такое преобразование не имеет смысла, так как это нарушило бы инкапсуляцию IO Монады.

Теперь вопрос в том, могу ли я структурировать свой код, чтобы чистые и IO-версии не полностью дублировали основную логику?

+0

Он уже реализован: 'filterM'. –

+0

Уверен, но, как сказано, это всего лишь надуманный пример. Является ли общее решение всегда реализовывать отдельно версию, которая взаимодействует с IO? Это будет очень шаблонный код для любого нетривиального алгоритма. – Chris

+0

Я думаю, что это было бы против использования «Монады». Что означает «a -> IO Bool», так это то, что вы подаете в качестве входных данных переменную 'a' и« она выполняет некоторую «IO», возвращающую bool ». Теперь вы хотите преобразовать это в «выполнение ввода IO общей функции». Но для выполнения функции, вероятно, нужно выполнить IO. Преобразование делает - afaik - никакого смысла вообще. –

ответ

5

Как это сделать его работу с фильтром без написания отдельной реализации

Это не представляется возможным, и факт такого рода вещи не представляется возможным является дизайн - Haskell устанавливает четкие ограничения на его типы, и вы должны подчиняться им. Вы не можете посыпать IO повсюду волей-неволей.

Теперь вопрос в том, могу ли я структурировать свой код, чтобы чистые и IO-версии не полностью дублировали основную логику?

Вас интересует filterM. Затем вы можете получить как функциональность filterIO, используя монаду IO, так и чистую функциональность, используя монаду Identity. Конечно, для чистого случая вам теперь придется заплатить дополнительную цену упаковки/разворачивания (или coerce ing) обертки Identity. (Сторона замечание: поскольку Identity является newtype это только стоимость читабельность кода, а не во время выполнения один.)

ghci> data Color = Red | Green | Blue deriving (Read, Show, Eq) 

Вот монадическая пример (обратите внимание, что строки, содержащие только Red, Blue и Blue пользователем -entered в командной строке):

ghci> filterM (\x -> do y<-readLn; pure (x==y)) [Red,Green,Blue] 
Red 
Blue 
Blue 
[Red,Blue] :: IO [Color] 

Вот чистый пример:

ghci> filterM (\x -> Identity (x /= Green)) [Red,Green,Blue] 
Identity [Red,Blue] :: Identity [Color] 
+0

Сноска: Я немного подумал о том, была ли версия 'filterM' обобщена на' Applicative'; затем я просмотрел его и нашел [это сам 'filterM' (https://hackage.haskell.org/package/base-4.9.1.0/docs/Control-Monad.html#v:filterM)! – duplode

+0

@Alec Спасибо! Монада «Идентичность» - это то, чего мне не хватало. – Chris

+0

Незначительная коррекция: чистый пример также должен использовать 'filterM' – Chris

1

в ALR eady сказал, вы можете использовать filterM для этой конкретной задачи.Однако, как правило, лучше придерживаться характерного строгого разделения Haskell IO и расчетов. В вашем случае, вы можете просто поставить галочку в один присесте все необходимые IO, а затем сделать интересную фильтрацию в хорошем, надежном, легко проверяемом чистом коде (т.е. здесь, просто с нормальным filter):

type A = Int 
type Annotated = (A, Bool) 

p' :: Annotated -> Bool 
p' = snd 

main :: IO() 
main = do 
    candidates <- forM [1..100] $ \n -> do 
     permitted <- p n 
     return (n, permitted) 
    print $ fst <$> filter p' candidates 

Здесь мы сначала аннотируем каждое число флагом, указывающим, что говорит окружающая среда. Затем этот флаг можно просто считывать на этапе фактической фильтрации, не требуя дальнейшего ввода-вывода.

Короче говоря, это было бы написано:

main :: IO() 
main = do 
    candidates <- forM [1..100] $ \n -> (n,) <$> p n 
    print $ fst <$> filter snd candidates 

Хотя это не представляется возможным для этой конкретной задачи, я бы также добавить, что вы можете в принципе достичь IO Разделения с чем-то как ваш p'. Это требует, чтобы тип A был «достаточно мал», чтобы вы могли оценить предикат с всеми значениями, которые возможны вообще. Например,

import qualified Data.Map as Map 

type A = Char 

p' :: IO (A -> Bool) 
p' = (Map.!) . Map.fromList <$> mapM (\c -> (c,) <$> p c) ['\0'..] 

Это оценивает предикат один раз для всех из 1114112 символов существуют и сохраняет результаты в виде таблицы перекодировки.