Tak jak w tytule: jakie są gwarancje, że jednostka zwrotna funkcji Haskell zostanie poddana ocenie? Można by pomyśleć, że w takim przypadku nie ma potrzeby przeprowadzania żadnej oceny, kompilator mógłby zastąpić wszystkie takie wywołania natychmiastową ()
wartością, chyba że istnieją wyraźne żądania zachowania ścisłości, w którym to przypadku kod może zdecydować, czy powinien powrót ()
lub dół.
Eksperymentowałem z tym w GHCi i wydaje się, że dzieje się odwrotnie, to znaczy, że taka funkcja wydaje się być oceniana. Byłby to bardzo prymitywny przykład
f :: a -> ()
f _ = undefined
Ocena f 1
powoduje błąd z powodu obecności undefined
, więc pewna ocena zdecydowanie się zdarza. Nie jest jednak jasne, jak daleko sięga ocena; czasami wydaje się, że idzie tak głęboko, jak to konieczne, aby ocenić wszystkie powracające wywołania funkcji ()
. Przykład:
g :: [a] -> ()
g [] = ()
g (_:xs) = g xs
Ten kod zapętla się w nieskończoność, jeśli jest wyświetlany z g (let x = 1:x in x)
. Ale wtedy
f :: a -> ()
f _ = undefined
h :: a -> ()
h _ = ()
może być użyty do pokazania, że h (f 1)
zwraca ()
, więc w tym przypadku nie wszystkie podwyrażenia o wartości jednostkowej są oceniane. Jaka jest tutaj ogólna zasada?
ETA: oczywiście wiem o lenistwie. Pytam, co uniemożliwia pisarzom kompilatorów uczynienie tego konkretnego przypadku jeszcze bardziej leniwym, niż zwykle jest to możliwe.
ETA2: podsumowanie przykładów: GHC wydaje się traktować ()
dokładnie tak, jak każdy inny typ, tzn. Jakby istniało pytanie o to, która regularna wartość zamieszkująca ten typ powinna zostać zwrócona z funkcji. Fakt, że istnieje tylko jedna taka wartość, nie wydaje się (ab) wykorzystywany przez algorytmy optymalizacyjne.
ETA3: kiedy mówię Haskell, mam na myśli Haskella, jak zdefiniowano w Raporcie, a nie Haskell-the-H-in-GHC. Wydaje się, że jest to założenie, które nie jest tak szeroko rozpowszechnione, jak sobie wyobrażałem (co stanowiło „100% czytelników”), lub prawdopodobnie byłbym w stanie sformułować jaśniejsze pytanie. Mimo to żałuję, że zmieniłem tytuł pytania, ponieważ pierwotnie pytano, jakie są gwarancje wywołania takiej funkcji.
ETA4: wydaje się, że pytanie to już się potoczyło i uważam, że nie ma na nie odpowiedzi. (Szukałem funkcji „zamknij pytanie”, ale znalazłem tylko „odpowiedz na swoje pytanie”, a ponieważ nie można na nie odpowiedzieć, nie poszedłem tą drogą.) Nikt nie przywołał niczego w Raporcie, które by to zadecydowało w obu przypadkach , który kusi mnie, by interpretować jako mocną, ale nieokreśloną odpowiedź „nie ma gwarancji dla języka jako takiego”. Wiemy tylko, że obecna implementacja GHC nie pominie oceny takiej funkcji.
Natknąłem się na rzeczywisty problem podczas przenoszenia aplikacji OCaml do Haskell. Oryginalna aplikacja miała wzajemnie rekurencyjną strukturę wielu typów, a kod zadeklarował szereg funkcji wywoływanych assert_structureN_is_correct
dla N w 1..6 lub 7, z których każda zwróciła jednostkę, jeśli struktura była rzeczywiście poprawna, i zgłosiła wyjątek, jeśli nie była . Ponadto funkcje te wywoływały się nawzajem, ponieważ rozkładały warunki poprawności. W Haskell lepiej sobie z tym Either String
poradzić za pomocą monady, więc zapisałem to w ten sposób, ale pytanie jako kwestia teoretyczna pozostało. Dzięki za wszystkie dane wejściowe i odpowiedzi.
źródło
h1::()->() ; h1 () = ()
ih2::()->() ; h2 _ = ()
. Uruchom obah1 (f 1)
ih2 (f 1)
i przekonaj się, że wymaga tego tylko pierwszy(f 1)
.f 1
jest „zastąpiony” przezundefined
we wszystkich przypadkach.... -> ()
może 1) zakończyć i zwrócić()
, 2) zakończyć z błędem wyjątku / czasu działania i nie zwrócić niczego, lub 3) odejść (nieskończona rekurencja). GHC nie optymalizuje kodu, zakładając, że tylko 1) może się zdarzyć: jeślif 1
jest wymagany, nie pomija jego oceny i powrotu()
. Semantyka Haskella polega na jej ocenie i sprawdzeniu, co dzieje się wśród 1,2,3.()
tym pytaniu nie ma nic specjalnego (ani typu, ani wartości). Wszystkie te same obserwacje zdarzają się, jeśli zastąpisz() :: ()
je, powiedzmy,0 :: Int
wszędzie. To wszystko nudne stare konsekwencje leniwej oceny.()
typu()
iundefined
.Odpowiedzi:
Wygląda na to, że wywodzisz się z założenia, że typ
()
ma tylko jedną możliwą wartość,()
a zatem oczekujesz, że każde wywołanie funkcji zwracające wartość typu()
powinno być automatycznie zakładane, aby rzeczywiście wygenerowało tę wartość()
.Nie tak działa Haskell. Każdy typ ma jeszcze jedną wartość w Haskell, a mianowicie brak wartości, błąd lub tak zwane „dno”, zakodowane przez
undefined
. Tak więc faktycznie odbywa się ocena:jest odpowiednikiem języka podstawowego
lub nawet (*)
i rdzenia
_Case
jest zmuszanie :Wartość jest zmuszona do osłabienia normalnej postaci głowy. Jest to część definicji języka.
Haskell nie jest deklaratywnym językiem programowania.
(*)
print x = putStr (show x)
ishow () = "()"
, więcshow
połączenie można całkowicie skompilować.Wartość jest rzeczywiście znana z góry jako
()
, a nawet wartośćshow ()
znana jest z góry jako"()"
. Nadal akceptowane semantyka Haskell żądać wartość(f 1)
jest zmuszony do słabej głowy postaci normalnej, przed przystąpieniem do drukowania z góry wiadomo, że ciąg,"()"
.edycja: Rozważ
concat (repeat [])
. Powinien być[]
, czy powinien być nieskończoną pętlą?Odpowiedź „deklaratywnego języka” na to jest najprawdopodobniej
[]
. Odpowiedź Haskella brzmi: nieskończona pętla .Lenistwo to „deklaratywne programowanie biednego człowieka”, ale nadal nie jest prawdziwe .
edit2 :
print $ h (f 1) == _Case (h (f 1)) _Of () -> print ()
i tylkoh
jest wymuszony, a nief
; i produkować swoją odpowiedźh
nie ma nic na siłę, zgodnie z jego definicjąh _ = ()
.uwagi pożegnalne: lenistwo może mieć rację bytu, ale nie jest to jego definicja. Lenistwo jest tym, czym jest. Definiuje się, że wszystkie wartości są początkowo thunkami, które są zmuszane do WHNF zgodnie z pochodzącymi wymaganiami
main
. Jeśli pomaga uniknąć dna w określonym przypadku zgodnie z jego konkretnymi okolicznościami, to robi. Jeśli nie, nie. To wszystko.Pomaga wdrożyć go samemu, w twoim ulubionym języku, aby poczuć to. Ale możemy również prześledzić ocenę dowolnego wyrażenia, ostrożnie nazywając wszystkie wartości pośrednie, gdy tylko powstają.
Idąc za raportem , mamy
następnie
i z
kontynuuje
Teraz sekcja 3.17.3 Formalna semantyka dopasowywania wzorców raportu mówi
i przypadek
(r)
, w schemacie 3.2 stany()
jest konstruktorem danych typu arity 0, więc jest taki sam jaka zatem ogólny wynik oceny jest taki
⊥
.źródło
case
rdzeń i ignorowałem ziejącą dziurę. :) Zredagowałem, aby wspomnieć o rdzeniu.show
wywołane przezprint
? Coś w stylushow x = case x of () -> "()"
case
rdzeń, a nie sam Haskell. Haskell jest tłumaczony na Core, który ma wymuszeniecase
, AFAIK. Masz rację, żecase
w Haskell samo w sobie nie narzuca. Mógłbym napisać coś w Schemacie lub ML (gdybym mógł napisać ML to znaczy) lub pseudokod.print
zmusza tyle wydruków, ile potrzeba. nie patrzy na typ, typy są usuwane, usuwane, do czasu uruchomienia programu wybrana jest już poprawna procedura drukowania i kompilowana zgodnie z typem w czasie kompilacji; ten podprogram nadal będzie wymuszał wartość wejściową WHNF w czasie wykonywania, a jeśli nie zostanie zdefiniowany, spowoduje błąd.