Перевод "Почему функциональное программирование имеет значение" в Haskell - PullRequest
14 голосов
/ 16 июня 2009

Для культурного и интеллектуального обогащения я решил немного изучить 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 ]

10 голосов
/ 16 июня 2009

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 являлся аргументом функции differentiate, поскольку это всего лишь отправная точка для ограничивающей последовательности 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))

который я считаю более читабельным.

4 голосов
/ 16 июня 2009

Для within и relative Я бы использовал защищенную запись:

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

(Тип проверен, не проверен - и я никогда не знаю, правильно ли я получу фильтр или нужно его отменить;)

2 голосов
/ 21 июля 2014

Роджер Костелло написал краткое изложение статьи Джона Хьюза , состоящей из двух частей, переводя исходный код Миранды на Хаскелл. Вот часть первая и часть вторая его рецензии.

0 голосов
/ 14 мая 2010

Итак, это прямой перевод (т. Е. Попытаться сделать код максимально похожим) или встроенный (т. Е. Сделать как можно больше идиоматических изменений в коде, не усложняя понимание примеров). , конечно) один?

Предполагая, что этот "проект" все еще продолжается ......

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...