2009-12-20 4 views
10

Haskell - это чистый функциональный язык, что означает, что функции Haskell не имеют побочных эффектов. I/O реализуется с использованием монад, представляющих куски вычислений ввода-вывода.Можно ли проверить возвращаемое значение функций ввода/вывода Haskell?

Можно ли проверить возвращаемое значение функций ввода/вывода Haskell?

Допустим, мы имеем простую программу «привет мир»:

main :: IO() 
main = putStr "Hello world!" 

это возможно для меня, чтобы создать тестовую, который может работать main и проверить, что/O монады I возвращает правильный 'стоимость'? Или тот факт, что монады должны быть непрозрачными блоками вычислений, мешает мне это делать?

Примечание. Я не пытаюсь сравнивать возвращаемые значения операций ввода-вывода. Я хочу сравнить возвращаемое значение функций ввода/вывода - сама монада ввода-вывода.

Поскольку в Haskell I/O возвращается, а не выполняется, я надеялся изучить кусок вычисления ввода-вывода, возвращенный функцией ввода-вывода, и посмотреть, правильно ли оно было. Я думал, что это может позволить тестировать модули ввода/вывода таким образом, чтобы они не были в императивных языках, где I/O является побочным эффектом.

+1

Монады не обязательно являются «непрозрачными» блоками вычислений. Например, монады List и Maybe имеют видимое поведение приложения. Монада IO - единственная (я знаю), специально разработанная для изолирования логики программы от некоторого ее поведения. –

+1

Control.Monad.ST также довольно непрозрачен. – ephemient

+0

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

ответ

8

Способ, которым я хотел бы это сделать, - создать мою собственную монаду IO, содержащую действия, которые я хотел бы моделировать. Я бы выполнил монадические вычисления, которые я хочу сравнить в своей монаде, и сравнить эффекты, которые у них были.

Приведем пример. Предположим, я хочу моделировать печатные материалы. Тогда я могу моделировать мой IO монада как это:

data IO a where 
    Return :: a -> IO a 
    Bind :: IO a -> (a -> IO b) -> IO b 
    PutChar :: Char -> IO() 

instance Monad IO where 
    return a = Return a 
    Return a >>= f = f a 
    Bind m k >>= f = Bind m (k >=> f) 
    PutChar c >>= f = Bind (PutChar c) f 

putChar c = PutChar c 

runIO :: IO a -> (a,String) 
runIO (Return a) = (a,"") 
runIO (Bind m f) = (b,s1++s2) 
    where (a,s1) = runIO m 
     (b,s2) = runIO (f a) 
runIO (PutChar c) = ((),[c]) 

Вот как я бы сравнить эффекты:

compareIO :: IO a -> IO b -> Bool 
compareIO ioA ioB = outA == outB 
    where ioA = runIO ioA ioB 

Есть вещи, которые этот вид модели не обрабатывать. Вход, например, сложный. Но я надеюсь, что это подойдет вам. Следует также упомянуть, что есть более умные и эффективные способы моделирования эффектов таким образом. Я выбрал именно этот путь, потому что я думаю, что это самый простой способ понять.

Для получения дополнительной информации я могу порекомендовать статью «Красота в зверю: функциональная семантика для неуклюжий отряд», которую можно найти на this page вместе с некоторыми другими соответствующими документами.

+1

Я считаю, что это ответ, который касается намерений OP. В этом контексте фраза «возвращаемое значение» немного запутанна, но я думаю, что ctford означает возвращаемое значение в смысле возвращаемого значения от функции, такой как 'putStr'. То есть, сравнение самих действий ИО, а не их результатов. – Dan

+0

@ Дань. Да, это смысл, который я имел в виду. Трудно ясно говорить о чистом функциональном IO :) – ctford

0

Прошу прощения, что вы не можете этого сделать.

unsafePerformIO В основном, давайте сделаем это. Но я бы предпочел, чтобы вы делали , а не.

Foreign.unsafePerformIO :: IO a -> a 

:/

+0

почему бы и нет: 'test = main >> = \ d -> return $ _test_value_ d' ?? –

+0

Итак, нет способа использовать unsafePerformIO в моем тестовом коде, но не в моем производственном коде для программного тестирования IO? – ctford

+1

@ctford: я бы действительно предложил сделать ваш тестовый код в монаде IO. – yairchu

4

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

Стоит отметить, что в данном примере вы показываете, значение main имеет тип IO(), что означает «Я действие ввода-вывода, которые, при выполнении, делает некоторые I/O, а затем возвращает значение типа (). " Тип () произносится как «единица», и есть только два значения этого типа: пустой кортеж (также написанный () и произносимый «единица») и «нижний», который является именем Хаскелла для вычисления, которое не заканчивается или иным образом не идет неправильно.

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

+1

Я надеялся проверить, что действие IO было правильным, в отличие от того, что возвращает действие (что, как вы правильно указываете, в этом случае ничего не значит). Я надеялся различать действие, которое пишет «Привет, мир!». в stdout и один, который пишет «Foo» в stdout. Я понимаю, что то, что я хочу сделать, несколько необычно и теоретично. – ctford

+0

ОК, я не понял. Ваш лучший выбор может быть monadic quickcheck (см. Отдельный ответ). Если вы попробуете его, сообщите нам, как вы разобрались. Также вы можете попробовать и посмотреть, сможете ли вы получить советы от донов, которые провели много интересного тестирования с помощью xmonad. –

0

Мне нравится this answer к аналогичному вопросу о SO и комментариях к нему. В принципе, IO обычно производит некоторые изменения, которые могут быть замечены из внешнего мира; ваше тестирование должно быть связано с тем, что это изменение кажется правильным. (Например, была создана правильная структура каталогов и т. Д.)

В принципе, это означает «поведенческое тестирование», которое в сложных случаях может быть довольно болезненным. Это является частью причины, по которой вы должны ограничить IO-специфическую часть вашего кода до минимума и максимально использовать логику до чистых (следовательно, супер легко проверяемых) функций.

Опять же, вы можете использовать функцию утверждать, что:

actual_assert :: String -> Bool -> IO() 
actual_assert _ True = return() 
actual_assert msg False = error $ "failed assertion: " ++ msg 

faux_assert :: String -> Bool -> IO() 
faux_assert _ _ = return() 

assert = if debug_on then actual_assert else faux_assert 

(Вы можете определить debug_on в отдельном модуле, построенном непосредственно перед сборкой с помощью сценария сборки Кроме того, это очень вероятно. предоставленный в более полированной форме пакетом на Hackage, если не стандартная библиотека ... Если кто-то знает о таком инструменте, отредактируйте этот пост/комментарий, чтобы я мог редактировать.)

I думаю GHC будет быть достаточно умным, чтобы пропустить любые поддельные утверждения, которые он находит полностью, но фактические утверждения будут определяться После сбоя ваша программа выйдет из строя.

Это очень маловероятно для ИМО - вам все равно придется выполнять поведенческое тестирование в сложных сценариях, но я думаю, это может помочь проверить правильность основных допущений, которые делает код.

1

Вы можете проверить монадический код с QuickCheck 2. Прошло много времени с тех пор, как я прочитал эту статью, поэтому не помню, относится ли она к действиям IO или к каким монадическим вычислениям она может быть применена. Кроме того, возможно, вам сложно выразить ваши модульные тесты как свойства QuickCheck. Тем не менее, как очень довольный пользователь QuickCheck, я скажу, что это лот лучше, чем ничего не делать, кроме как взломать с помощью unsafePerformIO.

+0

QuickCheck - это здорово. – ctford

Смежные вопросы