3 ms·
I tried to integrate a C++ logic programming library (Castor) with Sqlite and a linear constraint solver to make a dynamically sizing MFC (Windows GUI) layout l
by phaedrus 8y ago
I tried to integrate a C++ logic programming library (Castor) with Sqlite and a linear constraint solver to make a dynamically sizing MFC (Windows GUI) layout library. I thought it would "dog on surfboard with jet pack" awesome, but it turned out more like "taking Space Shuttle to McDonald's drive-thru." That is, it made a few hard things simple but also made a lot of easy things complicated.
I still think logic programming has untapped potential for use in "regular" programs, but we haven't invented the best way to integrate it yet. In order to take advantage of bi-directional nature of logic relations (every argument can be input or output) you really need all the consituent pieces of that computation to be relational, as well. (To do what William Byrd calls "running backwards".) But to support this in an imperative language, where some of your relation is purely logical and some of it is implemented with user-defined callbacks, requires careful defensive programming in the C++ callback functions.
Moreover the inherently lazy/on-demand nature of logic programming does not fit well with the way debuggers for imperative programs work. This is not specific to logic programming (for example, debugging a regular expression that is inside a C++ program is a similar case). What is needed is a way to tell the debugger to display the file, line, and character position where the regex or logic relation or SQL statement was defined and not the position inside the engine which is interpreting that data.
I would contrast the "fully integrated, compute on-demand" way I was attempting to use a logic programming library with the way Sqlite natively works. With Sqlite, the steps of preparing a statement, looping over the rows, and finalizing the statement are all distinct steps (as called from C/C++ code). The Sqlite query planner can calculate the entire plan from the SQL string and the tables it manages internally. When Sqlite computes each next row as you step the prepared statement object, it is free to use whatever evaluation order was chosen by the query plan, because it all happens synchronously in the "step" call and then returns.
Of course the drawbacks of this approach (the famous impedance mismatch) are well-known to anyone who has used any kind of SQL database API from a C-family language. So I am not saying it is the best we can do. However I believe this API interaction strategy is one way (surmised - I haven't tried it yet) that logic programming could be integrated with an imperative program without it devolving into an un-traceable mess: treat it like using an SQL engine. Or like compiling a regex, stepping through, and reading the captures.
I find this answer ultimately disappointing, because I feel that such a powerful technique ought to transform how you think about and structure your whole program. And instead of solving the impedance-mismatch problem, it gives up on it. But having tried things the other way (coroutining logic and imperative code), if I were to try again, I would try this other way (treating the logic program like a database or query engine).
- specialist 8y agoHeh. I feel ya. -- My first real gigs were CAD/CAM based. I really thought both constraint and parametric programming were the future. Finally gave up on constraints for UI layout. My alternative was to hand code the heuristics, esthetics. I created DesignGridLayout (for Java), based on canonical grids (a graphic design notion). It's now defunct, but if I ever circle back to UI work, that's where I'll resume. -- That impedance mismatch has tripped me up my entire career. I forfeited in a different way. I inverted the work flow. I now use working examples to generate the code. In the case of databases: instead of an ORM to generate SQL, I use SQL to generate the JDBC/ODBC wrappers. I can't speak to implementing a query planner (way beyond my intellect), or your notion of starting with a different mental model, but it's food for thought.
- marmaduke 8y ago> SQL to generate the JDBC/ODBC wrapper SQLAlchemy has an ORM but it has a neat feature to exactly this: it can take a DB connection and build ORM objects from the schéma metadata.
- phaedrus 8y agoFor my GUI layout library I decided "snaplines" could be a more fundamental concept than grids (because a grid can be defined with horizontal and vertical snaplines). Essentially it's just a linear constraint like "there exists a variable X_snap1, such that box1.right <= X_snap1 and box2.left >= X_snap1." The key is that if you re-use the same snapline variable for subsequent rows, it aligns a column. Another key idea was that the snaplines are completely independent from the logical (hierarchical, tree) structure of the "document" (aka form). So like if you wanted controls above and below something to be horizontally aligned with each other, but not affect content in the middle, you could do that by just only associating some controls with those snaplines; the fact that the snapline cuts across the middle content need not affect it because it's an abstract "membership" in a group not a physical barrier. How I came to this design was I had done XAML layouts with WPF on Windows, and while I preferred to use the Grid tag to solve most layout problems, I was also frustrated with its limitations: you kind of have to plan ahead, if you discover you need another alignment line you have to renumber things, or maybe you need to fuse certain cells, etc. I realized that I was using the grid to get alignment lines, but didn't (necessarily) want all of the other behaviors which come with it.