--- *Stage 1: Foundation.*
Progress Meter:
- [WIP] Knowledge-Representation Improvements
- [X] Better syntax, cleaner API & refactoring
- [W] Meta info in slots
- [W] Advice
- [X] Change predicates
- [W] Multi-Methods (value-based)
- [WIP] Constraint Management Improvements
- [X] Better syntax & some refactoring
- [X] Multi-input/output constraints
- [X] Advice (for constraints)
- [X] Store constraints as KR objects
- [X] Thread-safe execution
- 2D Graphics
- [ ] Linear Algebra and Geometry additions
- [ ] 2D API for geometry + Backend (via SDL)
- [ ] Event objects + Backend (via SDL)
- [ ] Text API + Backend (via Pango)
- [ ] Cell definition
- [ ] Configurations
- [ ] A demo application (after everything else)
---
Hi, all! A quite decent month of development behind.
--- Multiple-Output Constraints
So, here's how multiple-output constraints turned out:
(Ξ (r (Ξ (u (Ξ (y 5)))
(v (Ξ (x 10)))
(w (Ξ (x 15)))))
(c (ζ () (is-a multi-out#)
(filter
(λ/ζ (and (schema-p value) (symbol-package slot))))
(leaf-filter
(λ/ζ (numberp value)))
(leaf-ζ
(λ/ζ (ζ ((out (>> slot))) (strength 100) (:= out 123))))
(path
'(r :* :*)))))
This says that all the schemas under r (namely: u, v, and w) will have
all their slots that contain numbers be constrained to 123 at the
strength of 100.
The path description may include any number of wildcards or specific
slot names. The provided filters apply to all the wildcards, but a
wildcard may specify its own filter via a property-list syntax:
(path `(<...> (:* :filter ,(λ/ζ <...>)) <...>)
(Note: λ/ζ is not some special syntax, just a very simple shorthand
macro that expands to a lambda taking a few relevant arguments.)
Previously I thought of these as "parameterized pointer variables" and
that it would be necessary to change the internals of constraint
handling, but it turned out that with a few additions and tweaks, the
constraints as of now are already more than capable of constructing this
behavior. That was a pretty cool revelation (if you will). In fact, a
constraint like that can be written by the user.
There are some inefficiencies left in the implementation -- many
extraneous constraints are generated -- but that will be fixed with a
few more tweaks here and there.
There are also the "super wildcards" *** -- these apply to the whole
tree starting at the root and are not limited by depth.
Generally speaking, multiple-output constraints give a way to
dynamically enforce some property / add some behavior to certain parts
of a tree.
--- Some Other Constraints
Some other new constraints allow making statements such as:
- /collect, filter and map certain slots of a schema into another schema/,
- /collect and filter slot names into a list/,
- /sub-inherit a schema/.
Sub-inheritance will very likely be used for context-handling in the
future. And slot-name-collection provides a form of multi-input -- not
as general an approach as multi-output above, but should be plenty
enough. General-purpose constraints such as these ones above and a few
others will be supplied along with core KR, most certainly.
--- Constraint Interface Changes
Previously, I said how constraints could now be inherited and named, and
also how they could carry data via a custom /sub/ slot (with a
user-supplied inheritance function). Turns out this sucked and
constraints need to carry a bunch of data to be useful, and access to
this data is too damn painful through an indirection. So, named
constraints now expand to defstruct definitions (while still resembling
schema definitions). For instance:
(ζ some-constraint# (out in)
(some-value value) ; your data
(fn (λ (x) ...))) ; more data
(:= out (with-slots (some-value fn ...) self
(+ some-value (funcall fn in)))))
So, as you can see, fn and some-value become a part of the standard
constraint definition, and so can be accessed with the usual with-slots
syntax (with-accessors is too damn painful and unnecessary for this,
which I will be documenting as a convention of sorts). So, this is way
more usable.
--- Thread Safety
For KR to be used in multiple threads, it was necessary to make it
thread-safe. The only non-thread-safe parts were the special
variables. Thankfully, bordeaux-threads:make-thread takes
:initial-bindings where thread-local bindings can be
specified. Supplying all the special variables seems to do the trick,
and running multiple threads with KR object manipulation now works
without any issue.
--- SDL2 Wrappers + Thoughts
I have updated the SDL2 wrappers for the latest version. A potential
contributor got interested in rendering shapes, and things seem to be
going well. I have previously been considering Pango for the backend,
but he has also advised to look into freetype-gl. While less capable in
terms of internationalized complex text layouts, and maybe not as
popular as Pango, freetype-gl ought to be the fastest possible option
for SDL2. It should also be capable enough to handle all of the
use-cases I am thinking currently about. It's written in C -- which
brings me to the point that it might be sensible to package the backend
with some C code in the future (emphasis: for the backend only, the core
project is CL-only). This C part may be later rewritten in Lisp or it
may stay as part of the backend -- we will just have to see what works
best.
--- Next Steps
Things are shaping up. But there are still some steps left that are
necessary to complete KR/Constraints.
For one, I have gathered a backlog of quality-of-life improvements:
things concerning naming, conventions, useful functions, shorthand
macros. The user-perspective stuff. Won't hurt to do some refactoring
here and there. Some minor features. There's a bunch of TODOs all over
the codebase. All of this needs addressing.
- [ ] quality-of-life improvements: cleanup, refactoring, better naming,
syntax, minor bugs
Also, it would be pretty good to have a value-based dispatch system. The
available CLOS-driven dispatch systems work on types and subtypes. This
doesn't cover the schema situation very well, as schemas are all of a
single type: `schema'. Of course, value-testing is just of a more
general nature overall. Advice is as well just nonexistent, as far as I
can tell (:before and :after is not proper advice). The interface for
removal, addition and inspection of methods is function-driven -- when
it should just be objects that you can view and modify instead, like
data. So, the existing solutions just don't ring like good
foundation. But I think there might be a possibility of cannibalizing
some of them, such as [2] and [3]. And cl-environments [1] can provide
custom declarations and information useful for inlining.
- [ ] value-based dispatch
Of course, I will be writing the documentation too.
- [ ] comprehensive user documentation for KR
All of these things will likely take me the next 6-8 weeks to do. And
then will come the definition of cells, with the rest of Fern not being
that far away either. Quite exciting.
Best,
-- DK
[1] https://alex-gutev.github.io/cl-environments/
[2] https://github.com/numcl/specialized-function
[3] https://github.com/digikar99/polymorphic-functions