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ę?
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ę?
Odpowiedzi:
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.
map (const 42) [1..5]
skutkuje [42, 42, 42, 42, 42]
.
const
jest przydatny do stosowania do pojedynczego argumentu w celu uzyskania funkcji, gdy jest ona potrzebna (na przykład przekazywanie do map
).
head = foldr const (error "Prelude.head: empty list")
Aby dodać do doskonałej bezpośredniej odpowiedzi Hammara: skromne funkcje, takie jak const
i id
są 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 S
i K
kombinatorów tutaj , jeśli to rodzaj rzeczy, jesteś w.
((->) e)
jest również monadą czytelnika - z Reader
i tym podobnymi po prostu newtype
opakowaniami - a ask
funkcja jest wtedy id
, więc jest to również I
kombinator. Jeśli spojrzeć zamiast na podstawie oryginalnego bckw Haskell Curry, w B
, K
i W
to fmap
, return
i join
odpowiednio.
Prostym przykładem użycia const
jest 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 const
służy tutaj do „zapomnienia” o wejściu.
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ć, const
aby 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+)
.
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.
const
moż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 a
jest pierwszy element pierwszego elementu i to jest to fst.fst
, co projektuje. Zwróć uwagę, jaki c
jest 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.
const
pozwala 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 const
odgrywa 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.
Powiedzmy, że chcesz utworzyć listę Nothings
równą długości ciągu. As const
zwraca 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
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
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],[]]
λ>
backgroundColor :: Text -> Color
jest dla mniebackgroundColor = const White