The thing about trying to write a tiny language is that it's easy to write a 30 line interpreter or whatever but then you try to write anything nontrivial and it's like
Great, everything is lists, and when you get to like five or six levels of functions (which btw are ALSO lists) calling functions, it's REAL easy to introduce type errors and, say, accidentally rip the environment out of a closure list.
I'm this fucking close to writing a type system.
Bedtime thoughts:
Nominative, not structural. Everything is lists, too easy to collide.
No subtypes, higher rank types, type fns. Just tell me if I tried to use a Cat where a Dog was expected.
Partial. Something I can add incrementally to track down bugs.
Weird idea: a type box which wraps any value: (type cat '(1, meow)). Boxes are transparent to evaluation: add them anywhere with no change to downstream code. Assert types anywhere with (check cat x), which returns x, or throws.
okay so I... I implemented the dynamic/nominal type system I mentioned yesterday, where you can wrap any term in a type box, and it is... weird. About 30 lines to implement `type` and `check` primitives, a type term, and an untype caster for polymorphism. Primitive ops like addition/first/rest unbox types, otherwise basically no changes to the interpreter.
Can't say I've ever programmed like this before, but it actually kinda works, and you can add types incrementally to existing programs.
@aphyr Wait, so it's going Prolog -> Lisp -> miniKanren? That is beautiful, I love it!
I also like your comment on line 91 of that gist; logic variables are amazing!
For the code itself, any particular reason why all the semi-colons instead of defining more clauses for the predicate? I'd usually see like
foo(X) :- a.
foo(X) :- b.
...
vs
foo(X) :- ( a ); ( b ) ; ....
@jamesnvc honestly I have no idea what I'm doing--I know Erlang, but I just started teaching myself Prolog last weekend.
I started off with lots of separate clauses and assuming (incorrectly) pattern-matching had some notion of specificity or ordering. That put me in hellish states where every clause would count towards goal solving; you'd get the interpreter's answer, then false, false, false, false, etc. To get single answers for recursive clauses I started using reif (which is... finicky).
@aphyr Ah yeah, that's true. The implementations can do *some* optimizations to avoid extra choice-points, but it's pretty finicky & pretty ugly to take advantage of; that makes sense.
Yeah, reif is very cool -- I used it to write grammars for HTTP/2 that can run in either direction (i.e. parse or serialize) but it was very complicated to make it work (https://github.com/jamesnvc/http2_client/blob/master/prolog/frames.pl).
Very cool, in any case.