current status:

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.

I'm trying to figure out whether I try to do something static in the host language (is this even possible given I have fexpr-style macros and eval?), or something dynamic. Like... what's the *minimum* viable typechecking I can get away with that will still help me distinguish between my dozen-odd "types" of lists?

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.

I added a sort of dynamic struct thing earlier with (struct cat meow 2) and (destruct cat x) => (meow, 2) and that turned out to be a huge bonus to finding type errors, but it *won't* help me for typing higher order functions...

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.

one of the best parts of this program has been finding out what Weird Unicode Codepoints the SWI-Prolog parser thinks are variables vs atoms.
@aphyr Ooh, you're using SWI? Nice, I've gotten involved in that community over the last couple years & I've come to really love the language and some of the folks involved. If you have any issues, the Discourse at https://swi-prolog.discourse.group/ is quite good - Jan, the primary developer is known to add features/fix bugs in response to posts there in a matter of hours :p
SWI-Prolog

SWI-Prolog forum

SWI-Prolog
@jamesnvc oh cool! I don't know if... well... I don't know if they'd LIKE what I've done with the language, exactly 🤣 https://gist.github.com/aphyr/b593780556da210b8e661e8db778cfb1
prolispren.pl

prolispren.pl. GitHub Gist: instantly share code, notes, and snippets.

@aphyr 😆 An iconoclast where ever you go! 😂
@jamesnvc I started off actually trying to write an invertible relation to generate lisp quines and got so frustrated I decided to just bootstrap minikanren inside the lisp intepreter

@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).

@jamesnvc Failing that, I fell back to aggressive use of extra-logical cuts. Where possible, I've been trying to write the exclusive disjunctions logically, using nested ((a) ; (b)).

@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.

jamesnvc/http2_client

Prolog HTTP/2 Client. Mirror of https://gitlab.com/jamesnvc/prolog_http2_client - jamesnvc/http2_client