Dodanie ~
przed dopasowaniem wzorców powoduje, że dopasowanie jest niezastąpione. Możesz myśleć o tym jako dodając dodatkowe lenistwo do wzoru, tak aby nigdy nie zawiodło, chyba że dopasowanie jest absolutnie wymagane do oceny. Oto prosty przykład:
Prelude> (\ (_:_) -> "non-empty") []
"*** Exception: <interactive>:2:2-23: Non-exhaustive patterns in lambda
Prelude> (\ ~(_:_) -> "oops") []
"oops"
Z niepodważalny wzór meczu, choć nie mecz wzór na pustą listę, ponieważ nie są oceniane zmienne bound, nie ma błędu. Zasadniczo, przemienia niepodważalny wzór mecz funkcję do:
\ xs -> let (_:_) = xs in "oops"
Jest to dodatkowy owijania lenistwa który spowalnia funkcji długości. Jeśli stosuje się takie same niech wiążące przekształcić do lengthwtilde
masz
lengthwtilde [] = 0
lengthwtilde xs' = let (_:xs) = xs' in 1 + lengthwtilde xs
Pomyśl o tym, jak to jest oceniany. Na najwyższym poziomie otrzymujesz 1+lengthwtilde xs
. Ale xs nie jest nawet oceniany, ponieważ jest zmienną let-bound. Tak więc w następnym kroku najpierw oceniany jest xs
, aby ustalić, że pasuje on do drugiego przypadku lengthwtilde
, a proces się powtarza.
Porównaj to z lengthwotilde
. W tej funkcji akt dopasowania w drugim przypadku funkcji wymusza również ocenę argumentu. Efekt końcowy jest taki sam, ale skuteczniej jest go rozwinąć wcześniej, niż zostawiać na siłę następny thunk.
Technicznie lengthwtilde
jest nieco bardziej złożona: argument jest już oceniano w drugiej gałęzi, ponieważ to w jaki sposób określić, które jesteśmy w oddział, jednak zostaje ponownie owinięty kiedy przeszedł do rekurencyjnego wywołania.
Przydaje się możliwość zobaczenia wyprodukowanego rdzenia. Oto wyjście lengthwotilde
(produkowany od ghc -O0
:
Foo.lengthwotilde =
\ (@ t_afD)
(@ a_afE)
($dNum_afF :: GHC.Num.Num a_afE)
(eta_B1 :: [t_afD]) ->
letrec {
lengthwotilde1_af2 [Occ=LoopBreaker] :: [t_afD] -> a_afE
[LclId, Arity=1]
lengthwotilde1_af2 =
\ (ds_dgd :: [t_afD]) ->
case ds_dgd of _ {
[] -> GHC.Num.fromInteger @ a_afE $dNum_afF (__integer 0);
: ds1_dge xs_af1 ->
GHC.Num.+
@ a_afE
$dNum_afF
(GHC.Num.fromInteger @ a_afE $dNum_afF (__integer 1))
(lengthwotilde1_af2 xs_af1)
}; } in
lengthwotilde1_af2 eta_B1
Uwaga Funkcja lengthwotilde1_af2
natychmiast robi case
na argumencie ds_dgd
(jest to lista wejście), a następnie recurses wewnątrz obudowy, tworząc thunk (niektóre rozszerzenia):
1 + len [2..]
1 + (1 + len [3..])
1 + (1 + (1 + len [4..])
która ostatecznie wymaga oceny 1 + (1 + ((1 + 1 + ..)))
Oto lengthwtilde
Foo.lengthwtilde =
\ (@ t_afW)
(@ a_afX)
($dNum_afY :: GHC.Num.Num a_afX)
(eta_B1 :: [t_afW]) ->
letrec {
lengthwtilde1_afM [Occ=LoopBreaker] :: [t_afW] -> a_afX
[LclId, Arity=1]
lengthwtilde1_afM =
\ (ds_dgh :: [t_afW]) ->
case ds_dgh of wild_X9 {
[] -> GHC.Num.fromInteger @ a_afX $dNum_afY (__integer 0);
: ipv_sgv ipv1_sgw ->
GHC.Num.+
@ a_afX
$dNum_afY
(GHC.Num.fromInteger @ a_afX $dNum_afY (__integer 1))
(lengthwtilde1_afM
(case wild_X9 of _ {
[] ->
(Control.Exception.Base.irrefutPatError
@() "foo.hs:(3,1)-(4,42)|(_ : xs)")
`cast` (UnsafeCo() [t_afW] ::() ~# [t_afW]);
: ds1_dgk xs_aeH -> xs_aeH
}))
}; } in
lengthwtilde1_afM eta_B1
ocena ta tworzy inny thunk:
len [1..]
1 + (len (if null [1..] then error else [2..]))
1 + (len [2..])
1 + (1 + len (if null [2..] then error else [3..]))
co ostatecznie prowadzi do tego samego łańcucha dodatków jakie zostały po raz pierwszy, ale z kilku dodatkowych logika do obsługi niepowstrzymanych błędów wzorca.
Teraz, jeśli używasz skompilowanego kodu z dowolnymi optymalizacjami, ghc prawie na pewno zauważy, że argumenty nie mogą mieć wartości NULL, ponieważ są już ocenione i są znane z tego, że używają konstruktora (:)
. A kiedy skompiluję kod za pomocą ghc -O2
i uruchomię, obie funkcje będą wykonywane w tym samym czasie. Obaj są dość źli, ponieważ w obu wypadkach dochodzi do łańcucha kłaków. Standardowa definicja length
jest znacznie lepsza, jak na dobrą definicję foldl'
.
Czy obejrzałeś tę stronę wiki? [Normalnie, jeśli dopasowujesz wzorce za pomocą konstruktora jako części wzorca, musisz ocenić dowolny argument przekazany do tej funkcji, aby upewnić się, że pasuje do wzorca ... Jednak poprzedzanie wzoru znakiem tyldy opóźnia ocenę wartość do momentu użycia części składowych] (http://en.wikibooks.org/wiki/Haskell/Laziness#Lazy_pattern_matching) – zurgl