Traduire «Pourquoi la programmation fonctionnelle est importante» en Haskell
-
05-07-2019 - |
Question
Pour un enrichissement culturel et intellectuel, j'ai décidé d'apprendre un peu de Haskell. J'ai lu la "Pourquoi la programmation fonctionnelle est-elle importante pour " et j'essaye de traduire son code en vrai Haskell. J'ai joint quelques-unes de mes tentatives ci-dessous (pour les parties numériques du document; l'algorithme alpha-bêta est encore plus intéressant, mais je devrais aussi écrire un évaluateur de jeu à partir de rien!).
À ce stade, c’est plus un exercice de syntaxe Haskell qu’autre chose. J'ai déjà fait des choses simples, telles que traduire repeat
dans le iterate
natif de Haskell, traduire quelques fonctions utilisant beaucoup de parenthèses pour la composition (les rendant ainsi plus libres de points en cours), etc.
Mais mon code fonctionne certainement, et je me demande s'il est suffisamment "Haskell-ish". Des experts peuvent-ils me donner des indices?
-- 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...\
Pas de solution correcte
Autres conseils
4.1 Racines carrées Newton-Raphson
Ces deux lignes
sqroot a0 eps n = within eps (iterate (next n) a0)
relativesqrt a0 eps n = relative eps (iterate (next n) a0)
sont presque identiques, vous pouvez donc résumer les choses un peu plus loin:
sqroot method a0 eps n = method eps (iterate (next n) a0)
relativesqrt = sqroot relative
withinsqrt = sqroot within
4.2 Différenciation numérique
Je ne vois pas l'intérêt d'avoir h0
en tant qu'argument de la fonction différencier
car ce n'est que le point de départ du 0
. séquence limitante. (Il n'en va pas de même pour a0
dans le cas Newton-Rhapson, où le point de départ pourrait avoir de l'importance).
Je pense qu’il est également approprié d’abstraire sur le taux auquel cette limite est proche de zéro:
differentiate rate f x = map (easydiff f x) (iterate rate 1)
Bien sûr, on peut faire les deux:
differentiate rate h0 f x = map (easydiff f x) (iterate rate h0)
En tout cas, c'est une décision assez arbitraire.
Intégration 4.2
Vous pouvez utiliser
zipWith (+) (integ f a m fa fm) (integ f m b fm fb)
au lieu de
map (uncurry (+)) (zip (integ f a m fa fm) (integ f m b fm fb))
qui, à mon avis, est plus lisible.
Pour dans
et relatif
, j'utiliserais la notation protégée:
within eps (a:b:rest)
| abs(a-b)<=eps = b
| otherwise = within eps (b:rest)
Pendant seconde
, vous pouvez écrire !! 1
. Surtout ce dernier est la préférence personnelle, je suppose.
Vous devriez également donner les signatures de type.
Modifier : si vous êtes obsolète, essayez:
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
(Type vérifié, non testé - et je ne sais jamais si le filtre est correct ou s'il doit être annulé;)
Roger Costello a rédigé un résumé en deux parties du document traduction du code Miranda original en Haskell. Voici la la première partie et deuxième partie de son récit.
Donc, est-ce une traduction directe (essayez de garder le code aussi similaire que possible) ou incorporée (c'est-à-dire, apportez autant de modifications idiomatiques que possible au code, sans rendre les exemples plus difficiles à comprendre, , bien sûr) un?
En supposant que ce " projet " se poursuit ......