Jaka jest różnica pomiędzy . (kropka) i $ (znak dolara)?

709

Jaka jest różnica między kropką (.)a znakiem dolara ($)?

Jak rozumiem, oba są cukrem syntaktycznym, ponieważ nie muszą używać nawiasów.

Rabarberski
źródło

Odpowiedzi:

1226

$Operatora dla uniknięcia nawiasów. Wszystko, co pojawi się po nim, będzie miało pierwszeństwo przed wszystkim, co nastąpi wcześniej.

Załóżmy na przykład, że masz wiersz o treści:

putStrLn (show (1 + 1))

Jeśli chcesz się pozbyć tych nawiasów, dowolny z poniższych wierszy również zrobiłby to samo:

putStrLn (show $ 1 + 1)
putStrLn $ show (1 + 1)
putStrLn $ show $ 1 + 1

Głównym celem .operatora nie jest unikanie nawiasów, ale połączenie funkcji. Pozwala powiązać wyjście tego, co pojawia się po prawej stronie, z wejściem tego, co pojawia się po lewej stronie. Powoduje to zwykle mniej nawiasów, ale działa inaczej.

Wracając do tego samego przykładu:

putStrLn (show (1 + 1))
  1. (1 + 1)nie ma danych wejściowych i dlatego nie można go używać z .operatorem.
  2. showmoże wziąć Inti zwrócić a String.
  3. putStrLnmoże wziąć Stringi zwrócić IO ().

Możesz połączyć, showaby putStrLnpolubić to:

(putStrLn . show) (1 + 1)

Jeśli jest to zbyt wiele nawiasów na twój gust, pozbądź się ich z $operatorem:

putStrLn . show $ 1 + 1
Michael Steele
źródło
54
W rzeczywistości, ponieważ + jest również funkcją, nie możesz zrobić jej z prefiksem, a następnie skomponować, tak jak `putStrLn. pokazać . (+) 1 1 `Nie żeby to było bardziej zrozumiałe, ale mam na myśli ... że mógłbyś, prawda?
CodexArcanum
4
@CodexArcanum W tym przykładzie coś podobnego putStrLn . show . (+1) $ 1byłoby równoważne. Masz rację, ponieważ większość operatorów (wszystkie?) Są funkcjami.
Michael Steele,
79
Zastanawiam się, dlaczego nikt nigdy nie wspomina o takich zastosowaniach map ($3). Chodzi mi głównie o to, $aby unikać nawiasów, ale to nie tak, że po to są.
Cubic
43
map ($3)jest funkcją typu Num a => [(a->b)] -> [b]. Pobiera listę funkcji z liczbą, stosuje 3 do wszystkich i zbiera wyniki.
Cubic
21
Musisz zachować ostrożność, używając $ z innymi operatorami. „x + f (y + z)” to nie to samo, co „x + f $ y + z”, ponieważ to ostatnie faktycznie oznacza „(x + f) (y + z)” (tj. suma x i f wynosi traktowane jako funkcja).
Paul Johnson
186

Mają różne typy i różne definicje:

infixr 9 .
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)

infixr 0 $
($) :: (a -> b) -> a -> b
f $ x = f x

($)ma zastąpić normalną aplikację funkcji, ale ma inny priorytet, aby uniknąć nawiasów. (.)służy do połączenia dwóch funkcji w celu utworzenia nowej funkcji.

W niektórych przypadkach są one wymienne, ale ogólnie nie jest to prawdą. Typowy przykład, w którym się znajdują, to:

f $ g $ h $ x

==>

f . g . h $ x

Innymi słowy w łańcuchu $s, wszystkie oprócz ostatniego można zastąpić.

GS - Przeproś Monikę
źródło
1
Co jeśli xbyła funkcja? Czy możesz użyć go .jako ostatecznego?
richizy
3
@richizy, jeśli faktycznie aplikujesz xw tym kontekście, to tak - ale wtedy „ostateczny” dotyczyłby czegoś innego niż x. Jeśli nie aplikujesz x, nie jest niczym innym jak xbyciem wartością.
GS - Przeproś Monikę
124

Zauważ też, że ($)jest to funkcja tożsamości wyspecjalizowana w typach funkcji . Funkcja tożsamości wygląda następująco:

id :: a -> a
id x = x

Chociaż ($)wygląda tak:

($) :: (a -> b) -> (a -> b)
($) = id

Zauważ, że celowo dodałem dodatkowe nawiasy w podpisie typu.

