haskell functional-programming pointfree

¿Cómo funciona el "operador boobs" de Haskell en inglés no funcional?



functional-programming pointfree (3)

Lo más simple posible (.).(.) , También escrito (.:) veces, es composición de funciones cuando la función que se está componiendo en el lado derecho tiene dos argumentos faltantes. Si intenta utilizar (.) Directamente, no realiza una comprobación de tipo, aunque es bastante tentador debido a una idea de "fontanería" funcional.

Además, vale la pena pensar cómo (.:) es una especie de plomería opuesta a Data.Function.on .

flip on :: (a -> b) -> (b -> b -> c) -> (a -> a -> c) (.:) :: (a -> b) -> (c -> d -> a) -> (c -> d -> b)

on usa una función unaria para transformar las dos entradas de una función binaria. (.:) toma una función binaria y la usa para transformar una función unaria en una función binaria.

Esta pregunta ya tiene una respuesta aquí:

En esta respuesta https://stackoverflow.com/a/11006842/1065190 se menciona un "operador de búho":

absoluteError = ((.) . (.)) abs (-)

para expresar la función de error absoluto en notación sin puntos.

De todos modos, ¿cómo funciona esta notación realmente?

¿Podrías, por favor, explicarlo a un programador C ++ no funcional?


Entonces toma 2 funciones, f y g . Le da 2 argumentos a g y luego pasa el resultado a f

owl f g a b = f (g a b)

En tipos esto es

(a -> b) -> (c -> d -> a) -> c -> d -> b

Esto nos permite evitar nombrar valores intermedios en composiciones de funciones más largas ya que mucho código haskell se escribe algo así como

foo = bar . baz . quux 1

Ahora di que queríamos que foo diera argumentos de quux 2, no 1,

foo a b = let res = quux a b in bar . baz $ res

Esto es irritantemente largo, así que comparado con lo que tenemos antes, en cambio podemos escribir

(.:) = owl -- give a nice operator name foo = bar . baz .: quux

que es mucho más limpio

Una explicación larga (confusa) de cómo funciona:

Bien . es solo una función implementada algo como esto

(.) f g = /a -> f (g a)

Entonces ((.) . (.)) Es solo

compose = (.).(.) compose a = (.) ((.) a) -- expand the middle . compose a = (.) (/g v -> a (g v)) -- Expand the second (.) compose a = /g'' v'' -> (/g v -> a (g v)) (g'' v'') -- expand the first (.) compose a = /g'' v'' -> (/v -> a (g'' v'' v)) -- apply the first lambda to the second compose a g'' v'' v = a (g'' v'' v) -- Currying let''s us move the arguments to -- the other side

o cuando lo aplicamos en tu ejemplo

f = compose abs (-) f = /v'' v -> abs ((-) v'' v) f a b = abs ((-) a b) f a b = abs (a - b)


Asumiendo que puedes leer la notación lambda en Haskell, es muy simple:

/arguments -> body

Así que expresiones simples podrían ser:

/x -> x+1 /x y -> x+y

Lambdabot tiene un comando pointful que puede convertir pointfree en funciones puntuales. Expresado como una simple expresión lambda, se convierte en una función de orden superior bastante clara.

$ pointful "((.) . (.))" (/ i b c f -> i (b c f))

Entonces, ¿qué hace esto en inglés? Toma una función en su primer argumento "i" y la aplica al resultado de otra función "b" aplicada a los argumentos "c" y "f".

Entonces realmente es esta función una vez que la normalizas:

$ pointful "((.) . (.)) abs (-)" (/ c f -> abs (c - f))

Lo cual es bastante fácil de entender que ese es el error absoluto.

Si desea ver una derivación de lo que Lambdabot está haciendo detrás de las escenas, vea esta publicación , que es simplemente una reducción beta de los términos lambda. El único truco aquí es que estamos mezclando operadores de infijo y prefijo, el mismo término podría escribirse más claramente como:

(.) (.) (.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c

Entonces queda claro que es solo "composición de la composición de la función".