
What about cartesian programming? - rumcajz
http://250bpm.com/blog:91
======
tjalfi
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;
    

[0] [http://www.settheory.com](http://www.settheory.com)

[1] [http://wiki.c2.com/?SetlLanguage](http://wiki.c2.com/?SetlLanguage)

[2] [http://www.setl.org/setl/](http://www.setl.org/setl/)

[3] [https://randoom.org/Software/SetlX](https://randoom.org/Software/SetlX)

[4] [https://raw.githubusercontent.com/Keith-S-
Thompson/fizzbuzz-...](https://raw.githubusercontent.com/Keith-S-
Thompson/fizzbuzz-polyglot/master/fizzbuzz.setl)

Edited to fix formatting of footnotes and add newlines.

~~~
Rangi42
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

~~~
odonnellryan
You can concatenate dictionaries in Python:

a = {* * b, * * c}

(Python 3.5!)

Unless this is doing something unintended?

> No consistent map-lookup notation;

You can use .get() in Python on a dictionary :)

------
pharrington
Jacking a comment from tfa:

> 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.

~~~
rumcajz
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.

~~~
Twisell
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.

~~~
rumcajz
Can you share your experience?

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;

~~~
Twisell
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.

    
    
      WITH
      boxes(hostname, os, arch, ram) AS (VALUES
          ('box1', 'linux', 'x86-64', 8),
          ('box2', 'freebsd', 'arm', 16),
          ('box3', 'windows', 'x86-64', 4),
          ('box4', 'illumos', 'sparc', 4)
      ),
      compilers("binary",version,output_option) AS (VALUES
          ('gcc', '4.8.4', '-o'),
          ('clang', '3.4.1', '-o'),
          ('msvc', '15.00.30729.01', '/Fe')
      ),
      tests(name,sources) AS (VALUES
          ('frobnicate' , 'frbonicate.c' ),
          ('loadtest', 'loadtest.c helpers.c'),
          ('end2end', 'end2end.c helpers.c')
      )  
      
      SELECT *, compilers.binary || ' ' || tests.sources || ' ' ||
              compilers.output_option || ' ' || tests.name cmdline
      FROM boxes, compilers, tests
      WHERE NOT(tests.name = 'loadtest' AND boxes.ram < 8)
    

Here is a fiddle to play around...
[http://sqlfiddle.com/#!15/9eecb7db59d16c80417c72d1e1f4fbf1/1...](http://sqlfiddle.com/#!15/9eecb7db59d16c80417c72d1e1f4fbf1/16606/0)

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.

~~~
rumcajz
Thanks! That looks a bit nicer.

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.

------
dragonwriter
"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.

------
seliopou
I don't wanna be "that guy", but this seems to just be the the list monad.

~~~
drostie
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.

------
geocar

        q)datacenter: ([] name: `newyork`london`tokyo; webservers: 7 4 2);
        q)plan::update backends: webservers * 10 from datacenter
        q)webservers::select name, cmdline:`:/usr/bin/webserver, instances:webservers from plan
        q)backends::select name, cmdline:`:/usr/bin/backend, instances:backends from plan
        q)1@.j.j backends;
        [{"name":"newyork","cmdline":":/usr/bin/backend","instances":70},
         {"name":"london","cmdline":":/usr/bin/backend","instances":40},
         {"name":"tokyo","cmdline":":/usr/bin/backend","instances":20}]
    

kdb views track dependencies, so we can get Seattle represented with minimum
effort:

    
    
        q)`datacenter insert (`seattle; 6);
        q)1@.j.j backends;
        [{"name":"newyork","cmdline":":/usr/bin/backend","instances":70},
         {"name":"london","cmdline":":/usr/bin/backend","instances":40},
         {"name":"tokyo","cmdline":":/usr/bin/backend","instances":20},
         {"name":"seattle","cmdline":":/usr/bin/backend","instances":60}]
    

What was "B" supposed to be twice of "A"?

    
    
        q)A:([] x:10?10);
        q)B::update x:x*2 from A
        q)A`x
        8 1 9 5 4 6 6 1 8 5
        q)B`x
        16 2 18 10 8 12 12 2 16 10
        q)update x:10?10 from `A;
        q)A`x
        4 9 2 7 0 1 9 2 1 8
        q)B`x
        8 18 4 14 0 2 18 4 2 16
    

And the syntax is much nicer than JavaScript.

------
gavanwoolery
By the title, I thought it might be referring to something like the esolang
Befunge
([https://en.wikipedia.org/wiki/Befunge](https://en.wikipedia.org/wiki/Befunge)),
which operates on a grid rather than the usual linear sequence of
instructions.

------
uriva
Cute, but isn't this almost like functional programming given a cartesianMap?

e.g.

    
    
        const X = ['a', 'b'];
        const Y = [1, 2];
        const res = cartesianMap(X, Y)((x, y) => [x, y]);
        console.log(res);  // [['a', 1], ['a', 2], ['b', 1], ['b', 2]]

~~~
bbcbasic
That's a testament to Turing completeness.

------
nickpsecurity
It might help to look at the bigger post at Github:

[https://github.com/sustrik/cartesian](https://github.com/sustrik/cartesian)

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.

~~~
barrkel
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.

------
YeGoblynQueenne
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?

I hope that's not true.

~~~
nickpsecurity
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.

------
Boothroid
The concept of dependency is one aspect of ABM:
[https://en.wikipedia.org/wiki/Agent-
based_model](https://en.wikipedia.org/wiki/Agent-based_model)

------
GavinMcG
Is this any different from "reactive" programming, a la Elm?

~~~
zubairq
I agree.. when I hear that this doesn't exist then I am wondering if the
author has heard of Eve or Elm

------
Communitivity
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.

------
zubairq
witheve.com, seems to do cartisian/relational programming Isn't Cartisian
Porgamming essentialy what Eve is?

------
fizixer
\- Click-bait title

\- 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.

All in all, a low effort post.

[1]
[https://en.wikipedia.org/wiki/Programming_paradigm](https://en.wikipedia.org/wiki/Programming_paradigm)

~~~
abtinf
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.

It's as if we read completely different articles.

All in all, a low effort comment.

~~~
fizixer
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.

Just my two cents.

~~~
dragonwriter
Titles aren't the whole of the post; in the body the author enumerates several
other paradigms the proposed paradigm is not.

