• Stars
    star
    103
  • Rank 323,232 (Top 7 %)
  • Language
    CoffeeScript
  • Created about 9 years ago
  • Updated about 9 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Concatenative programming for Javascript

Catenary TravisCI

Catenary is a concatenative programming library for Javascript. Like functional programming, concatenative programming can be hard to get your head around at first, but used appropriately it can make your programs cleaner and more elegant.

Catenary is an attempt to unite the fun and elegance of the concatenative style with the ease and popularity of Javascript. It puts pragmatism over purity, and is designed to interoperate cleanly with regular JS functions so you can mix and match to do whatever suits your code best.

In addition, Catenary is designed to be a utility library with all your old favourites like .extend and .shuffle.

You can install it with npm install catenary

Concatenative programming

Concatenative programming is kind of the bizarro world version of regular imperative or functional programming.

From functional programming you might be used to applying functions like then_do_that(do_this(take_this)), but concatenative programming turns all that on its head! Functions chain (or concatenate) together instead of applying, so you would write take_this do_this then_do_that. Each function receives the arguments from the previous function.

And from imperative programming you might be used to passing variables around, like x = leftFoot(); y = rightFoot(); z = shakeAllAbout(x, y);. But concatenative programming doesn't believe in variables! Instead you would write something like leftFoot rightFoot shakeAllAbout. Each function knows how many arguments it needs and how many it returns, so the variables can be implicit.

The way this is usually implemented is with a stack, which holds those implicit variables. Each function then pulls as many arguments as it likes from the stack and puts as many as it likes back on. Because all the data is implicit, you can think of it less like executing instructions and more like building a pipeline, or making a chain of operations that each connect to the next.

Want an example? Here's an example!

  > cat(5)
  { [Function] _stack: [ 5 ] }
  > cat(5).dup()
  { [Function] _stack: [ 5, 5 ] }
  > cat(5).dup.times()
  { [Function] _stack: [ 25 ] }
  > cat.define('square', cat.dup.times)
  { [Function] _stack: [] }
  > cat.square(2)
  { [Function] _stack: [ 4 ] }
  > cat.square.square(2)
  { [Function] _stack: [ 16 ] }

Or how about our old friend fibonacci?

// [a, b] --swap--> [b, a] --over--> [b, a, b], --plus--> [b, a+b]
cat.define('nextfib', cat.swap.over.plus)

cat(0, 1, 10, cat.dup.print.nextfib).repeat()
// 1
// 1
// 2
// 3
// 5
// 8
// 13
// 21
// 34
// 55

Want a more complicated example? Okay!

// Add 'n bottle/bottles' to the stack
cat.define('bottles',
  cat(1).dupd.equal
    .cat(' bottle', ' bottles').q
    .dupd.plus
)

cat.define('beer',
  cat
    .bottles.cat(' of beer on the wall,').plus.print
    .bottles.cat(' of beer!').plus.print
    .cat('Take one down, pass it around,').print
    .dec
    .bottles.cat(' of beer on the wall.').plus.print
    .cat('').print
)

cat(99, cat.beer).loop()

//99 bottles of beer on the wall,
//99 bottles of beer!
//Take one down, pass it around,
//98 bottles of beer on the wall.
// [etc...]

How Catenary works

You can create stacks of items with cat(...):

> cat(1, 2, 3)
{ [Function] _stack: [ 1, 2, 3 ] }

And concatenate more values to the stack with cat(...).cat(...):

> cat(1, 2, 3).cat(4, 5, 6)
{ [Function] _stack: [ 1, 2, 3, 4, 5, 6 ] }

When you access a property on a cat, that adds a function to the stack:

> cat(1, 2, 3).plus
{ [Function] _stack: [ 1, 2, 3, [Function] ] }

Chained properties just add more functions:

> cat(1, 2, 3).plus.plus
{ [Function] _stack: [ 1, 2, [Function], [Function] ] }

Then when you execute the cat, all functions are called in order from left to right:

> cat(1, 2, 3).plus.plus()
{ [Function] _stack: [ 5 ] }

That means you can actually just add plain functions to the stack and they will be executed:

