Traducción & # 8220; Por qué la programación funcional importa & # 8221; en Haskell
-
05-07-2019 - |
Pregunta
Para el enriquecimiento cultural e intelectual, he decidido aprender un poco de Haskell. He estado leyendo Hughes '" ¿Por qué importa la programación funcional " y estoy tratando de traducir su código en verdadero Haskell. A continuación, adjunto algunos de mis intentos (para las partes numéricas del documento; el algoritmo alfa-beta es aún más interesante, pero también tendría que escribir un evaluador de juegos desde cero).
En este punto, ha sido más un ejercicio en la sintaxis de Haskell que cualquier otra cosa. Ya he hecho cosas simples como traducir repetir
en el nativo de Haskell iteración
, traducir algunas funciones que usaban muchos paréntesis para la composición de la función (lo que las hace más libres de puntos). en el proceso), etc.
Pero mi código ciertamente funciona, y me pregunto si es lo suficientemente " Haskell-ish " ;. ¿Puede algún experto por ahí darme algunos consejos?
-- 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...\
No hay solución correcta
Otros consejos
4.1 Raíces cuadradas de Newton-Raphson
Estas dos líneas
sqroot a0 eps n = within eps (iterate (next n) a0)
relativesqrt a0 eps n = relative eps (iterate (next n) a0)
son casi idénticos, por lo que podría abstraer las cosas un paso más allá:
sqroot method a0 eps n = method eps (iterate (next n) a0)
relativesqrt = sqroot relative
withinsqrt = sqroot within
4.2 Diferenciación numérica
No veo el punto de tener h0
como un argumento para la función diferenciar
, ya que es solo el punto de partida para el 0
secuencia limitante (Lo mismo no es cierto para a0
en el caso de Newton-Rhapson, donde el punto de partida puede ser importante).
Creo que es igualmente apropiado abstraer la tasa a la que este límite se aproxima a cero:
differentiate rate f x = map (easydiff f x) (iterate rate 1)
Por supuesto, uno podría hacer ambas cosas:
differentiate rate h0 f x = map (easydiff f x) (iterate rate h0)
En cualquier caso, es una decisión bastante arbitraria.
4.2 Integración
Puedes usar
zipWith (+) (integ f a m fa fm) (integ f m b fm fb)
en lugar de
map (uncurry (+)) (zip (integ f a m fa fm) (integ f m b fm fb))
que creo que es más legible.
Para dentro de
y relativo
usaría la notación protegida:
within eps (a:b:rest)
| abs(a-b)<=eps = b
| otherwise = within eps (b:rest)
Para segundo
, ¡podrías escribir ! 1
. Supongo que especialmente esta última es una preferencia personal,
También debe dar las firmas de tipo.
Editar : si te gusta la ofuscación, prueba:
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
(Tipo marcado, no probado, y nunca sé si el filtro es correcto o si tiene que ser negado;)
Entonces, ¿es esta una traducción directa (es decir, tratar de mantener el código tan similar como sea posible) o incrustado (es decir, hacer tantos ajustes idiomáticos al código como sea posible, sin hacer que los ejemplos sean más difíciles de entender?) , por supuesto) uno?
Suponiendo que este " proyecto " todavía está en marcha ......