Перевод & # 8220; Почему важно функциональное программирование & # 8221; в Хаскелл
-
05-07-2019 - |
Вопрос
Для культурного и интеллектуального обогащения я решил немного изучить Haskell. Я читал Хьюза "Почему функциональное программирование имеет значение" и я пытаюсь перевести его код на настоящий Haskell. Ниже я приложил некоторые из моих попыток (для числовых частей статьи; алгоритм альфа-бета еще более интересен, но мне также пришлось бы написать игровой оценщик с нуля!). Р>
На данный момент это было скорее упражнение в синтаксисе Haskell, чем что-либо еще. Я уже сделал простые вещи, такие как перевод repeat
в нативный Haskell iterate
, перевод нескольких функций, которые использовали множество скобок, для компоновки функций (делая их более бессмысленными в процессе) и т. д.
Но мой код, безусловно, работает, и мне интересно, достаточно ли он "Haskell-ish". Могут ли какие-нибудь эксперты дать мне несколько советов?
-- 4.1 Newton-Raphson square roots
next n x = (x + n/x)/2.0
-- -- this is "iterate::(a->a)->a->[a]"
-- repeat f a = a : iterate f (f a)
within eps (a:b:rest) =
if abs(a-b) <= eps
then b
else within eps (b:rest)
sqroot a0 eps n = within eps (iterate (next n) a0)
relative eps (a:b:rest) =
if abs(a-b) <= eps*abs(b)
then b
else relative eps (b:rest)
relativesqrt a0 eps n = relative eps (iterate (next n) a0)
-- 4.2 numerical differentiation
easydiff f x h = (f (x+h) - f x) / h
differentiate h0 f x = map (easydiff f x) (iterate (/2) h0)
-- diff1a h0 eps f x = within eps (differentiate h0 f x)
diff1 h0 eps f = within eps . differentiate h0 f
elimerror n (a:b:rest) = (b*(2**n)-a)/(2**n-1) : elimerror n (b:rest)
-- need fromIntegral to make a non-integer out of the Int which comes out of round
order (a:b:c:rest) = fromIntegral (round (logBase 2 ((a-c)/(b-c)-1)))
improve s = elimerror (order s) s
--diff2a h0 eps f x = within eps (improve (differentiate h0 f x))
diff2 h0 eps f = within eps . improve . differentiate h0 f
--super s = map second (iterate improve s) -- how can we make this point-free?
super :: (RealFrac t, Floating t) => [t] -> [t]
-- w/o this it wants to be [double]->[double]
super = map second . iterate improve
-- second (a:b:rest) = b
second = head . tail
diff3 h0 eps f = within eps . super . differentiate h0 f
-- 4.3 integration
easyintegrate f a b = (f a + f b)*(b-a)/2
-- addpair becomes (uncurry (+))
integrate f a b = integ f a b (f a) (f b)
integ f a b fa fb =
(fa+fb)*(b-a)/2 : map (uncurry (+)) (zip (integ f a m fa fm) (integ f m b fm fb))
where m = (a+b)/2
fm = f m
-- test: following should be about pi
approxpi eps = within eps (improve (integrate (\x -> 4/(1+x*x)) 0 1))
superpi eps = within eps (super (integrate (\x -> 4/(1+x*x)) 0 1))
-- is there any way to keep track of the number of iterations? state monad, but seems like a lot of work...\
Нет правильного решения
Другие советы
4.1 Квадратные корни Ньютона-Рафсона
Эти две строки
sqroot a0 eps n = within eps (iterate (next n) a0)
relativesqrt a0 eps n = relative eps (iterate (next n) a0)
почти идентичны, так что вы можете абстрагироваться на шаг впереди:
sqroot method a0 eps n = method eps (iterate (next n) a0)
relativesqrt = sqroot relative
withinsqrt = sqroot within
4.2 Численное дифференцирование
Я не вижу смысла в том, чтобы h0
являлся аргументом функции дифференцировать
, поскольку это всего лишь отправная точка для 0
ограничивающая последовательность. (То же самое не верно для a0
в случае Ньютона-Рапсона, где может иметь значение отправная точка).
Я думаю, что в равной степени целесообразно абстрагироваться от скорости, с которой этот предел приближается к нулю.
differentiate rate f x = map (easydiff f x) (iterate rate 1)
Конечно, можно сделать и то, и другое:
differentiate rate h0 f x = map (easydiff f x) (iterate rate h0)
В любом случае, это довольно произвольное решение.
4.2 Интеграция
Вы можете использовать
zipWith (+) (integ f a m fa fm) (integ f m b fm fb)
вместо
map (uncurry (+)) (zip (integ f a m fa fm) (integ f m b fm fb))
который я считаю более читабельным.
Для в
и относительный
я бы использовал защищенную запись:
within eps (a:b:rest)
| abs(a-b)<=eps = b
| otherwise = within eps (b:rest)
За second
вы можете написать !! 1 код>. Тем более, что последний - личное предпочтение, я думаю.
Вы также должны указать сигнатуры типов.
Изменить . Если вы запутались, попробуйте:
within :: (Ord a, Num a) => a -> [a] -> a
within eps l@(_:xs) = snd. head . filter ((<= eps) . fst) $ zip zs xs
where
zs = zipWith (\ a b -> abs (a-b)) l xs
(Тип проверен, не проверен - и я никогда не знаю, правильно ли я получу фильтр или нужно его отменить;)
Роджер Костелло написал краткое изложение статьи Джона Хьюза из двух частей: статья перевод оригинального кода Миранды на Haskell. Вот часть первая и часть вторая его рецензии.
Итак, это прямой перевод (т. е. стараться, чтобы код выглядел максимально похожим) или встроенный (т. е. внесите в код как можно больше идиоматических изменений, не усложняя понимание примеров). конечно) один?
Предполагая, что этот " проект " все еще продолжается ......