¿Cuál es la diferencia entre el punto (.)
y el signo de dólar ($)
?
Según tengo entendido, ambos son azúcar sintáctica por no necesitar usar paréntesis.
¿Cuál es la diferencia entre el punto (.)
y el signo de dólar ($)
?
Según tengo entendido, ambos son azúcar sintáctica por no necesitar usar paréntesis.
Respuestas:
El $
operador es para evitar paréntesis. Cualquier cosa que aparezca después tendrá prioridad sobre cualquier cosa que ocurra antes.
Por ejemplo, supongamos que tiene 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 del .
operador no es evitar paréntesis, sino encadenar funciones. Le 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 se puede usar con el .
operador.show
puede tomar un Int
y devolver a String
.putStrLn
puede tomar un String
y devolver un IO ()
.Puedes encadenar show
para que te putStrLn
guste esto:
(putStrLn . show) (1 + 1)
Si son demasiados paréntesis para su gusto, elimínelos con el $
operador:
putStrLn . show $ 1 + 1
putStrLn . show . (+1) $ 1
sería equivalente. Tienes razón en que la mayoría (¿todos?) Los operadores infijos son funciones.
map ($3)
. Quiero decir, también uso principalmente $
para evitar paréntesis, pero no es que eso sea todo lo que están ahí para hacer.
map ($3)
Es una función de tipo Num a => [(a->b)] -> [b]
. Toma una lista de funciones que toman un número, aplica 3 a todas y recopila los resultados.
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 precedencia diferente para ayudar a evitar paréntesis. (.)
es para componer dos funciones juntas para crear 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.
x
fuera una función? ¿Puedes usarlo .
como el final?
x
en este contexto, entonces sí, pero luego el "final" se aplicaría a algo diferente x
. Si no está aplicando x
, entonces no es diferente a x
ser un valor.
También tenga en cuenta que ($)
es la función de identidad especializada para los tipos de función . La función de identidad se ve así:
id :: a -> a
id x = x
Mientras se ($)
ve así:
($) :: (a -> b) -> (a -> b)
($) = id
Tenga en cuenta que he agregado paréntesis adicionales intencionalmente en la firma de tipo.
Los usos de ($)
usualmente se pueden eliminar agregando paréntesis (a menos que el operador se use en una sección). Por ejemplo: se f $ g x
convierte f (g x)
.
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
f x = (g . h) x
se convierte
f x = g (h x)
¡Espero que esto ayude!
($)
permite que las funciones se encadenen juntas sin agregar paréntesis para controlar el orden de evaluación:
Prelude> head (tail "asdf")
's'
Prelude> head $ tail "asdf"
's'
El operador de redacció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 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 una 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"
Una aplicación que es útil y me llevó algo de tiempo comprender la breve descripción de aprender un haskell : Desde:
f $ x = f x
y entre paréntesis el lado derecho de una expresión que contiene un operador infijo lo convierte en una función de prefijo, se puede escribir de forma ($ 3) (4+)
análoga (++", 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 map (\x -> x ++ ", world") ...
o map (\f -> f 3) ...
. Obviamente, las últimas variantes serían más legibles para la mayoría de las personas.
$3
sin el espacio. Si Template Haskell está habilitado, esto se analizará como un empalme, mientras que $ 3
siempre significa lo que dijiste. En general, parece haber una tendencia en Haskell a "robar" bits de sintaxis al insistir en que ciertos operadores tienen espacios a su alrededor para ser tratados como tales.
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úcar sintáctica por no necesitar usar paréntesis.
Son sin azúcar sintáctica para no tener que paréntesis de uso - que son funciones, - infijo, por lo tanto les podemos llamar operadores.
(.)
y cuándo usarlo.(.)
es la función componer Entonces
result = (f . g) x
es lo mismo que construir una función que pasa el resultado de su argumento pasado g
a f
.
h = \x -> f (g x)
result = h x
Úselo (.)
cuando no tenga los argumentos disponibles para pasar a las funciones que desea componer.
($)
, y cuándo usarlo($)
es una función de aplicación asociativa a la derecha con baja precedencia de enlace. Por lo tanto, simplemente calcula primero las cosas a la derecha. Así,
result = f $ g x
es lo mismo, procesalmente (lo que importa ya 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)
Úselo ($)
cuando tenga todas las variables para evaluar antes de aplicar la función anterior al resultado.
Podemos ver esto leyendo la fuente de cada función.
Aquí está la fuente 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 fuente 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
Use la composición cuando no necesite evaluar inmediatamente la función. Tal vez desee pasar la función que resulta de la composición a otra función.
Use la aplicación cuando proporcione todos los argumentos para una evaluación completa.
Entonces, para nuestro ejemplo, sería semánticamente preferible hacer
f $ g x
cuando tenemos x
(o más bien, g
los argumentos de), y hacemos:
f . g
cuando no lo hacemos
... o podría evitar las construcciones .
y $
utilizando la canalización :
third xs = xs |> tail |> tail |> head
Eso es después de haber agregado en la función auxiliar:
(|>) x y = y x
$
operador realmente funciona más como F # 's <|
de lo que hace |>
, por lo general en Haskell que iba a escribir la función anterior como esto: third xs = head $ tail $ tail $ xs
o tal vez incluso como third = head . tail . tail
, que en Fa # sintaxis al estilo sería algo como esto:let third = List.head << List.tail << List.tail
¡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 recordar este pequeño truco:
:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
y
:t ($)
($) :: (a -> b) -> a -> b
¡Solo recuerde usar :t
generosamente y envolver a sus operadores ()
!
Mi regla es simple (yo también soy principiante):
.
si desea pasar el parámetro (llame a la función), y$
si aún no hay ningún parámetro (componer una función)Es decir
show $ head [1, 2]
pero nunca:
show . head [1, 2]
Creo que un breve ejemplo de dónde 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 funciones.
Todas las otras respuestas son bastante buenas. Pero hay un detalle de usabilidad importante sobre cómo trata ghc $, que el verificador de tipo ghc permite la instalación con tipos cuantificados / de rango superior. 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 molesto equivalente. (Esto realmente me hace preguntarme si $! Merece el mismo tratamiento o no)