2013-11-13 3 views
6

Извините, если это общий вопрос. У меня есть этот простой IO() функции:Строки трубопроводов haskell в IO

greeter :: IO() 
greeter = do 
    putStr "What's your name? " 
    name <- getLine 
    putStrLn $ "Hi, " ++ name 

Теперь я хочу, чтобы позвонить greeter и в то же время указать параметр, который будет предварительно заполнить getLine, так что я на самом деле не нужно взаимодействовать. Я полагаю, что-то вроде функции

IOwithinputs :: [String] -> IO() -> IO() 

, то я бы сделал

IOwithinputs ["Buddy"] greeter 

, который будет производить IO действий, не требующих ввода пользователя, который будет выглядеть примерно так:

What's your name? 
Hi, Buddy 

Я хочу для этого без изменения оригинальной функции IO()greeter. Я также не хочу компилировать greeter и вводить данные из командной строки. Я не вижу ничего подобного IOwithinputs в Hoogle. (withArgs мучительно набрано и названо, но совсем не то, что я хочу.) Есть ли простой способ сделать это? Или это невозможно по какой-то причине? Для этого нужны Pipes?

+2

Я не думаю, что это возможно без изменения 'greeter', к сожалению.'getLine' будет * всегда * читать из' stdin', который является типом пользователя. Самый простой способ изменить это - использовать вместо этого 'hGetLine', который позволяет указать, с какой' Handle' вы хотите получить линию. Возможно также (я очень не уверен в этом), чтобы сделать некоторые манипуляции на низком уровне и настроить перенаправление себя с помощью модуля 'GHC.IO.Handle', но это, безусловно, последнее, что вы хотите сделать. – kqr

+1

