Set-based programming is a fairly old idea. Jack Schwartz[0] created SETL[1] in 1969.
SETL2[2] and SETLX[3] interpreters are available.
Here is a sample program[4] by Keith S. Thompson. I have removed the comment header for brevity.
program Fizz_Buzz;
fizz := {3, 6..100};
buzz := {5, 10..100};
fizzbuzz := fizz * buzz;
fizz -:= fizzbuzz;
buzz -:= fizzbuzz;
num := {1..100} - fizz - buzz - fizzbuzz;
line_map := {[n, "Fizz"] : n in fizz} +
{[n, "Buzz"] : n in buzz} +
{[n, "FizzBuzz"] : n in fizzbuzz} +
{[n, n] : n in num};
loop for i in [1..100] do
print(line_map(i));
end loop;
end Fizz_Buzz;
That translates fairly well into Python, with its set type and list/set/dict comprehensions.
def setrange(first, next, last):
return set(range(first, last+1, next-first))
fizz = setrange(3, 6, 100)
buzz = setrange(5, 10, 100)
fizzbuzz = fizz & buzz
fizz -= fizzbuzz
buzz -= fizzbuzz
num = setrange(1, 2, 100) - fizz - buzz - fizzbuzz
line_map = dict({n: "Fizz" for n in fizz}.items() +
{n: "Buzz" for n in buzz}.items() +
{n: "FizzBuzz" for n in fizzbuzz}.items() +
{n: n for n in num}.items())
for i in range(1, 100+1):
print(line_map[i])
Some SETL features Python lacks:
• No ellipsis notation for ranges; must use the "range" function and add one to the maximum value, or define a custom "setrange" function
• Can't concatenate dictionaries; must concatenate their "items" lists and convert back to a dictionary
• No consistent map-lookup notation; collections are indexed with brackets, but functions are called with parentheses
> B. K. Oxley (binkley) (guest) 30 Apr 2017, 11:42
> 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.
"
My immediate thought also was set-based programming, and in turn SQL and its kin.
I think that comment goes in the right direction. It's kind of like relational programming but with focus on generation of data rather than querying it. While writing this kind of thing in SQL would be a headache, maybe a simpler language that would translate to SQL or maybe an extension to SQL would be a good idea.
So I guess is should order a metric ton of painkiller for my potentiel headache.
"Relational programming using SQL with a focus on generation of data rather than querying it" is basically my job since 4-5 years. And i can't believe I'm the only one SQL guy out there doing that.
It very effective for data manipulation once you get the correct mindset.
I've tried to rewrite the testsuite example to SQL, but it's quite ugly. Also, I am not sure how to model the inheritance relationship:
CREATE TEMP VIEW boxes AS
SELECT 'box1' AS hostname, 'linux' AS os, 'x86-64' AS arch, 8 AS ram UNION
SELECT 'box2', 'freebsd', 'arm', 16 UNION
SELECT 'box3', 'windows', 'x86-64', 4 UNION
SELECT 'box4', 'illumos', 'sparc', 4;
CREATE TEMP VIEW compilers AS
SELECT 'gcc' AS binary, '4.8.4' AS version, '-o' AS output_option UNION
SELECT 'clang', '3.4.1', '-o' UNION
SELECT 'msvc', '15.00.30729.01', '/Fe';
CREATE TEMP VIEW tests AS
SELECT 'frobnicate' AS name, 'frbonicate.c' AS sources UNION
SELECT 'loadtest', 'loadtest.c helpers.c' UNION
SELECT 'end2end', 'end2end.c helpers.c';
CREATE TEMP view testsuite AS
SELECT *, compilers.binary || ' ' || tests.sources || ' ' ||
compilers.output_option || ' ' || tests.name AS cmdline
FROM boxes, compilers, tests
WHERE NOT(tests.name = 'loadtest' AND boxes.ram < 8);
SELECT * FROM testsuite;
To make it less ugly, and assuming you are using PostgreSQL you could make the code a little more appealing rewriting it like that. (If you are not using postgres WITH will probably not work but VALUES instead of UNION should still work). Please also notice that "binary" is quoted because it's a reserved postgresql word.
But this is a stand alone query, SQL really become useful IMHO if data is stored inside the DB or gathered automatically by the db. For the inheritance I'm really clueless, it depend on what you try to achieve and i'm just a DB guy.
As for the comment about stored data, that's actually the reason why I haven't considered SQL an option at first. The use case doesn't involve any stored data, so SQL intuitively doesn't sound like the right tool for the job.
In the end, I think the cartesian paradigm can be expressed using SQL, but you have to twist your traditional SQL mindset to do that. You have to forget about tables etc.
You know what, I'm not sure. I only learned anything about set theory after learning SQL, and I haven't yet taken the time to think/read about SQL's formal relation to mathematical sets, so I may just be holding onto on misinformed assumption born from ignorance.
I got the other problem: I've been away from SQL and databases too long to tell you. Set theory is one of those mathematical notations that can be used to model all kinds of things. They often prove out the simpler, easy-to-automate logics in more powerful ones like it. There's introductions to set theory online but here's what you might have not spotted:
It was also used in formal specifications like Z. Also, used in one, verifying compiler that cheated the spec-to-code correspondence by straight-up turning the logical specs into executable Prolog. On database side, it would be Datalog for you to look into to explore logic programming + queries. Plenty of info out there on that with one DB (Datomic) built around it.
"Cartesian programming" seems to be, or be nearly indistinguishable from, relational programming; the examples given in the readme for the referenced library amount starts out with a three-relation cross join, adds some computed attributes to the join, and then adds some filtering conditions.
You're not wrong, if the only verb is `c.expand()`. In Haskell you would write:
-- omitting the definitions as trivial...
cmdline (Suite box (Compiler cmd _ out_opt) (Test binary sources))
= intercalate " " [cmd, sources, out_opt, binary]
testSuites = [Suite srvr comp test |
srvr <- [box1, box2, box3, box4],
comp <- [gcc, clang, msvc],
test <- [frobnicate, loadtest, end2end],
not (binary test == "loadtest" && ram srvr < 8)]
(Note that Haskell lets you specify what `==` means so probably the above should say `test == loadtest` more directly...)
If there are other verbs then it might not be 100% equivalent. For example one might want a sort of "Non-uniform random choice monad," let's say a monad of "Choosers", which will quickly generate a random candidate test suite. If you wanted to support both, you'd want to quickly define:
class Monad m => MChoice m where
choose :: [x] -> m x
invalid :: m x
invalid = choose []
mfilter :: Bool -> m ()
mfilter True = return ()
mfilter False = invalid
instance MChoice [] where choose = id
Then you can say,
testSuites :: MChoice m => m TestSuite
testSuites = do
srvr <- choose [box1, box2, box3, box4]
comp <- choose [gcc, clang, msvc]
test <- choose [frobnicate, loadtest, end2end]
mfilter . not $ test == loadtest && ram srvr < 8
return $ Suite srvr comp test
In this respect maybe Cartesian programming represents a generalization of the list monad.
I think someone once proved that all programming languages (or just about) are equivalent in their expressiveness, and given that similar problems arise in all paradigms, it is not surprising that all paradigms have their own names for similar things. Just as monads and continuations are two different ways to express similar computational notions, it would not be surprising if there were others. None of these is more fundamental or "real" than the others. They are simply different ways to formally model an abstract concept.
By the title, I thought it might be referring to something like the esolang Befunge (https://en.wikipedia.org/wiki/Befunge), which operates on a grid rather than the usual linear sequence of instructions.
Here's what author told me on Lobste.rs about this:
"As for the motivation, it’s twofold.
Firstly, I have to deal with complex configurations on daily basis and most of what’s out there is pretty ugly. Approach in Cartesian tries to solve the ugliness without resorting to exotic paradigms. It’s (I think) something that your average JavaScript developer would be comfortable with, yet powerful enough to describe really complex systems.
Secondly, it’s a methodological experiment. I think it’s fair to say that inheritance has mostly failed us when we tried to model the complexity and irregularity of the real world. Therefore, this is a sketch of an alternative paradigm, one that uses cartesian products instead of inheritance. (But, as mentioned in the README, it can even be combined with classic inheritance.)"
I'll message author about the thread to see if he wants to answer any questions.
In the configuration space there's a certain irreducible complexity. Depending on what angle you come at things, you can try to abstract away details, but you need to mentally evaluate the abstractions anyway to figure out the effective configuration, to see if it makes sense.
I've long thought that configuration can be modeled as intersecting N-dimensional subspaces, where N is the number of configuration axes (e.g. production vs test vs dev is one axis, as are datacenter location, machine role, etc). You can say that all test machines enable feature flag X, or everything in datacenter Y should use network setting Y.net - and these are cross-cutting concerns that don't lend themselves to hierarchical composition (and thus inheritance is out).
The configuration settings themselves have a scope in the configuration space. But then you need to evaluate it mentally to sanity-check it: are all the spaces covered correctly? Are there any odd overlaps?
There's a pairing with table-based exhaustive testing. Perhaps the cartesian product of all the axes results in too many distinct N-cubes to test individually, but predicates might be written that act as sanity checks, and testing for exhaustive coverage of predicates over the configuration space is possible (e.g. "every box has a disk space allocation", "boxes running service D have at least X disk space", etc.).
Anyhow, it's rattled away in my head for decades and has leaked out into implemented systems a couple of times, but never in a fully formalized or packaged way.
I'm not sure if there are any big uses for cartesian products outside of test variants though. Probably not enough to justify making it a first-class language feature.
The author is aware of the logic programmign paradigm and constraint logic programming and has been "thinking a lot about Prolog" but only mentions imperative and functional in the article title because:
most programmers would just not use Prolog (too exotic) while they may be OK with JavaScript
That just makes me sad. I don't know how else to put it.
Learning new languages used to be fun, I think. Now we gotta mollycoddle programmers into thinking about "new" paradigms, which are really not new at all, by wrapping it all up in javascript otherwise they run away screaming?
Yeah, it's sad. It's the Worse is Better effect combined with momentum of legacy systems. You have to reuse things that have momentum to increase odds of your own thing having momentum. Three examples coming to mind were C++ building on C, successors using C-like syntax, and a number of languages integrating Java or .NET libraries. Those clean-slating solid concepts usually didn't go anywhere.
I admit I've only started programming around '99/00, but I don't remember learning "weird" languages being very popular even then. Mainstream communities mostly had a mix of C/C++/Java/Perl/PHP.
I read the article and the examples reminded me a great deal of R. Granted, I haven't done much in R, just wrote a dashboard in R Shiny that combined IoT data from devices and perform analysis on them with fitting.
- Shows limited knowledge of the author (that imperative and functional are the only paradigms; ever looked at wikipedia page on programming paradigms [1], especially the sidebar on the right?)
- There is no such thing that goes by the name Cartesian Programming (likely the author giving his own name to something known with a different name).
- In the text, author starts discussing the paradigm with a tone that assumes the reader knows what it is.
Sometimes, I wish I could double downvote a comment.
The author doesn't claim those are the one two paradigms.
The author effectively calls out that there is no existing thing that goes by that name - how could he, when in the first paragraph he says he has never heard of it elsewhere.
He succinctly states what it is before going into more detail.
Well the title of the HN post is changed already. Before it is changed on the blog post as well (in which case my comment would look very misplaced), here it is:
"Sure, we have imperative and functional. But what about Cartesian programming?"
> The author doesn't claim those are the one two paradigms.
I never said the author made that claim (I said it shows the state of his knowledge). But for any statement along the lines "Sure we have imperative and functional. But what about X?"
Well what about X? There are plently of X's around. If you look at the right sidebar on the page I linked, the number of X's are probably 30 to 40. Has the author studied and understood those 30 to 40 before asking "what about X"?
What does the reader learn about X if the author doesn't put in the effort to study at least half of the 30 to 40 paradigms and then put his own thoughts in proper context? Well I guess the author might realize what he/she is thinking of is so well-known under another name that his way of presenting the content is simply causing more unnecessary terminology and bloat on the internet.
SETL2[2] and SETLX[3] interpreters are available.
Here is a sample program[4] by Keith S. Thompson. I have removed the comment header for brevity.
[0] http://www.settheory.com[1] http://wiki.c2.com/?SetlLanguage
[2] http://www.setl.org/setl/
[3] https://randoom.org/Software/SetlX
[4] https://raw.githubusercontent.com/Keith-S-Thompson/fizzbuzz-...
Edited to fix formatting of footnotes and add newlines.