Replies: 4 comments 3 replies
-
On a tangential note of very little interest, I find it striking that this usage of DCGs is a strict superset of the semantic functionality of Clojure transducers: (transduce
(map identity) ;; transducing concept, effectively pass-thru here
(fn ([[n xs]] [n (reverse xs)]) ;; monadic arrity, terminal
([[n xs] x] [(inc n) (cons x xs)])) ;; diadic arrity, step fn
[0 '()] ;; initial value (like 2nd arg of phrase/3)
[1 2 3])
;;=> [3 (1 2 3)] In case anyone finds this remotely interesting, I've highlighted the anatomical similarities: ![]() ![]() ![]() ![]() ![]() However, as I said, DCGs are a strict superset here because Clojure transducers are not only unimodal, but they can only process sequences (not trees), and they certainly can't be used for parsing. Furthermore, transducers as you've seen are not easily composable -- they are all contained in one block of code. DCGs, ironically, do a better job of respecting the open-closed principle! |
Beta Was this translation helpful? Give feedback.
-
Interesting write-up, thank you a lot! One small suggestion I have for everyone from now on: Please add the following to your (setq ediprolog-prefix "") Since Scryer now supports embedded quads in programs, we can state toplevel interactions verbatim in programs: ?- member(X, "abc"). X = a ; X = b ; X = c. member(E, [L|Ls]) :- member_(Ls, L, E). member_(_, E, E). member_([L|Ls], _, E) :- member_(Ls, L, E). |
Beta Was this translation helpful? Give feedback.
-
To fix this, you need to "break up" the otherwise compact recursive rule. So semicontext notation is not that helpful here. |
Beta Was this translation helpful? Give feedback.
-
You can do quite crazy stuff with semicontex notation, the most obvious is describing natural language rules. I also find operational explanation to be more understandable, but I'm afraid it is a wrong kind of intuition to build. As a declarative programmers we should strive to develop declarative intuition. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
For the longest time I was not able to comprehend @triska's notes on semicontext notation:
The amount of witchcraft here seemed insurmountable. How on earth could one definition of
num_leaves//1
have a semicontext... context while the other definition did not!! Where did the context go?? I've had more than one melt down about it. However, @hurufu's really interesting implementation of a stack machine using DCGs with semicontext notation renewed my interest, so I tried once more.For some reason a few things finally started to click:
Operational Generalization
But how do we generalize this concept? How would we use it with more than one variable in the context?
Let's consider the following example:
If we unpack the call chain:
We observe that semicontext is continually pushed back on the stack and maintained in equilibrium.
If we maintain this principle, we can attempt some more audacious semicontext maneuvers. But it's not as straightforward as you might think. Let's say we want a second semicontext var that maintains a list of terms encountered:
Oh no, we failed! But why? Well if we use the previous analysis, the reason becomes obvious quickly:
Apparently the
--> [X]
was meant to "push" something onto the stack, but it had the opposite effect! The most obvious way to solve this is with a slight adjustment:Here, we injected the desired
X
as an argument tostate_processor//1
. But the outcome is strange, what happend?The well intentioned rule of
state_processor, [N,Xs] --> [].
did not have the intended effect of "preserving the context"!And now finally we have the correct formulation:
Except it is a bit disappointing that the elements come out backwards. However, our "mistake" in the previous round that revealed to us that we can use the terminal
state_processor//1
rule to do a "final pass" over the context, opening up some interesting options for more ambitious transformations:However, there are yet more footguns to be found.
You might be tempted to think, given your new understanding, that the following technique would work:
Why doesn't
double//0
have access to the twoN
s placed on the stack bya
?? This is because the semicontext of the head of the rule is not available to the goals of the body!We can observe this with the following tool:
If we use that in the scenario described:
We see clearly what happened.
This makes it clear that a goal in the body of a rule must have context provided by the return value of a previous rule.
To illustrate this point:
And if we "instrument" this with
dtrace/2
:(Note:
push//1
is very procedural language and not generally idiomatic for declaratively written semantics. This was done purely for an operational demonstration)At last, we have some general (operational) mental tools for working with semicontext! I'm sure a lot of this was obvious to many of you, but I had to simmer on this for nearly a year before it began to solidify, so hopefully someone finds it helpful!
I also realize that the operational description is very different than the actual list difference semantics happening, but those are still not fully understood to me yet.
There are still many techniques to learn.
Beta Was this translation helpful? Give feedback.
All reactions