Я не знаю, возможно ли это (сначала я попытался найти способ записи в 'stdin', но это не позволяет вам), b ut это не то, для чего предназначена библиотека' pipe'. 'pipe' предназначен для написания программы, в которой вы отделяете генераторы данных от шагов обработки данных. Вы можете использовать его с IO (и это часто бывает), я бы рекомендовал [tutorial] (http://hackage.haskell.org/package/pipes-4.0.0/docs/Pipes-Tutorial.html) – bheklilr

+0

Извините, Я не понимаю, что разумно в этой идее. Для меня это звучит просто как ужасный хак. Если аргумент 'IO()' должен иметь дело с данными из других функций Haskell, то почему он не принимает его как параметр? 'IOwithinputs :: [String] -> ([String] -> IO()) -> IO()'. (Обратите внимание, что тогда, в основном, 'IOwithinputs ≡ flip ($)'.) – leftaroundabout

ответ

2

Я не думаю, что это легко сделать, как вы просите, но вы можете сделать следующий:

greeter' :: IO String -> IO() 
greeter' ioS = do 
    putStr "What's your name? " 
    name <- ioS 
    putStrLn $ "Hi, " ++ name 

greeter :: IO() 
greeter = greeter' getLine 

ioWithInputs :: Monad m => [a] -> (m a -> m()) -> m() 
ioWithInputs s ioS = mapM_ (ioS.return) s 

и проверить:

> ioWithInputs ["Buddy","Nick"] greeter' 
What's your name? Hi, Buddy 
What's your name? Hi, Nick 

и даже более смешно с эмуляции ответа:

> ioWithInputs ["Buddy","Nick"] $ greeter' . (\s -> s >>= putStrLn >> s) 
What's your name? Buddy 
Hi, Buddy 
What's your name? Nick 
Hi, Nick 
2

Как уже отмечалось, нет чистый способ «имитировать» IO, если вы уже используете вещи Лик e getLine и putStrLn. Вы должны изменить greeter. Вы можете использовать версии hGetLine и hPutStr и выкрикнуть IO с подделкой Handle, или вы можете использовать метод Purify Code with Free Monads.

Это гораздо более сложный, но более общий и обычно подходит для такого рода насмешек, особенно когда он становится более сложным. Я кратко объясню это ниже, хотя детали несколько сложны.

Идея состоит в том, что вы будете создавать свою собственную «фальшивую IO» монаду, которая может быть «интерпретирована» несколькими способами. Первичная интерпретация заключается в том, чтобы использовать обычный IO. Издевательская интерпретация заменяет getLine некоторыми фальшивыми линиями и отгоняет все до stdout.

Мы будем использовать пакет free. Первый шаг - описать ваш интерфейс с помощью Functor. Основное понятие состоит в том, что каждая команда является ветвью вашего типа данных-функтора и что «слот» функтора представляет собой «следующее действие».

{-# LANGUAGE DeriveFunctor #-} 

import   Control.Monad.Trans.Free 

data FakeIOF next = PutStr String next 
        | GetLine (String -> next) 
        deriving Functor 

Эти конструкторы почти как регулярные IO функций с точки зрения кого-то строит FakeIOF если вы игнорируете следующее действие. Если мы хотим PutStr, мы должны предоставить String.Если мы хотим GetLine, мы предоставляем функцию, которая дает следующее действие при задании String.

Теперь нам нужно немного запутать шаблон. Мы используем функцию liftF, чтобы превратить наш функтор в монаду FreeT. Обратите внимание, что мы предоставляем () в качестве следующего действия на PutStr и id в качестве нашей функции String -> next. Оказывается, они дают нам правильные «возвращаемые значения», если мы подумаем о том, как будет вести себя наш FakeIOMonad.

-- Our FakeIO monad 
type FakeIO a = FreeT FakeIOF IO a 

fPutStr :: String -> FakeIO() 
fPutStr s = liftF (PutStr s()) 

fGetLine :: FakeIO String 
fGetLine = liftF (GetLine id) 

Используя эти мы можем построить любой функциональности мы, как и переписать greeter с минимальными изменениями.

fPutStrLn :: String -> FakeIO() 
fPutStrLn s = fPutStr (s ++ "\n") 

greeter :: FakeIO() 
greeter = do 
    fPutStr "What's your name? " 
    name <- fGetLine 
    fPutStrLn $ "Hi, " ++ name 

Это может выглядеть немного волшебный --- мы используем do обозначения без определения Monad экземпляра. Фокус в том, что FreeT f m является Monad для любых Monadm и Functor f`.

Это завершает нашу функцию "maceded" greeter. Теперь мы должны интерпретировать его так или иначе, поскольку мы практически не реализовали функциональность. Чтобы написать интерпретатор, мы используем функцию iterT от Control.Monad.Trans.Free. Это полностью общий тип выглядит следующим образом

iterT 
    :: (Monad m, Functor f) => (f (m a) -> m a) -> FreeT f m a -> m a 

Но когда мы применяем его к нашему FakeIO монады выглядит

iterT 
    :: (FakeIOF (IO a) -> IO a) -> FakeIO a -> IO a 

, который намного лучше. Мы предоставляем ему функцию, которая принимает FakeIOF функторов, заполненных IO актонами в позиции «следующего действия» (как это получилось) до простого IO действия и iterT сделает магию превращения FakeIO в реальную IO.

Для нашего интерпретатора по умолчанию это очень просто.

interpretNormally :: FakeIO a -> IO a 
interpretNormally = iterT go where 
    go (PutStr s next) = putStr s >> next -- next :: IO a 
    go (GetLine doNext) = getLine >>= doNext -- doNext :: String -> IO a 

Но мы также можем сделать издевательский интерпретатор. Мы будем использовать средства IO для хранения некоторого состояния, в частности циклической очереди поддельных ответов.

newQ :: [a] -> IO (IORef [a]) 
newQ = newIORef . cycle 

popQ :: IORef [a] -> IO a 
popQ ref = atomicModifyIORef ref (\(a:as) -> (as, a)) 

interpretMocked :: [String] -> FakeIO a -> IO a 
interpretMocked greetings fakeIO = do 
    queue <- newQ greetings 
    iterT (go queue) fakeIO 
    where 
    go _ (PutStr s next) = putStr s >> next 
    go q (GetLine getNext) = do 
     greeting <- popQ q    -- first we pop a fresh greeting 
     putStrLn greeting     -- then we print it 
     getNext greeting     -- finally we pass it to the next IO action 

и теперь мы можем проверить эти функции

λ> interpretNormally greeter 
What's your name? Joseph 
Hi, Joseph. 

λ> interpretMocked ["Jabberwocky", "Frumious"] (greeter >> greeter >> greeter) 
What's your name? 
Jabberwocky 
Hi, Jabberwocky 
What's your name? 
Frumious 
Hi, Frumious 
What's your name? 
Jabberwocky 
Hi, Jabberwocky 
0

После того, как вы в IO вы не можете изменить то, как вы получите вход. Чтобы ответить на ваш вопрос о pipes, да можно абстрагироваться от входа путем определения Consumer:

import Pipes 
import qualified Pipes.Prelude as P 

greeter :: Consumer String IO() 
greeter = do 
    lift $ putStr "What's your name? " 
    name <- await 
    lift $ putStrLn $ "Hi, " ++ name 

После этого вы можете либо указать, чтобы использовать командную строку в качестве входных данных:

main = runEffect $ P.stdinLn >-> greeter 

.. или использовать чистый набор строк в качестве входных данных:

main = runEffect $ each ["Buddy"] >-> greeter