Jaki jest sens słowa „const” w preludium Haskella?

94

Przeglądając Haskell Prelude, widzę funkcję const :

const x _ = x

Nie mogę znaleźć nic odpowiedniego do tej funkcji.

Jaki jest sens? Czy ktoś może podać przykład, gdzie można zastosować tę funkcję?

stusmith
źródło
10
Przykład: backgroundColor :: Text -> Colorjest dla mniebackgroundColor = const White
Zhen

Odpowiedzi:

84

Przydaje się do przechodzenia do funkcji wyższego rzędu, gdy nie potrzebujesz całej ich elastyczności. Na przykład monadyczny operator sekwencji >>można zdefiniować w kategoriach monadycznego operatora wiązania jako

x >> y = x >>= const y

Jest to trochę schludniejsze niż użycie lambdy

x >> y = x >>= \_ -> y

i możesz go nawet używać bez punktów

(>>) = (. const) . (>>=)

chociaż nie polecam tego szczególnie w tym przypadku.

hammar
źródło
9
+1. Pojawia się również często podczas używania kombinatorów parsera.
Fred Foo
49
Ahh, więc jest to bardziej „generator funkcji” - używam go z jednym argumentem i daje mi funkcję (przyjmującą jeden argument), która zawsze zwraca stałą wartość. Tak map (const 42) [1..5]skutkuje [42, 42, 42, 42, 42].
stusmith
2
stusmith: Masz to. constjest przydatny do stosowania do pojedynczego argumentu w celu uzyskania funkcji, gdy jest ona potrzebna (na przykład przekazywanie do map).
Conal,
9
@stusmith: Możesz go używać na kilka interesujących sposobów:head = foldr const (error "Prelude.head: empty list")
rampion
27

Aby dodać do doskonałej bezpośredniej odpowiedzi Hammara: skromne funkcje, takie jak consti idsą naprawdę przydatne jako funkcje wyższego rzędu z tego samego powodu, z którego są fundamentalne w rachunku kombinatorów SKI .

Nie sądzę, żebym uważał, że funkcje preludium haskella były świadomie wzorowane na tym formalnym systemie czy czymkolwiek. Tyle, że tworzenie bogatych abstrakcji w haskell jest bardzo łatwe, więc często widzisz, że tego typu teoretyczne rzeczy okazują się praktycznie przydatne.

Wtyczka bezczelny, ale na blogu o tym, jak na przykład aplikacyjnych (->)są rzeczywiście Si Kkombinatorów tutaj , jeśli to rodzaj rzeczy, jesteś w.

jberryman
źródło
8
Cóż, kombinatory SKI z pewnością wpłynęły na Preludium. Pamiętam, jak kłóciłem się z Joe Faselem, czy kombinator S powinien zostać uwzględniony, czy nie.
sierpień
4
Nawiasem mówiąc, ((->) e)jest również monadą czytelnika - z Readeri tym podobnymi po prostu newtypeopakowaniami - a askfunkcja jest wtedy id, więc jest to również Ikombinator. Jeśli spojrzeć zamiast na podstawie oryginalnego bckw Haskell Curry, w B, Ki Wto fmap, returni joinodpowiednio.
CA McCann,
1
Link do bloga w odpowiedzi jest martwy. Powinien teraz wskazywać tutaj: brandon.si/code/…
nsxt
22

Prostym przykładem użycia constjest Data.Functor.(<$). Dzięki tej funkcji można powiedzieć: mam tu funktor z czymś nudnym, ale zamiast tego chcę mieć w sobie tę inną interesującą rzecz, bez zmiany kształtu funktora. Na przykład

import Data.Functor

42 <$ Just "boring"
--> Just 42

42 <$ Nothing
--> Nothing

"cool" <$ ["nonsense","stupid","uninteresting"]
--> ["cool","cool","cool"]

Definicja jest następująca:

(<$) :: a -> f b -> f a
(<$) =  fmap . const

lub napisane nie tak bezcelowe:

cool <$ uncool =  fmap (const cool) uncool

Widzisz, jak constsłuży tutaj do „zapomnienia” o wejściu.

Landei
źródło
21

Nie mogę znaleźć nic odpowiedniego do tej funkcji.

Wiele innych odpowiedzi dotyczy stosunkowo ezoterycznych (przynajmniej dla nowicjuszy) zastosowań const. Oto prosty: możesz go użyć, constaby pozbyć się lambdy, która przyjmuje dwa argumenty, odrzuca pierwszy, ale robi coś interesującego z drugim.

Na przykład, następujący (nieefektywne ale pouczające) realizacja length,

length' = foldr (\_ acc -> 1 + acc) 0

można przepisać jako

length' = foldr (const (1+)) 0

co jest być może bardziej eleganckie.

Wyrażenie const (1+)jest rzeczywiście semantycznie równoważne z \_ acc -> 1 + acc, ponieważ pobiera jeden argument, odrzuca go i zwraca sekcję (1+) .

jub0bs
źródło
4
Zrozumienie, jak to działa, zajęło mi 5 minut :)
Mukesh Soni,
15

