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

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

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

Скажем, у нас есть простое 'привет мировая' программа:

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

Для меня действительно ли возможно создать тестовую обвязку, которая может работать main и проверьте, что монада ввода-вывода это возвращает корректное 'значение'? Или то, что монады, как предполагается, являются непрозрачными блоками вычисления, препятствуют тому, чтобы я делал это?

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

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

10
задан ctford 21 December 2009 в 12:08
поделиться

5 ответов

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

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

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

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

Для получения дополнительной информации я могу порекомендовать статью «Красавица в чудовище:

8
ответ дан 3 December 2019 в 23:50
поделиться

Сожалею, что вы не можете этого сделать.

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

Foreign.unsafePerformIO :: IO a -> a

: /

0
ответ дан 3 December 2019 в 23:50
поделиться

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

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

И опять же, можно использовать функцию assert:

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 будет достаточно умён, чтобы полностью пропустить любые фальшивые утверждения, которые он найдёт, если реальные утверждения определённо приведут к сбою вашей программы.

Этого, IMO, вряд ли будет достаточно - вам всё равно придётся проводить тестирование поведения в сложных сценариях - но я думаю, что это может помочь проверить, что основные предположения, которые делает код, верны.

.
0
ответ дан 3 December 2019 в 23:50
поделиться

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

.
1
ответ дан 3 December 2019 в 23:50
поделиться

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

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

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

.
4
ответ дан 3 December 2019 в 23:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: