árbol validacion sintaxis sintactico sintactica informatica ejemplos concreta compiladores arbol analizador analisis abstracto abstracta haskell functional-programming rust

haskell - sintaxis - validacion sintactica



¿Es posible representar la sintaxis abstracta de orden superior en Rust? (1)

En Haskell, es muy fácil escribir tipos de datos algebraicos (ADT) con funciones. Esto nos permite escribir intérpretes que se basan en funciones nativas para sustituciones, es decir, una sintaxis abstracta de orden superior (HOAS) , que se sabe que es muy eficiente. Por ejemplo, este es un simple intérprete de λ-cálculo que usa esa técnica:

data Term = Hol Term | Var Int | Lam (Term -> Term) | App Term Term pretty :: Term -> String pretty = go 0 where go lvl term = case term of Hol hol -> go lvl hol Var idx -> "x" ++ show idx Lam bod -> "λx" ++ show lvl ++ ". " ++ go (lvl+1) (bod (Hol (Var lvl))) App fun arg -> "(" ++ go lvl fun ++ " " ++ go lvl arg ++ ")" reduce :: Term -> Term reduce (Hol hol) = hol reduce (Var idx) = Var idx reduce (Lam bod) = Lam (/v -> reduce (bod v)) reduce (App fun arg) = case reduce fun of Hol fhol -> App (Hol fhol) (reduce arg) Var fidx -> App (Var fidx) (reduce arg) Lam fbod -> fbod (reduce arg) App ffun farg -> App (App ffun farg) (reduce arg) main :: IO () main = putStrLn . pretty . reduce $ App (Lam$ /x -> App x x) (Lam$ /s -> Lam$ /z -> App s (App s (App s z)))

Observe cómo se usaron las funciones nativas en lugar de los índices de Bruijn. Eso hace que el intérprete sea mucho más rápido de lo que sería si sustituyéramos las aplicaciones manualmente.

Estoy consciente de que Rust tiene cierres y muchos tipos de Fn() , pero no estoy seguro de que funcionen exactamente como los cierres de Haskell en esta situación, y mucho menos de cómo expresar ese programa dada la naturaleza de bajo nivel de Rust. ¿Es posible representar a HOAS en Rust? ¿Cómo se representaría el tipo de datos Term ?


Como fanático del cálculo lambda, decidí intentarlo y, de hecho, es posible, aunque un poco menos interesante que en Haskell ( enlace del patio de recreo ):

use std::rc::Rc; use Term::*; #[derive(Clone)] enum Term { Hol(Box<Term>), Var(usize), Lam(Rc<dyn Fn(Term) -> Term>), App(Box<Term>, Box<Term>), } impl Term { fn app(t1: Term, t2: Term) -> Self { App(Box::new(t1), Box::new(t2)) } fn lam<F: Fn(Term) -> Term + ''static>(f: F) -> Self { Lam(Rc::new(f)) } fn hol(t: Term) -> Self { Hol(Box::new(t)) } } fn pretty(term: Term) -> String { fn go(lvl: usize, term: Term) -> String { match term { Hol(hol) => go(lvl, *hol), Var(idx) => format!("x{}", idx), Lam(bod) => format!("λx{}. {}", lvl, go(lvl + 1, bod(Term::hol(Var(lvl))))), App(fun, arg) => format!("({} {})", go(lvl, *fun), go(lvl, *arg)), } } go(0, term) } fn reduce(term: Term) -> Term { match term { Hol(hol) => *hol, Var(idx) => Var(idx), Lam(bod) => Term::lam(move |v| reduce(bod(v))), App(fun, arg) => match reduce(*fun) { Hol(fhol) => Term::app(Hol(fhol), reduce(*arg)), Var(fidx) => Term::app(Var(fidx), reduce(*arg)), Lam(fbod) => fbod(reduce(*arg)), App(ffun, farg) => Term::app(Term::app(*ffun, *farg), reduce(*arg)), }, } } fn main() { // (λx. x x) (λs. λz. s (s (s z))) let term1 = Term::app( Term::lam(|x| Term::app(x.clone(), x.clone())), Term::lam(|s| Term::lam(move |z| Term::app( s.clone(), Term::app( s.clone(), Term::app( s.clone(), z.clone() )))))); // λb. λt. λf. b t f let term2 = Term::lam(|b| Term::lam(move |t| Term::lam({ let b = b.clone(); // necessary to satisfy the borrow checker move |f| Term::app(Term::app(b.clone(), t.clone()), f) }) )); println!("{}", pretty(reduce(term1))); // λx0. λx1. (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 (x0 x1))))))))))))))))))))))))))) println!("{}", pretty(reduce(term2))); // λx0. λx1. λx2. ((x0 x1) x2) }

Gracias a BurntSushi5 por la sugerencia de usar Rc que siempre olvido que existe y a Shepmaster por sugerir eliminar la Box innecesaria bajo Rc en Lam y cómo satisfacer al prestamista en cadenas Lam más largas.