Dear lazyweb: is this language design worth implementing? https://github.com/enkiv2/misc/blob/master/pox/README.md
1) If you already have a Prolog like language for the constraints, why do you need to add a second, functional language for the definitions, which will need to be built and debugged separately? Why not just do like Prolog and make everything be constraints? Then it's all just one language.
2) Global variables? Global to where? I don't think that will scale at all.
3) There's got to be a better way to map anonymous arrays into JSON keys than that, surely?
My answer to #3 is:
a) either use something like my T-expression format, so, all expressions are Prolog terms stored as JSON arrays
b) or if you MUST use JSON objects for speed, then, reserve one special key, maybe '_', and under that key put a meta-object, and in that meta-object put all the stuff that won't fit in a normal JSON object, like object or array-valued keys. Then you won't get weird key conflict problems later on.
For #2, I feel like you're trying here to reproduce the hard divide that Prolog has between 'variables' (local to rules, with no nestable scopes BUT also with object-like identity as 'unknowns') and 'the database' (which is one global namespace)? But that divide is something I think Prolog got wrong, and best not to reproduce in a modern language.
The MicroKanren approach would be I think to get rid of the globals/Database and have everything be variables?
For #1, I feel like it *might* be useful for a Prolog-like language to have rules split into two parts, one being... non-Turing-complete conditions? and the other any potentially Turing-complete bits? And that might make reasoning about rules easier?
Prolog is really messed up as a functional language though... the whole 'rules/variables can't be nested' thing means it doesn't have proper closures. We need to work out how to do closures in logic properly.
As an example of how hard it is to do: the IF language 'Dialog' takes Prolog and adds a 'closure' feature based on a list, which fails SPECTACULARLY due to how Prolog variables work differently to Lisp/Scheme ones.. you can't use it to write a recursive function, like 'reduce', cos a variable gets bound and stuck to a value and ugh nope.
There's gotta be a way to unify Prolog and Scheme, and I'm still not sure that MicroKanren is the best we can do.
@natecull @vertigo @enkiv2 The lack of nesting is just the equivalent of a language not having nested functions, which is pretty common. You don't need functions like reduce or map in Prolog because you can just directly recurse.
There's no reason in a Prolog-like language you couldn't have local rules using a "let"-like syntax, and anonymous predicates.
<< the equivalent of a language not having nested functions, which is pretty common >>
It's common, *but it's bad*. There are a whole class of useful functional programming things that you simply can't do in a language without nested functions.
This lack is one reason why the functional style has taken so long to be adopted, because languages like C didn't allow closures, and a generation grew up not understanding how they worked.
<< You don't need functions like reduce or map in Prolog because you can just directly recurse.>>
No, you need them. You just don't realise that you need them.
Prolog has an idiom of writing recursive functions from scratch *because* it's hard to do higher-order functions.
They can be sorta-kinda simulated using atoms for predicate names, but, anonymous predicates are really awkward to define, and there's no good standard for it. It's just really messy.
@natecull @enkiv2 @vertigo @freakazoid
re: (b) -- I'm just using the style that mycroft already uses for synthetic / compiler-generated anonymous functions. They are first-class keys for performance reasons (this is not really json -- it's msgpack, but parsed into native dictionary format, so probably a hash table)
@natecull @enkiv2 @vertigo @freakazoid
(1) this is an extension of the idea (from theorem-proving languages like idris, and from TDD) that methods should have contracts but that naive implementations can be generated from the contracts. The idea is that you only write an actual implementation for performance reasons. You can have a functional mockup based on constraints (like prolog), but then (unlike prolog) write fast procedural code.
@enkiv2 @natecull @vertigo @freakazoid
(2) global to the executable (in fact, the plan is that the executable, during execution, periodically gets rewritten to store these persistent variables). This idea is from MUMPS but also a lot like image-based environments like smalltalk & like resource database environments like mac & android apps & palmos.
<< (2) global to the executable >>
That's what I mean about not scaling. This seems pegged to a very small-scale notion of 'executable' but... what even is an executable in the future, if we're running across something like Scuttlebutt?
What I think we require at minimum is a language with functions / procedures / predicates whose arguments (or even the function itself) can be (immutable) resources anywhere from the local desktop to the Internet.
At least that's what I think we need if we want to do hypertext, or hyperdata?
And maybe a 'function' is a more universal entity than a 'predicate', in that it's something you can put into a templated hyperdocument that gets replaced at load or read or crawl time with ... 'a value that it points to'. (I think maybe something like John Schutt's vau-calculus 'combiners', ie a sort of lambda-macro).
To me that seems a basic thing that the logic of hypertext needs?
I guess this is why I've been gravitating to the idea that 'arrays are more general than cons pairs'.
I think I want a universal, global hyperdata system where:
* Documents are arrays, of any size
* Arrays can have a type field, so are very cheap objects
* A pointer or slice is a small array of [array, start, end]
* Physical storage is an array of blocks
* Blocks are arrays of words/bytes
* A file is a document locating blocks
* Links are pure function calls
Then the core eval function would do something like:
* fetch and cache arrays from local and network storage
* allow arrays to be continued with a pointer if they're above a certain length
* evaluate function-calls as macro-like things (ie, a triple of function, environment, program text) but bound this somehow at the call level so that it doesn't send private local data to untrusted functions and can't consume infinite resources
* cache results (and GC them)
@mathew @vertigo @natecull @enkiv2 In Io and Self, activation records are just objects, so named argument support is natural and symmetric. In Io objects are hash tables, and in Self they're like JS objects. Self is the language that pioneered the polymorphic inline cache later used in V8 IIRC.
The nice thing about named arguments is that it lets you avoid the Java and JS hack of chaining methods that mutate the object.
@enkiv2 @vertigo @natecull I've been partial to image-based languages since using LambdaMOO, Self, and Genesis. However, I think *orthogonal* persistence is a trap, because it tricks the user into thinking they don't have to worry about state, versioning, etc. I think the right approach is something like Erlang/OTP's behaviors (gen_server, gen_fsm, etc) when you're using hot code reloading; attach versions to each piece of state and have migration functions. You also want snapshots.
@enkiv2 I usually start by implementing a language's semantics because that's the most important part. That design says a lot about the translation steps, which aren't that important except with macros or other syntax extensibility features.
Personally I would not use messagepack as a final representation. You want something that's easily executed on your virtual machine, so a binary encoding of the postfix form seems like it'd be better.
@enkiv2 I also don't see the point of the intermediate prefix form; the Shunting Yard algorithm outputs postfix notation directly.
You talk about Forth, but the use of postfix form as an internal representation doesn't make it Forthy, nor does it necessarily even matter to the programmer unless you are letting them use it to extend the language.
I'm doing a stack language with some forthy names & conventions because it's what I find easiest to implement. You're right that the intermediate steps can probably be eliminated. I'll implement first with them & then try to eliminate them because I don't trust myself to parse a mix of prefix and infix to postfix reliably in one step.
@enkiv2 You can use a recursive descent parser that calls into an operator precedence parser. Each parse function should be able to return postfix notation directly because the current branch of the parse tree is represented implicitly by the host language's stack, so it should be in the right order already. If you need to do significant rewriting on the tree you may need to use an intermediate tree representation, though.
Right. Messagepack is there just so that we can separate out the declarations and definitions for everything & have them available & quickly accessible at runtime (skips making an AST). I was considering a packed binary thing with a symbol table but I'm not sure if it'll be worth the optimization work.
Une instance se voulant accueillante pour les personnes queers, féministes et anarchistes ainsi que pour leurs sympathisant·e·s. Nous sommes principalement francophones, mais vous êtes les bienvenu·e·s quelle que soit votre langue.
A welcoming instance for queer, feminist and anarchist people as well as their sympathizers. We are mainly French-speaking people, but you are welcome whatever your language might be.