My little pet peeve with prolog is the lack of context parameters (which can be thought as a type of abstraction).
For example, imagine I'm writing a maze solver. The maze solver predicate receives obviously, but it has to pass as a parameter the maze again and again to all sub-predicates. There is no concept of "current maze", like in OO you would have with this.maze, or in Haskell you would do with a reader monad. As a result, all the "internal" predicates in a module get full of parameters all they do is pass around until some final predicate uses it to do some calculation.
Either that or you do the assert/retract dance and now you have bigger problems.
Quantum Prolog has this feature where you can query against a dynamic database, not just the global default database; ie. where in regular Prolog (and in Quantum Prolog as well of course since it's full ISO) you query
p(a).
p(b).
?- p(X)
you can instead query
KB = [ p(a), p(b) ],
KB ?- p(X)
introducing a clause-list as first parameter to "?-".
In the description [1], this is used to avoid destructive database manipulation via assertz/retract builtins, and thus to allow much more complex combinatorial planning problems and action post-conditions to be solved/optimized without resorting to ad-hoc hacks. But you can also use this for mere convenience in large knowledge graphs, and a technique very similar to it, albeit implemented in Prolog itself and not provided with native speed, has been used as a historic extension to Prolog DCG parsing (cf. definite-clause translation grammars by Dahl et al).
There are multiple ways to accomplish this, but the one that is the most straightforward is to simply make an object mapping of the type you are familiar with via AVL trees[1]. Easy way to get the "this.maze" semantics. You can get global context and local context via "blackboard"[2] semantics.
However quite frankly the most powerful way to do this is not obvious because it doesn't translate well to other languages: meta-interpreters[3].
For [1], you would either need to pass the AVL tree around or to have it as a global (which is not wanted), and instead pass the key (the "this" context which is different for different mazes) for the context around.
For [2] again you have a global table (with copying semantics as for assert/retract? or maybe without copying? the docs don't say). But again you would need to pass a key around.
[3] is... yeah. I mean, sure, you could demonstrate this with a toy metainterpreter on a toy example. But do you really want to run your whole application inside a metainterpreter?
One could also abuse DCG syntax, with the context being some state object instead of a DCG list.
A more practical way would be Logtalk-like code generation. The most practical way would be actual Logtalk. Unfortunately, last I heard Scryer refused to host Logtalk for no real reason.
> For [1], you would either need to pass the AVL tree around or to have it as a global (which is not wanted), and instead pass the key (the "this" context which is different for different mazes) for the context around.
Not sure how tracking references is different here than any other programming language. Passing objects around is pretty common in OO programming. An AVL tree is just the underlying abstraction used to implement the object. You don't have to implicitly supply the "this" parameter to each "object" predicate if you don't want to -- you could insert that yourself via compilation (with term/goal expansion)[4] or interpretation (via meta-interpreters) if you were really interested in that level of syntactic sugar.
> For [2] again you have a global table (with copying semantics as for assert/retract? or maybe without copying? the docs don't say). But again you would need to pass a key around.
You could use global or local semantics as desirable. The blackboard has a global version that is persistent across application state or a local version that provides lexical context which unwinds on backtracking. Not sure how "passing a key around" is different than most other forms of programming, but if you wanted to compile it away, please review the techniques listed above.
> [3] is... yeah. I mean, sure, you could demonstrate this with a toy metainterpreter on a toy example. But do you really want to run your whole application inside a metainterpreter?
A "toy" meta-interpreter? Meta-interpreters are as fundamental to Prolog as for-loops are to Python. Certain applications, such as games, are run "entirely inside a loop", so I'm not sure how this criticism applies. You can run as much or as little of your application inside a meta-interpreter as you want. The value proposition of Prolog is that the simple homoiconic syntax allows for powerful meta-interpretation. I'd suggest checking out the video I linked to in [3] if you have doubts on that topic.
> One could also abuse DCG syntax, with the context being some state object instead of a DCG list.
State transitions (a sequence of states) are a great use for DCG syntax! I wouldn't call that "abuse".
> A more practical way would be Logtalk-like code generation. The most practical way would be actual Logtalk.
If you are willing to give up the most powerful features of Prolog, sure.
> Unfortunately, last I heard Scryer refused to host Logtalk for no real reason.
> You don't have to implicitly supply the "this" parameter to each "object" predicate if you don't want to [...] if you were really interested in that level of syntactic sugar.
>>>> There is no concept of "current maze", like in OO you would have with this.maze, or in Haskell you would do with a reader monad. As a result, all the "internal" predicates in a module get full of parameters all they do is pass around until some final predicate uses it to do some calculation.
I would say that yes, the OP wanted exactly that level of syntactic sugar and your previous suggestions [1] and [2] were addressing something else entirely.
> you could insert that yourself via compilation (with term/goal expansion)[4]
Yes, that's why I meant above by "Logtalk-like code generation". Suggesting that I study the thing that I suggested feels a little condescending.
For example, imagine I'm writing a maze solver. The maze solver predicate receives obviously, but it has to pass as a parameter the maze again and again to all sub-predicates. There is no concept of "current maze", like in OO you would have with this.maze, or in Haskell you would do with a reader monad. As a result, all the "internal" predicates in a module get full of parameters all they do is pass around until some final predicate uses it to do some calculation.
Either that or you do the assert/retract dance and now you have bigger problems.