> cat(1, 2, 3, function((a, b) { return a * b })()
{ [Function] _stack: [ 1, 6 ] }

The arity of the function is inspected via its .length property to figure out how many items to take off the stack. Any value it returns other than undefined will be put back on the stack:

> cat(1, 2, 3, function(a, b, c) { return 7 })()
{ [Function] _stack: [ 7 ] }

And you can chain multiple plain functions together too:

> cat(1, function(x) { return x + 1 }, function(x) { return x * 5 })()
{ [Function] _stack: [ 10 ] }

You can also pass arguments when you execute the cat - they are added to the start of the stack:

> cat(3).plus(5)
{ [Function] _stack: [ 8 ] }
> cat().plus(3, 5)
{ [Function] _stack: [ 8 ] }

Executing a cat with no functions in it will just return a copy:

> cat(1, 2, 3)()
{ [Function] _stack: [ 1, 2, 3 ] }
> cat(1, 2, 3)()()()()()()
{ [Function] _stack: [ 1, 2, 3 ] }

So actually the main export is just a cat with an empty stack, which is fine because cats are immutable:

> cat = require 'catenary'
{ [Function] _stack: [] }

Which means you can also write in regular function style, like this:

> cat.plus(2, 3)
{ [Function] _stack: [ 5 ] }
> cat.times.times(3, 4, 5)
{ [Function] _stack: [ 60 ] }

To get values out, you can use $ ($ is the show-me-the-money operator)

> cat(1, 2).$
2
cat(1, 2).stack().$
[1, 2]

But I am generally of the opinion that it's better to just pass whatever function you were going to return to into Catenary instead.

> console.log cat(1, 2).plus().$ //boo
3

> cat.define('print', function(x) { console.log(x) })
{ [Function] _stack: [] }
> cat(1, 2).plus.print() //yay!
3

Higher order programming

To do loops and if statements and other fun things, we need to be able to use functions without executing them, what some concatenative languages call quotation. We do this by wrapping them in a cat(). A cat inside another cat won't be executed, and can be passed around like a value:

> cat(1, function(x) { return x + 1 })()
{ [Function] _stack: [ 2 ] }
> cat(1, cat(function(x) { return x + 1 }))()
{ [Function] _stack: [ 1, { [Function] _stack: [Object] } ] }

We can execute a cat from inside another cat by using .exec:

> cat(1, cat(function(x) { return x + 1 })).exec()
{ [Function] _stack: [ 2 ] }

Or do other things with it...

> cat(1, 500, cat(function(x) { return x + 1 })).repeat()
{ [Function] _stack: [ 501 ] }

You can also include values inside the cat:

> cat(1, cat(5, function(x, y) { return x + y })).exec()
{ [Function] _stack: [ 6 ] }

Or to put it another way:

> cat(1, cat(5).plus).exec()
{ [Function] _stack: [ 6 ] }

You can also use .cat to create higher-order cats:

> cat(1).cat.plus(5)
{ [Function] _stack: [ 1, { [Function] _stack: [Object] } ] }
> cat(1).cat.plus(5).exec()
{ [Function] _stack: [ 6 ] }

Which leads to a nifty self-contained imperative style, if you're into that kind of thing:

> cat(1).
... cat.plus(2).exec.
... cat.minus(5).exec.
... cat.times(7).exec()
{ [Function] _stack: [ -14 ] }

Defining, importing and returning

You can define your own words:

> cat.define('add2', function(x) { return x + 2 })
{ [Function] _stack: [] }
> cat.add2(3)
{ [Function] _stack: [ 5 ] }

And they don't have to be functions, they can be any value:

> cat.define('hello', 'hello!')
{ [Function] _stack: [] }
> cat.hello.hello.hello
{ [Function] _stack: [ 'hello!', 'hello!', 'hello!' ] }

You can also define namespaced words by using an object:

> cat.define('letters', {a: 'apple', b: 'banana'})
{ [Function] _stack: [] }
> cat.letters.a.letters.b
{ [Function] _stack: [ 'apple', 'banana' ] }

Or import a whole object at once:

> cat.import({a: 'apple', b: 'banana'})
{ [Function] _stack: [] }
> cat.a.b.a.b
{ [Function] _stack: [ 'apple', 'banana', 'apple', 'banana' ] }

If you're concerned about collisions, you can use an instance:

> cat2 = cat.instance
{ [Function] _proto: {}, _stack: [] }
> cat2.define('hello', 'world')
{ [Function] _proto: {}, _stack: [] }
> cat2.hello
{ [Function] _proto: {}, _stack: [ 'world' ] }
> cat.hello
undefined

Standard library

Catenary can do a lot of stuff that is, for now, tragically undocumented. The aim is to eventually be a fully-featured utility library in the spirit of Underscore, lodash or Ramda. You can have a browse through the lib/ and test/lib folders to see what is currently supported.

Limitations

Right now Catenary is fairly early stage. I have not done any performance or browser testing of any kind, so it is almost certainly unsuitable for your billion dollar web app. But there are tests for all the major functionality and I am committed to being one with the semver, so it should be safe enough to use.

More Repositories

1

caniuse-cmd

Caniuse command line tool
CoffeeScript
1,592
star
2

chip

CoffeeScript
69
star
3

tabletone

Live-looping in Javascript!
JavaScript
40
star
4

scrawl

An iterative doodling adventure!
HTML
35
star
5

pere

Record and playback DOM edits
HTML
29
star
6

chef-node

Chef API client for nodejs
JavaScript
26
star
7

automata-by-example

Automata by Example
CSS
18
star
8

devrouter

Easy router for developing http services
JavaScript
16
star
9

unql-node

SQL-like frontend for CouchDB
CoffeeScript
15
star
10

markov-technology

Markov Technology
JavaScript
14
star
11

soundoflife

HTML
8
star
12

redux-actionemitter

Redux actions as event emitters
JavaScript
8
star
13

areyousure

Chrome extension to make you think before visiting distracting sites
CSS
7
star
14

markov-contact

A dynamic audio journey
JavaScript
6
star
15

pairjs

Instant pair programming for the interweb
JavaScript
6
star
16

E-X-T-E-N-S-I-O-N

Adds a special button after every mention of the word "inception" on a webpage.
JavaScript
5
star
17

whenthefuckissydjs

HTML
5
star
18

infinite-gest

JavaScript
4
star
19

mpd-rest

RESTful MPD interface
CoffeeScript
4
star
20

MPDTie

Ruby-based Bowtie MPD remote controller
Ruby
4
star
21

starboard

Shooting Stars keyboard
HTML
3
star
22

automaintainer

3
star
23

python-cec-daemon

Python CEC daemon for uinput
Python
3
star
24

pass-scene

HTML
3
star
25

medb-github

MeDB plugin for GitHub
CoffeeScript
3
star
26

medb

Pluggable InfluxDB loader for personal data analytics
CoffeeScript
3
star
27

audiomata

CoffeeScript
2
star
28

promises-talk

A talk about promises
CoffeeScript
2
star
29

kata

Practice makes perfect
CSS
2
star
30

davescript

2
star
31

mpdiment

NodeJS MPD web interface
CoffeeScript
2
star
32

docs-in-a-box

CoffeeScript
2
star
33

medb-posts

CoffeeScript
2
star
34

fetch-progress-lol

How to make a progress bar for the Fetch API
HTML
2
star
35

evtail

Automatically exported from code.google.com/p/evtail
C
2
star
36

monotonic

CoffeeScript
2
star
37

shakeoff

Silly web game based on Socket.IO and the HTML5 history API
JavaScript
2
star
38

mosquito

JavaScript
2
star
39

flourish

Epicyclic music visualiser
JavaScript
2
star
40

relative-word-cloud

JavaScript
2
star
41

metaknight

Meta-trickery with parsers!
CoffeeScript
2
star
42

placehodor

Hodor
CoffeeScript
2
star
43

multiply-average

CoffeeScript
1
star
44

hkcontrol

HK3350 remote control audio files
1
star
45

prototypes

CoffeeScript
1
star
46

hiddenreftest

1
star
47

vroom

JavaScript
1
star
48

hof

CoffeeScript
1
star
49

take-on.me

Take me on!
HTML
1
star
50

contextable

JavaScript
1
star
51

gish

Git-style file and directory hashing
JavaScript
1
star
52

hackcache

1
star
53

demoserver

Git-powered micro-PaaS to host little web demos
1
star
54

livewall

Shell
1
star
55

singing-bikes

Singing bikes!
JavaScript
1
star
56

hold-to-confirm

HTML
1
star
57

samgentle.com

HTML
1
star
58

lagproxy

Laggy proxy for webserver & web service testing
1
star
59

robot-party

Robots run robots
CSS
1
star
60

ideaglobe

CoffeeScript
1
star
61

pullitzer

Dead-simple GitHub repo updates
CoffeeScript
1
star
62

expanding-explanations

HTML
1
star
63

cardiograph

Live plotting ECG
Rust
1
star
64

schema-conformer

Fix your JSON for uploading into BigQuery
CoffeeScript
1
star
65

worm-farmer

1
star
66

rlvsinternet

CoffeeScript
1
star
67

aquire

Asynchronous require with promises
CoffeeScript
1
star
68

sak

sak
1
star
69

dotextensions

node require.extensions that works properly
CoffeeScript
1
star
70

etaoin-shrdlu

Statistically meaningful gibberish
JavaScript
1
star
71

singing-keyboard

Singing keyboard!
JavaScript
1
star
72

q3ds-info

CoffeeScript
1
star
73

goose-and-gander-license

1
star
74

whipcalc

PureScript
1
star
75

promserver

Promise-based webserver
JavaScript
1
star
76

sliding-blocks

A little puzzle game
HTML
1
star
77

Unrandom

A repeatable random number generator for javascript testing
JavaScript
1
star
78

scouchule

Rust
1
star