Обратите внимание на чистую функцию ниже, на императивном языке:
def foo(x,y):
x = f(x) if a(x)
if c(x):
x = g(x)
else:
x = h(x)
x = f(x)
y = f(y) if a(y)
x = g(x) if b(y)
return [x,y]
Эта функция представляет собой стиль, в котором вы должны постепенно обновлять переменные. В большинстве случаев этого можно избежать, но бывают ситуации, когда эта схема неизбежна - например, написание процедуры приготовления для робота, которая по своей сути требует серии шагов и решений. Теперь представьте, что мы пытались представить foo
в Haskell.
foo x0 y0 =
let x1 = if a x0 then f x0 else x0 in
let x2 = if c x1 then g x1 else h x1 in
let x3 = f x2 in
let y1 = if a y0 then f y0 else y0 in
let x4 = if b y1 then g x3 else x3 in
[x4,y1]
Этот код работает, но он слишком сложный и подвержен ошибкам из-за необходимости ручного управления числовыми тегами. Обратите внимание, что после x1
значение x0
никогда не должно использоваться снова, но оно все еще может. Если вы случайно его используете, это будет необнаруженная ошибка.
Мне удалось решить эту проблему, используя государственную монаду:
fooSt x y = execState (do
(x,y) <- get
when (a x) (put (f x, y))
(x,y) <- get
if c x
then put (g x, y)
else put (h x, y)
(x,y) <- get
put (f x, y)
(x,y) <- get
when (a y) (put (x, f y))
(x,y) <- get
when (b y) (put (g x, x))) (x,y)
Таким образом, необходимость отслеживания меток уходит, а также риск случайного использования устаревшей переменной. Но теперь код достаточно подробный и гораздо сложнее понять, в основном из-за повторения (x,y) <- get
.
Итак: что является более читаемым, элегантным и безопасным способом выражения этого шаблона?