Navigation
Outline:
Last modification
2025-09-04
This is smol web. If you don't like it, fix your browser.

Comeback: Prolog je typový systém Haskellu#

Pokud se oprostíme od několika detailů (např. od datových konstrukturů, polymorfismu a několika dalších drobností včetně rekurze), je typový systém Haskellu poměrně jednoduchý a popsatelný několika řádky Prologu. Kromě rekonstruování těchto několika řádek je cílem úkolu F ještě (trochu užitečnější) drobné opakování Prologu před zkouškovým.

Trochu zjednodušený Haskell#

Předem, když odstraníte konstruktory, polymorfismus a rekurzi, zbyde vám z Haskellu jen obyčejný lambda kalkulus, tj. jazyk, ve kterém existují jen 3 věci:

V lambda kalkulu přímo neexistují čísla, booleany, stringy, ani jiné datové typy, protože bez datových konstruktorů je vyrobit nejde. Kupodivu, i bez toho je to plnohodnotný programovací jazyk a jde v něm naprogramovat cokoliv, tj. i čísla, booleany a stringy.

Úkol, část 1#

Vyrobte predikát parse_lambda/2 tak, aby platilo např.:

parse_lambda("\a -> \b -> b (b a)", lambda('a', lambda('b', app('b', app('b', 'a'))))).

Pro jednoduchost uvažujte jen proměnné vyrobené z jednoho malého písmene, jen lambdy s jednou abstrahovanou proměnnou, a whitespace neřešte (tj. výraz z příkladu můžete považovat za ekvivalentní "\a->\b->b(ba)".

Podobně jako v Haskellu, lambdová šipka platí “kam může”, tj. většinou až do konce závorky nebo do konce výrazu. Tj. \a -> a b je vždycky \a -> (a b), nikoliv (\a -> a) b.

Upozornění: aplikace funkcí jsou asociativní zleva, tj. aaa je (aa)a.

Typový systém#

Pokud máte v ruce rozparsovaný lambda kalkulus, odvodit z něj typ jde už celkem jednoduše.

Nejdřív potřebujeme vyrobít jazyk typů. Ten se sestává jen z:

V Prologu typ funkce budeme reprezentovat pomocí struktury fun/2 a prologových proměnných, tj. např. typ a -> b -> b budeme psát třeba jako fun(A, fun(B, B)).

Při odvozování typu si musíte pamatovat, jakým datovým proměnným jste už přiřadili jaký typ. Takovou informaci ukládáte do tzv. kontextu, což je vlastně jen asociativní seznam, ve kterém k reálným proměnným přiřazujete typy.

V našem případě navíc zadarmo můžete uvažovat, že všechny výrazy jsou uzavřené, tj. každá proměnná ve výrazu je uvnitř nějaké lambdy, která ji zachycuje.

Pak to už jde jednoduše:

Pro příklad odvodíme typ \a -> \b -> b a: (zanoření seznamu odpovídá zanoření rekurze)

Pokud si výsledek přepíšete zpátky do běžné šipkovité notace, vyjde vám X1 -> (X1 -> V) -> V, což mimochodem (až na jména proměnných) odpovídá tomu, co si o tom samém výrazu myslí Haskell:

ghci> :t \a -> \b -> b a
\a -> \b -> b a :: t1 -> (t1 -> t2) -> t2

O systému si kdyžtak můžete víc přečíst tady.

Úkol, část 2#

Vyrobte predikát lambda_typ/2, který implementuje popsané odvozování typu z rozparsovaného výrazu. Pro kontrolu odvoďte typ \f -> \g -> \x -> g(f(g(f(g x)))).

Disclaimer#

Pokud pro parsování rozumně použijete gramatiky, řešení vyjde zhruba na 20 neúplně obsazených řádků Prologu.