Innym zastosowaniem jest implementacja funkcji składowych klasy, które mają fikcyjny argument, który nie powinien być oceniany (używany do rozwiązywania niejednoznacznych typów). Przykład, który może znajdować się w Data.bits:

instance Bits Int where
  isSigned = const True
  bitSize  = const wordSize
  ...

Używając const, wyraźnie mówimy, że definiujemy wartości stałe.

Osobiście nie podoba mi się używanie fikcyjnych parametrów, ale jeśli są one używane w klasie, jest to raczej przyjemny sposób pisania instancji.

Jonas Duregård
źródło
Argumenty proxy są rzeczywiście znacznie lepsze, a gdy celujemy w najnowsze GHC, aplikacje typu świetnie sobie radzą.
dfeuer
2

constmoże być po prostu implementacją, której szukasz w połączeniu z innymi funkcjami. Oto przykład, który odkryłem.

Powiedzmy, że chcemy przepisać strukturę 2-krotek do innej struktury 2-krotek. Mógłbym to wyrazić następująco:

((a,b),(c,d)) ⇒ (a,(c,(5,a)))

Mogę podać prostą definicję z dopasowywaniem wzorców:

f ((a,b),(c,d)) = (a,(c,(5,a)))

A jeśli chcę bezcelowego (milczącego) rozwiązania tego rodzaju przepisywania? Po pewnym czasie myślenia i zabawy, odpowiedź jest taka, że ​​możemy wyrazić dowolne przepisanie za pomocą (&&&), const, (.), fst, snd. Zauważ, że (&&&)pochodzi z Control.Arrow.

Rozwiązanie przykładu wykorzystującego te funkcje to:

(fst.fst &&& (fst.snd &&& (const 5 &&& fst.fst)))

Zwróć uwagę na podobieństwo z (a,(c,(5,a))). Co jeśli zastąpimy &&&z ,? Następnie brzmi:

(fst.fst, (fst.snd, (const 5, fst.fst)))

Zwróć uwagę, jaki ajest pierwszy element pierwszego elementu i to jest to fst.fst, co projektuje. Zwróć uwagę, jaki cjest pierwszy element drugiego elementu i to jest to fst.snd, co projektuje. Oznacza to, że zmienne stają się ścieżką do ich źródła.

constpozwala nam na wprowadzenie stałych. Ciekawe, jak nazwa pasuje do znaczenia!

I wtedy uogólnić ten pomysł z aplikacyjnych, dzięki czemu można napisać dowolną funkcję w bezcelowym stylu (tak długo, jak masz analiza case dostępne funkcje, takie jak maybe, either, bool). Ponownie constodgrywa rolę wprowadzania stałych. Możesz zobaczyć tę pracę w pakiecie Data.Function.Tacit .

Kiedy zaczynasz abstrakcyjnie, od celu, a następnie pracujesz nad wdrożeniem, możesz być zaskoczony odpowiedziami. Oznacza to, że każda funkcja może być tak tajemnicza, jak każdy trybik w maszynie. Jeśli jednak cofniesz się, aby zobaczyć całą maszynę, możesz zrozumieć kontekst, w którym ten trybik jest potrzebny.

erisco
źródło
2

Powiedzmy, że chcesz utworzyć listę Nothingsrówną długości ciągu. As constzwraca swój pierwszy argument, bez względu na drugi, możesz zrobić:

listOfNothings :: String -> [Maybe Char]
listOfNothings = (map . const) Nothing

lub bardziej szczegółowo:

listOfNothing st = map (const Nothing) st
A.Saramet
źródło
0

Powiedz, że chcesz obrócić listę. To idiomatyczny sposób na zrobienie tego w Haskell:

rotate :: Int -> [a] -> [a] rotate _ [] = [] rotate n xs = zipWith const (drop n (cycle xs)) xs

Ta funkcja zamyka dwie tablice za pomocą funkcji const, pierwsza to nieskończona tablica cykliczna, a druga to tablica, od której zacząłeś.

const działa jak kontrola granic i używa oryginalnej tablicy do zakończenia tablicy cyklicznej.

Zobacz: Obracanie listy w Haskell

Znani Jameis
źródło
0

Nie mogę znaleźć nic odpowiedniego do tej funkcji.

Załóżmy, że chcesz wygenerować wszystkie podciągi z danej listy.

Dla każdego elementu listy w danym momencie masz do wyboru True (uwzględnij go w bieżącym podciągu) lub False (nie uwzględniaj go). Można to zrobić za pomocą funkcji filterM .

Lubię to:

 λ> import Control.Monad
 λ> :t filterM
 filterM :: Applicative m => (a -> m Bool) -> [a] -> m [a]
 λ> 

Na przykład chcemy, aby wszystkie podciągi [1..4].

 λ> filterM  (const [True, False])  [1..4]
 [[1,2,3,4],[1,2,3],[1,2,4],[1,2],[1,3,4],[1,3],[1,4],[1],[2,3,4],[2,3],[2,4],[2],[3,4],[3],[4],[]]
 λ> 
jpmarinier
źródło