haskell - signo - simbolo del dolar
Cuál es la diferencia entre.(punto) y $(signo de dólar)? (12)
Haskell: diferencia entre
.
(punto) y$
(signo de dólar)¿Cuál es la diferencia entre el punto
(.)
Y el signo de dólar($)
? Según tengo entendido, ambos son azúcares sintácticos por no necesitar usar paréntesis.
No son azúcares sintácticos por no tener que usar paréntesis, son funciones, infijadas, por lo que podemos llamarlos operadores.
(.)
es la función de composición. Asi que
result = (f . g) x
es lo mismo que construir una función que pasa el resultado de su argumento pasado a g
en f
.
h = /x -> f (g x)
result = h x
($)
es una función de aplicación asociativa a la derecha con baja prioridad de enlace. Así que simplemente calcula las cosas a su derecha primero. Así,
result = f $ g x
es lo mismo que esto, de manera procesal (lo que importa dado que Haskell se evalúa perezosamente, comenzará a evaluar f
primero):
h = f
g_x = g x
result = h g_x
o más concisamente:
result = f (g x)
Podemos ver esto leyendo la fuente de cada función.
Lee la fuente
Aquí está la source de (.)
:
-- | Function composition.
{-# INLINE (.) #-}
-- Make sure it has TWO args only on the left, so that it inlines
-- when applied to two functions, even if there is no final argument
(.) :: (b -> c) -> (a -> b) -> a -> c
(.) f g = /x -> f (g x)
Y aquí está la source de ($)
:
-- | Application operator. This operator is redundant, since ordinary
-- application @(f x)@ means the same as @(f ''$'' x)@. However, ''$'' has
-- low, right-associative binding precedence, so it sometimes allows
-- parentheses to be omitted; for example:
--
-- > f $ g $ h x = f (g (h x))
--
-- It is also useful in higher-order situations, such as @''map'' (''$'' 0) xs@,
-- or @''Data.List.zipWith'' (''$'') fs xs@.
{-# INLINE ($) #-}
($) :: (a -> b) -> a -> b
f $ x = f x
Cuándo usar:
Use la composición cuando no necesite evaluar inmediatamente la función. Tal vez quiera pasar la función que resulta de la composición a otra función.
Use la aplicación cuando suministre todos los argumentos para una evaluación completa.
Así que para nuestro ejemplo, sería semánticamente preferible hacer
f $ g x
cuando tenemos x
(o más bien, los argumentos de g
), y hacemos:
f . g
cuando no lo hacemos
¿Cuál es la diferencia entre el punto (.)
Y el signo de dólar ($)
? Según tengo entendido, ambos son azúcares sintácticos por no necesitar usar paréntesis.
¡Una excelente manera de aprender más sobre cualquier cosa (cualquier función) es recordar que todo es una función! Ese mantra general ayuda, pero en casos específicos como operadores, ayuda a recordar este pequeño truco:
:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
y
:t ($)
($) :: (a -> b) -> a -> b
Solo recuerde usar :t
generosamente, y envuelva a sus operadores en ()
!
... o podrías evitar el .
y $
construcciones mediante la canalización :
third xs = xs |> tail |> tail |> head
Eso es después de haber agregado en la función de ayuda:
(|>) x y = y x
Creo que un breve ejemplo de donde usarías .
y no $
ayudaría a aclarar las cosas.
double x = x * 2
triple x = x * 3
times6 = double . triple
:i times6
times6 :: Num c => c -> c
Tenga en cuenta que times6
es una función que se crea a partir de la composición de la función.
El operador $
es para evitar paréntesis. Cualquier cosa que aparezca después tendrá prioridad sobre cualquier cosa que venga antes.
Por ejemplo, digamos que tienes una línea que dice:
putStrLn (show (1 + 1))
Si desea deshacerse de esos paréntesis, cualquiera de las siguientes líneas también haría lo mismo:
putStrLn (show $ 1 + 1)
putStrLn $ show (1 + 1)
putStrLn $ show $ 1 + 1
El propósito principal de la .
El operador no es para evitar paréntesis, sino para encadenar funciones. Te permite vincular la salida de lo que aparece a la derecha con la entrada de lo que aparece a la izquierda. Esto generalmente también produce menos paréntesis, pero funciona de manera diferente.
Volviendo al mismo ejemplo:
putStrLn (show (1 + 1))
-
(1 + 1)
no tiene una entrada y, por lo tanto, no puede usarse con.
operador. -
show
puede tomar unInt
y devolver unString
. -
putStrLn
puede tomar unaString
y devolver unIO ()
.
Se puede show
cadena para putStrLn
así:
(putStrLn . show) (1 + 1)
Si son demasiados paréntesis para su gusto, elimínelos con el operador $
:
putStrLn . show $ 1 + 1
La versión corta y dulce:
-
($)
llama a la función que es su argumento de la izquierda en el valor que es su argumento de la derecha. -
(.)
compone la función que es su argumento de la izquierda sobre la función que es su argumento de la derecha.
Mi regla es simple (yo también soy principiante):
- no utilizar si desea pasar el parámetro (llamar a la función), y
- No use
$
si no hay ningún parámetro todavía (componga una función)
Es decir
show $ head [1, 2]
pero nunca:
show . head [1, 2]
También tenga en cuenta que ($)
es la función de identidad especializada para tipos de función . La función de identidad se ve así:
id :: a -> a
id x = x
Mientras que ($)
ve así:
($) :: (a -> b) -> (a -> b)
($) = id
Tenga en cuenta que he agregado intencionalmente paréntesis adicionales en la firma de tipo.
Usos de ($)
generalmente pueden eliminarse agregando paréntesis (a menos que el operador se use en una sección). Por ejemplo: f $ gx
convierte en f (gx)
.
Los usos de (.)
A menudo son un poco más difíciles de reemplazar; por lo general necesitan una lambda o la introducción de un parámetro de función explícito. Por ejemplo:
f = g . h
se convierte en
f x = (g . h) x
se convierte en
f x = g (h x)
¡Espero que esto ayude!
Tienen diferentes tipos y diferentes definiciones:
infixr 9 .
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)
infixr 0 $
($) :: (a -> b) -> a -> b
f $ x = f x
($)
está destinado a reemplazar la aplicación de función normal, pero con una prioridad diferente para ayudar a evitar paréntesis. (.)
es para componer dos funciones juntas para hacer una nueva función.
En algunos casos son intercambiables, pero esto no es cierto en general. El ejemplo típico donde están es:
f $ g $ h $ x
==>
f . g . h $ x
En otras palabras, en una cadena de $
s, todos menos el final pueden ser reemplazados por .
Todas las otras respuestas son bastante buenas. Pero hay un detalle importante de usabilidad sobre cómo trata ghc $, que el verificador de tipos ghc permite instatiarion con rango más alto / tipos cuantificados. Si observa el tipo de $ id
por ejemplo, encontrará que tomará una función cuyo argumento es en sí mismo una función polimórfica. Pequeñas cosas como esa no tienen la misma flexibilidad con un operador equivalente. (Esto en realidad me hace preguntarme si $ merece el mismo tratamiento o no)
Una aplicación que es útil y me tomó un tiempo para averiguar a partir de la breve descripción en Learn You A Haskell : Desde:
f $ x = f x
y el paréntesis del lado derecho de una expresión que contiene un operador de infijo la convierte en una función de prefijo, se puede escribir ($ 3) (4+)
análoga a (++", world") "hello"
.
¿Por qué alguien haría esto? Para listas de funciones, por ejemplo. Ambos:
map (++", world") ["hello","goodbye"]`
y:
map ($ 3) [(4+),(3*)]
son más cortos que el map (/x -> x ++ ", world") ...
o el map (/f -> f 3) ...
Obviamente, las últimas variantes serían más legibles para la mayoría de las personas.
($)
permite que las funciones se encadenen entre sí sin agregar paréntesis para controlar el orden de evaluación:
Prelude> head (tail "asdf")
''s''
Prelude> head $ tail "asdf"
''s''
El operador de composición (.)
Crea una nueva función sin especificar los argumentos:
Prelude> let second x = head $ tail x
Prelude> second "asdf"
''s''
Prelude> let second = head . tail
Prelude> second "asdf"
''s''
El ejemplo anterior es posiblemente ilustrativo, pero en realidad no muestra la conveniencia de usar la composición. Aquí hay otra analogía:
Prelude> let third x = head $ tail $ tail x
Prelude> map third ["asdf", "qwer", "1234"]
"de3"
Si solo usamos el tercero una vez, podemos evitar nombrarlo usando un lambda:
Prelude> map (/x -> head $ tail $ tail x) ["asdf", "qwer", "1234"]
"de3"
Finalmente, la composición nos permite evitar la lambda:
Prelude> map (head . tail . tail) ["asdf", "qwer", "1234"]
"de3"