Cuál es la diferencia entre . (punto) y $ (signo de dólar)?

709

¿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.

Rabarberski
fuente

Respuestas:

1226

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 + 1)no tiene una entrada y, por lo tanto, no se puede usar con el .operador.
  2. showpuede tomar un Inty devolver a String.
  3. putStrLnpuede tomar un Stringy devolver un IO ().

Puedes encadenar showpara que te putStrLnguste esto:

(putStrLn . show) (1 + 1)

Si son demasiados paréntesis para su gusto, elimínelos con el $operador:

putStrLn . show $ 1 + 1
Michael Steele
fuente
55
En realidad, dado que + también es una función, ¿no podría hacerlo prefijado y luego componerlo también, como `putStrLn. show . (+) 1 1 `No es que esté más claro, pero quiero decir ... podrías, ¿verdad?
CodexArcanum
44
@CodexArcanum En este ejemplo, algo así putStrLn . show . (+1) $ 1sería equivalente. Tienes razón en que la mayoría (¿todos?) Los operadores infijos son funciones.
Michael Steele
79
Me pregunto por qué nadie menciona usos como 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.
Cubic
43
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.
Cubic
21
Debe tener cuidado al usar $ con otros operadores. "x + f (y + z)" no es lo mismo que "x + f $ y + z" porque este último en realidad significa "(x + f) (y + z)" (es decir, la suma de x y f es tratado como una función).
Paul Johnson
186

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.

GS - Disculpate con Monica
fuente
1
¿Y si xfuera una función? ¿Puedes usarlo .como el final?
richizy
3
@richizy si realmente está aplicando xen 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 xser un valor.
GS - Pídele disculpas a Monica el
124

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 xconvierte 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!

Martijn
fuente
"Tenga en cuenta que he agregado paréntesis adicionales intencionalmente en la firma de tipo". Estoy confundido ... ¿por qué hiciste esto?
Mateen Ulhaq
3
@MateenUlhaq El tipo de ($) es (a -> b) -> a -> b, que es lo mismo que (a -> b) -> (a -> b), pero los paréntesis adicionales aquí agregan algunos claridad.
Rudi
2
Oh, supongo. Lo estaba pensando como una función de dos argumentos ... pero debido al currículum, es exactamente equivalente a una función que devuelve una función.
Mateen Ulhaq
78

($) 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"
softmechanics
fuente
3
Si el stackoverflow tuviera una función de combinación, preferiría la respuesta que combina las dos explicaciones anteriores con el ejemplo en esta respuesta.
Chris.Q
59

La versión corta y dulce:

  • ($) llama a la función que es su argumento izquierdo sobre el valor que es su argumento derecho.
  • (.) compone la función que es su argumento de la izquierda sobre la función que es su argumento de la derecha.
ellisbben
fuente
29

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.

Christoph
fuente
14
por cierto, aconsejaría no usar $3sin el espacio. Si Template Haskell está habilitado, esto se analizará como un empalme, mientras que $ 3siempre 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.
GS - Pídele disculpas a Monica
1
Me tomó un tiempo descubrir cómo funcionaban los paréntesis: en.wikibooks.org/wiki/Haskell/…
Casebash
18

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.

Componer, (.)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 ga f.

h = \x -> f (g x)
result = h x

Úselo (.)cuando no tenga los argumentos disponibles para pasar a las funciones que desea componer.

Aplicativo asociativo correcto ($), 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 fprimero):

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.

Lee la fuente

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

Conclusión

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, glos argumentos de), y hacemos:

f . g

cuando no lo hacemos

Aaron Hall
fuente
12

... 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
usuario1721780
fuente
2
Sí, |> es el operador de canalización F #.
user1721780
66
Una cosa a destacar aquí, es que de Haskell $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 $ xso 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
Café eléctrico
1
¿Por qué agregar una función auxiliar para hacer que Haskell se vea como F #? -1
vikingsteve
99
El volteado $ya está disponible, y se llama & hackage.haskell.org/package/base-4.8.0.0/docs/…
pat
11

¡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 :tgenerosamente y envolver a sus operadores ()!

jajaja
fuente
11

Mi regla es simple (yo también soy principiante):

  • no lo use .si desea pasar el parámetro (llame a la función), y
  • no lo use $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]
halacsy
fuente
3
Buena heurística, pero podría usar más ejemplos
Zoey Hewll
0

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 times6es una función que se crea a partir de la composición de funciones.

Brennan Cheung
fuente
0

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, $ idpor 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)

Carter Tazio Schonwald
fuente