Recently, I've released a small tool to write configurations (repo). The README is pragmatic, just a worked example, and doesn't claim anything extraordinary. However, my ultimate motive was to explore a new programming paradigm, or at least a paradigm that I — being a programmer for three dacades — have never heard of.
Describing the world using cartesian products isn't logical programming or object-oriented programming. It isn't functional programming in the strict sense and it's definitely not imperative programming.
I am not making any big claims now either, mind you. I would just like to explore the mechanics and the limits of this new paradigm. Maybe its use is limited to the niche area of writing config files? Or maybe it's a full-blown paradigm, a worthwhile peer to functional and imperative paradigms? I frankly don't know.
That being said, I want to address an obvious objection to the cartesian paradigm. The objection goes like this: "Cartesian program generates a description of the world in the form of a set of anonymous (unnamed) objects. You can iterate over them, but you cannot really reference any specific object. This makes the paradigm not useful for anything but config files, which don't, by their nature, require addressable objects."
To put it in practical terms, what if we wanted one object to be dependent on another object? Say, what if we wanted property X of object A to be twice the property X of object B:
How would you write getter function for property X? Two times what exactly?
var cfg = {
x: alt(0, 1, 2, 3),
y: alt(0, 1, 2, 3),
z: alt(0, 1, 2, 3),
get x() {
if(this.x == 3 && this.y == 3 && this.z == 3) {
return 2 * ????
}
...
}
}
Well, I think (prove me wrong, if you can) that this problem is really in the eye of the beholder. As long as you think as described above there's no way to solve the problem. However, once you start thinking in top-down manner rather than traditional bottom-up manner, the problem disappears. Let me explain.
In both imperative and funcional paradigms we are accustomed to start with smallest functions and then to combine them into more complex functional units.
Now have a look at a possible solution of our original problem:
var global = {
base: 1,
get expanded() {return this.base * 2}
}
var cfg = {
x: alt(0, 1, 2, 3),
y: alt(0, 1, 2, 3),
z: alt(0, 1, 2, 3),
get x() {
if(this.x == 3 && this.y == 3 && this.z == 3) return global.expanded
return global.base
}
}
What's going on here?
We've solved the problem by creating an object with a bigger scope (a global one) and referencing it from the smaller-scoped object (our configuration). Note that in both imperative and functional programming it works exactly the other way round: Bigger-scoped objects invoke smaller-scoped functions to solve problems in subdomains of their original problem.
Now, the above may look like just a nasty hack to get the desired behaviour. Before passing such verdict though, let's have a look at a worked real-world-like example.
Let's say we want to configure our production deployment in such a way that we run ten backend servers for each web server. We also have multiple datacenters, each running different number of web servers:
var newyork = {
name: 'newyork',
webservers: 7
}
var london = {
name: 'london',
webservers: 4
}
var tokyo = {
name: 'tokyo',
webservers: 2
}
var plan = {
datacenter: alt(newyork, london, tokyo),
get webservers() {return this.datacenter.webservers},
get backends() {return this.webservers * 10},
}
Having created the large-scope object (i.e. "global deployment plan") we can use it to create smaller-scoped objects, say, configurations for individual binaries:
webservers = {
plan: plan,
get datacenter() {return this.plan.datacenter.name},
cmdline: "/usr/bin/webserver",
get instances() {return this.plan.webservers}
}
backends = {
plan: plan,
get datacenter() {return this.plan.datacenter.name},
cmdline: "/usr/bin/backend",
get instances() {return this.plan.backends}
}
Let's try to expand 'backends' object, delete the 'plan' property that is only an intermediate phase of processing and not interesting in the result and print the whole thing out:
console.log(JSON.stringify(expand(backends), function(k, v) {
if(k == 'plan') return undefined
return v
}, ' '))
And here's the result, nicely matching our expectations:
[
{
"datacenter": "newyork",
"cmdline": "/usr/bin/backend",
"instances": 70
},
{
"datacenter": "london",
"cmdline": "/usr/bin/backend",
"instances": 40
},
{
"datacenter": "tokyo",
"cmdline": "/usr/bin/backend",
"instances": 20
}
]
So, once again: I am not claiming that cartesian programming is a full-blown paradigm, on a par with logical, functional or imperative programming. However, it seems that the most obvious objection does not really apply.
What are the other objections? I would love to hear about them.
Martin Sústrik, Apr 30th, 2017
Does SQL match your notion of Cartesian paradigm? It's declarative (not procedural or functional), and works over sets. The "larger scope" would be the whole set of data; the "smaller scopes" would be projections, selections, etc.
You can to similar things in PostgreSQL, which has the notion of literal tables (not standard SQL though). Instead of declaring variables you can create views. Nested types and inheritance may prove problematic to implement in SQL though.
But all it all, I think you are right: What I called cartesian programming is more or less relational programming, albeit with the focus on generating data rather than querying data.
Here's the code in this article translated to SQL:
The clarity of expression is lacking, but, algorith-wise it does the same thing.
Here's the testsuite example rewritten to SQL. I don't know how to model the inheritance relationship though:
The concept seems closely related to the “constraint programming” paradigm — more specifically, with finite domains. You declare a set of variables, each one taking its value in some finite domain. A program in this paradigm describes a set of assignments of these variables. It uses constraints to do this. If there is no constraint, then the “output” of the program is the whole Cartesian product. Constraints eliminate some assignments: by declaring “x == y * 2’, you're eliminating all assignments in which x and y do not satisfy the equality.
The CP frameworks I used did not feature struct-like objects as in your examples, but I suppose there are some that do. I'm not saying it's exactly equivalent to what you described in this article, but there are interesting similarities, and I think it's worth mentioning.
Ack. I've been thinking a lot about Prolog while contemplating the problem. But in the end, I think, most programmers would just not use Prolog (too exotic) while they may be OK with JavaScript.
I like actually like prolog, I wish that paradigm was used more and I think it makes sense in this setting. Fortunately you still get some prolog-like benefits in JS with inheritance and the getter syntax. Prolog style input with JSON as an output might be accessible though.
So what's the Baconian paradigm that complements this?
witheve.com, seems to do cartisian/relational programming Isn't this essentialy what Eve is?
You have a typo: "to explore the machanics ". Feel free to delete this comment once fixed.
Fixed. Thanks!
Resembles miniKahren[1] and Clojure/core.logic[2] quite a lot.
[1] http://minikanren.org/
[2] https://clojure.github.io/core.logic/
If we always tell that people will not learn language A , we will never make progress.
It would be better to build such a tool but then provide examples of more advanced languages that have already solved these problems.
Some will use the tool, others will learn a better language which will be much better in the end.
Table Oriented Programming http://wiki.c2.com/?TableOrientedProgramming
Post preview:
Close preview