Zastosowania ($)można zwykle wyeliminować przez dodanie nawiasów (chyba że w sekcji użyto operatora). Np .: f $ g xstaje się f (g x).

Zastosowania (.)są często nieco trudniejsze do zastąpienia; zazwyczaj potrzebują lambda lub wprowadzenia jawnego parametru funkcji. Na przykład:

f = g . h

staje się

f x = (g . h) x

staje się

f x = g (h x)

Mam nadzieję że to pomoże!

Martijn
źródło
„Pamiętaj, że celowo dodałem dodatkowe nawiasy w podpisie typu”. Jestem zmieszany ... dlaczego to zrobiłeś?
Mateen Ulhaq
3
@MateenUlhaq Typ ($) to (a -> b) -> a -> b, który jest taki sam jak (a -> b) -> (a -> b), ale dodatkowe nawiasy tutaj dodają trochę przejrzystość.
Rudi
2
Och, tak sądzę. Myślałem o tym jako o funkcji dwóch argumentów ... ale z powodu curry jest to dokładnie równoważne funkcji, która zwraca funkcję.
Mateen Ulhaq
78

($) umożliwia łączenie funkcji bez dodawania nawiasów w celu sterowania kolejnością oceny:

Prelude> head (tail "asdf")
's'

Prelude> head $ tail "asdf"
's'

Operator tworzenia (.)tworzy nową funkcję bez podawania argumentów:

Prelude> let second x = head $ tail x
Prelude> second "asdf"
's'

Prelude> let second = head . tail
Prelude> second "asdf"
's'

Powyższy przykład jest prawdopodobnie ilustracyjny, ale tak naprawdę nie pokazuje wygody używania kompozycji. Oto kolejna analogia:

Prelude> let third x = head $ tail $ tail x
Prelude> map third ["asdf", "qwer", "1234"]
"de3"

Jeśli użyjemy trzeciego tylko raz, możemy uniknąć nazwania go za pomocą lambda:

Prelude> map (\x -> head $ tail $ tail x) ["asdf", "qwer", "1234"]
"de3"

Wreszcie, kompozycja pozwala nam uniknąć lambda:

Prelude> map (head . tail . tail) ["asdf", "qwer", "1234"]
"de3"
softmechanika
źródło
3
Gdyby przepływ stosu miał funkcję kombinacji, wolałbym odpowiedź łączącą dwa poprzednie wyjaśnienia z przykładem w tej odpowiedzi.
Chris.Q
59

Krótka i słodka wersja:

  • ($) wywołuje funkcję będącą argumentem po lewej stronie wartości, która jest argumentem po prawej stronie.
  • (.) tworzy funkcję będącą argumentem po lewej stronie funkcji, która jest argumentem po prawej stronie.
ellisbben
źródło
29

Jedna aplikacja, która jest przydatna i zajęła mi trochę czasu, aby zrozumieć bardzo krótki opis na stronie hashell : Od:

f $ x = f x

a nawiasowanie prawej strony wyrażenia zawierającego operator infix przekształca go w funkcję prefiksu, którą można napisać ($ 3) (4+) analogicznie (++", world") "hello".

Dlaczego ktoś miałby to robić? Na przykład dla list funkcji. Obie:

map (++", world") ["hello","goodbye"]`

i:

map ($ 3) [(4+),(3*)]

są krótsze niż map (\x -> x ++ ", world") ...lub map (\f -> f 3) .... Oczywiście te ostatnie warianty byłyby bardziej czytelne dla większości ludzi.

Christoph
źródło
14
btw, odradzam używanie $3bez przestrzeni. Jeśli szablon Haskell jest włączony, zostanie on przeanalizowany jako splot, a $ 3zawsze oznacza to, co powiedziałeś. Ogólnie rzecz biorąc, wydaje się, że w Haskell istnieje tendencja do „kradzieży” fragmentów składni przez naleganie, aby niektórzy operatorzy mieli wokół siebie spacje, aby byli traktowani jako tacy.
GS - Przeproś Monikę
1
Zajęło mi trochę czasu, aby dowiedzieć się, jak działają nawiasy: en.wikibooks.org/wiki/Haskell/…
Casebash
18

Haskell: różnica między .(kropka) a$ (znak dolara)

Jaka jest różnica między kropką (.)a znakiem dolara ($)? Jak rozumiem, oba są cukrem syntaktycznym, ponieważ nie muszą używać nawiasów.

Są one nie cukrem syntaktycznym, ponieważ nie muszą używać nawiasów - są funkcjami, - naprawione, dlatego możemy nazwać je operatorami.

Komponować, (.) i kiedy z niego korzystać.

(.)to funkcja tworzenia. Więc

result = (f . g) x

jest równoznaczne z budowaniem funkcji, która przekazuje wynik argumentu przekazanego gdo f.

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

Posługiwać się (.) gdy nie masz dostępnych argumentów do przekazania funkcji, które chcesz skomponować.

Obowiązuje właściwy asocjator, ($) i kiedy z nich korzystać

($)jest funkcją stosującą asocjację z niskim priorytetem wiązania. Więc najpierw oblicza tylko rzeczy po prawej stronie. A zatem,

result = f $ g x

jest taki sam jak ten, proceduralnie (co ma znaczenie, ponieważ Haskell jest oceniany leniwie, zacznie oceniać jako fpierwszy):

h = f
g_x = g x
result = h g_x

lub bardziej zwięźle:

result = f (g x)

Użyj, ($)gdy masz wszystkie zmienne do oceny przed zastosowaniem poprzedniej funkcji do wyniku.

Możemy to zobaczyć, czytając źródło dla każdej funkcji.

Przeczytaj źródło

Oto źródło dla (.):

-- | 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)

A oto źródło dla ($):

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

Wniosek

Użyj kompozycji, gdy nie musisz od razu oceniać funkcji. Może chcesz przekazać funkcję wynikającą ze składu do innej funkcji.

Użyj aplikacji, gdy podajesz wszystkie argumenty do pełnej oceny.

W naszym przykładzie byłoby to semantycznie lepsze

f $ g x

kiedy mamy x(a raczej gargumenty) i wykonujemy:

f . g

kiedy my nie.

Aaron Hall
źródło
12

... lub możesz uniknąć .i $konstrukcji, używając rurociągów :

third xs = xs |> tail |> tail |> head

To po dodaniu funkcji pomocnika:

(|>) x y = y x
użytkownik1721780
źródło
2
Tak, |> jest operatorem potoku F #.
user1721780,
6
Warto zauważyć tutaj, że Haskell'a $operator faktycznie działa bardziej jak F # 's <|niż to robi |>, zazwyczaj w Haskell chcesz napisać powyższą funkcję tak: third xs = head $ tail $ tail $ xsa może nawet jak third = head . tail . tail, co w F # -Style składnia byłoby coś takiego:let third = List.head << List.tail << List.tail
Kawa elektryczna
1
Po co dodawać funkcję pomocnika, aby Haskell wyglądał jak F #? -1
vikingsteve
9
Odwrócona $jest już dostępna, i to się nazywa & hackage.haskell.org/package/base-4.8.0.0/docs/...
pat
11

Świetnym sposobem, aby dowiedzieć się więcej o czymkolwiek (dowolnej funkcji) jest zapamiętanie, że wszystko jest funkcją! Ta ogólna mantra pomaga, ale w szczególnych przypadkach, takich jak operatorzy, pomaga zapamiętać tę małą sztuczkę:

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

i

:t ($)
($) :: (a -> b) -> a -> b

Pamiętaj tylko, aby używać :tswobodnie i owijać swoich operatorów ()!

lol
źródło
11

Moja zasada jest prosta (ja też jestem początkujący):

  • nie używaj . jeśli chcesz przekazać parametr (wywołać funkcję), i
  • nie używaj, $jeśli nie ma jeszcze parametru (utwórz funkcję)

To jest

show $ head [1, 2]

ale nigdy:

show . head [1, 2]
halacsy
źródło
3
Dobra heurystyka, ale
przydałoby się
0

Myślę, że krótki przykład tego, gdzie byś użył .i nie $pomógłby wyjaśnić rzeczy.

double x = x * 2
triple x = x * 3
times6 = double . triple

:i times6
times6 :: Num c => c -> c

Zauważ, że times6jest to funkcja utworzona z kompozycji funkcji.

Brennan Cheung
źródło
0

Wszystkie pozostałe odpowiedzi są całkiem dobre. Ale jest ważny szczegół użyteczności dotyczący tego, jak ghc traktuje $, że moduł sprawdzania typu ghc pozwala na instatiar z typami wyższego rzędu / ilościowymi. Jeśli spojrzysz na $ idprzykład, zobaczysz, że przyjmie on funkcję, której argument sam w sobie jest funkcją polimorficzną. Takie małe rzeczy nie mają tej samej elastyczności z równorzędnym operatorem zdenerwowanym. (To sprawia, że ​​zastanawiam się, czy $! Zasługuje na to samo traktowanie, czy nie)

Carter Tazio Schonwald
źródło