Przykład, w jaki sposób taki typ danych pozwala nam zamieszkiwać w dowolnym typie, podany jest w Turner, D.A. (2004-07-28), Total Functional Programming, sect. 3.1, strona 758 w Zasada 2: Typ rekursji musi być kowariantna „
Zróbmy bardziej skomplikowany przykład używając Haskell Zaczniemy z.«Złego»rekurencyjne typu danych
data Bad a = C (Bad a -> a)
.
i skonstruować Y combinator z niego bez innej postaci rekursji. oznacza to, że posiadanie takiego typu danych pozwala skonstruować każdy rodzaj rekursji lub zamieszkują dowolnego typu nieskończoną rekursji.
Y combinator w rachunku bez typu lambda jest definiowana jako
Y = λf.(λx.f (x x)) (λx.f (x x))
Kluczem do niej jest to, że możemy zastosować x
sobie w x x
. W językach pisanych maszynowo nie jest to bezpośrednio możliwe, ponieważ nie ma prawidłowego typu, który może mieć x
. Ale nasz typ danych Bad
Pozwala to modulo dodawania/usuwania konstruktora:
selfApp :: Bad a -> a
selfApp ([email protected](C x')) = x' x
Biorąc x :: Bad a
możemy rozpakować jego konstruktora i zastosować funkcję wewnątrz do x
sama. Raz wiemy, jak to zrobić, to jest łatwe do skonstruowania COMBINATOR Y:
yc :: (a -> a) -> a
yc f = let fxx = C (\x -> f (selfApp x)) -- this is the λx.f (x x) part of Y
in selfApp fxx
Zauważ, że ani selfApp
ani yc
są rekurencyjne, nie ma rekurencyjne wywołanie funkcji do siebie. Rekursja pojawia się tylko w naszym rekurencyjnym typie danych.
Możemy sprawdzić, czy skonstruowany kombinator rzeczywiście robi to, co powinien. Możemy wykonać nieskończoną pętlę:
loop :: a
loop = yc id
lub obliczyć powiedzmy GCD:
gcd' :: Int -> Int -> Int
gcd' = yc gcd0
where
gcd0 :: (Int -> Int -> Int) -> (Int -> Int -> Int)
gcd0 rec a b | c == 0 = b
| otherwise = rec b c
where c = a `mod` b
Dobra odpowiedź. Podoba mi się to eleganckie podejście z jego teoretycznymi wytłumaczeniami (osadzanie niepasującego rachunku lambda). Czy byłoby możliwe przedłużenie go tak, aby arbitralnie rekursji do danego języka (powiedzmy Agda)? –
@ PetrPudlák Tak, właśnie rozmawiałem z moim officemates, którzy są daleko lepszymi teoretykami niż ja. Konsensus jest taki, że to "Złe" nie dałoby wyrazu typu 'forall a. ' a' (co tak naprawdę Cię interesuje, rekursja to tylko sposób na dotarcie). Argument byłby następujący: możesz skonstruować set-teore- tyczny model Agdy; wtedy możesz dodać do tego modelu interpretację 'Bad' jako zestawu jednoelementowego; ponieważ w wynikowym modelu są jeszcze niezamieszkane typy, nie ma funkcji tłumaczenia pętli "Złych" terminów na terminy pętli innego typu. –