Stream has a lot of cool features, of which most have been used in various languages before, but put them together and you get a programming language that's pretty radical. Here are some of its design goals:
- Declarative - you tell the compiler what to do, and the compiler figures out how to do it
- Reactive - your program consists of a set of rules that specify how data should propogate when new input is received
- Actor-oriented - your program consists of a set of functions that asynchronously receive and respond to messages
- Lazy evaluation - Rule evaluation is delayed until the value is needed.
- Inherently concurrent - no need for threading-related confusion
- Stream-oriented - every variable is a stream that contains functions
- Minimal built-in syntax - there are only about 10 built-in operators. Most of the syntax is provided by a library called a dialect, which is itself written in Stream
Note that at the time of this writing (Aug 20, 2014) there is no implementation for Stream. Work is in progress on that front.
<= Console.write('Hello World')
target = source
,target <= source
,source => target
- Streams from the source to the target. Any functions that the source stream contains, both now and in the future, are forwarded into the target stream. Iftarget
is not supplied, the current function's output stream is assumed. Ifsource
is not supplied, the current function's input stream is assumed.target source
,target <- source
,source -> target
- Pipes from the source to the target. Each function in target will be executed for each function in source. For example:(2 +, 5 +, 7 +) <- (10, 20)
evaluates to(12, 15, 17, 22, 25, 27)
. Iftarget
is not supplied, the current function's output stream is assumed. Ifsource
is not supplied, the current function's input stream is assumed.{...}
- Returns a function, and definesin
,out
, andself
as the input stream, output stream, and current function respectively. Also creates a scope, so that any streams created with+
will not be accessible outside. Streams will not be returned implicitly, so{1, 2} <- .
will return an empty stream.[...]
- Returns a function, but does not definein
,out
, orself
. Also does not create a new scope, so created streams will be accessible outside. Streams will be returned implicitly, so[1, 2] <- .
will return a stream of1
and2
.(...)
- Like[...]
, but evaluates immediately. Streams will be returned implicitly, so(1, 2)
will return a stream of1
and2
..prop: value
- The:
returns an inline function that returnsvalue
if.prop
is supplied as input. Inline means that it adds its code to the containing function instead of creating a new function. For example,[.answer: 42].answer
will return42
.'abc'
,"def"
,.ghi
- Returns a function with string-like properties.42
,-3.14
,1e100
- Retrurns a function with number-like properties.+my_stream
- Creates a new, empty stream, scoped inside the closest{...}
.my_stream #
- Returns the length ofmy_stream
as a number. The#
,$
, and^
operators are unique because they do not operate on the contents of a stream, but on the stream itself. As a consequence, they will not write the length to their output stream until no more functions can be written to the input stream.my_stream $
- Returns the last function ofmy_stream
. Like#
, it operates on the stream itself.my_stream ^
- Returns the first function ofmy_stream
. Like#
, it operates on the stream itself.
There are only 2 types exposed in the Stream language: streams and functions. Variables are streams. Streams are un-ordered lists of functions. Funcions, when called, return a stream. It might sound confusing, but here are a few examples:
+my_stream = (2, 3, 5, 7, 11)
Let's break that down:
+my_stream
- Creates a new stream calledmy_stream
. The+
is likevar
in javascript. It sets the scope ofmy_stream
.=
- Streams from the right side to the left side. Whatever the right side evaluates to will be streamed intomy_stream
. We could have used<=
instead of=
. They do exactly the same thing. As you've probably guessed, there is a=>
operator that streams from the left to the right.2
, ...,11
- These literals each return a stream containing a function that contain methods that you'd expect a number to have, like+
,-
,*
, etc.(2, 3, 5, 7, 11)
- Combines each number stream into a single stream.
Let's print each number:
<= Console.write(my_stream)
// or
<= Console.write my_stream
There's more here than may seem at first:
<=
- The only way to use external OS features, like the console, filesystem, or access other processes is to stream out of the top level (not inside a function) of the main file.Console.write
doesn't do anything with the console. It can't. What it does do is return a special stream. When that stream is streamed out of the main file with<=
, the console is updated.Console
- Nothing too interesting here, just accessing the stream calledConsole
, which was defined by the dialect. In this case, theConsole
stream contains one function..write
- A dot followed by an identifier returns a string. So.write
is the same as'write'
. And when I say string, I mean a stream containing a function that has stringy methods, like.substr
,.length
, etc.Console 'write'
- A stream followed by another stream calls each function of the first stream with each function of the second stream as arguments. So that's (the length of stream 1) * (the length of stream 2) function calls. In this case, both theConsole
and the'write'
stream only have one function, soConsole
is only called once, with'write'
as the argument. Guess what's returned? A stream containing one function!(my_stream)
- As stated before, parenthesis create a stream, but sincemy_stream
is already a stream, they aren't necessary. In fact, since everything is a stream, the only time you would have to use parenthesis is when you'd like to group something into one stream, like(1, 2, 3)
.Console.write my_stream
- This will call the result ofConsole.write
5 times, each time with one of the functions inmy_stream
as the argument. Each number will be printed.
Let's find all the primes less than 1000:
// Lazily calculate all natural numbers
+N = 1
N -> {in + 1 => N}
// Calculate primes
+primes = N > 1 < 1000 -> {
<= in % (N > 1 < in) ~ 0 #~0 -> [in]
}
+N = 1
- Creates a new stream.N -> {in + 1 => N}
- ForeachN
asin
, addin + 1
to theN
stream. Each time a number is added, the function is called again, adding another function. If this were not lazily evaluated, it would be an infinite loop.N > 1 < 1000
->
is a global variable declared by the dialect.N >
calls each function (number) inN
with>
as an argument. This returns a stream of greater than functions. Instead of returning a boolean, comparison functions return a stream containing the first operand if true, or an empty stream if false. SoN > 1
will return a stream of eachN
that is greater than1
.< 1000
filters the stream in the same way.in % (N > 1 < in)
- Calculates the remainder ofin
divided by every number less than it.~ 0
- The~
operator compares the value of a number or string, as opposed to the function itself like==
. So~ 0
selects all the remainders equal to zero.#~0
- The#
operator returns the length of the stream. So# ~ 0
returns a stream containing a single0
if there are no divisors, or an empty stream if there is a divisor. I omitted the spaces only because I think it's cleaner that way.-> [in]
-[in]
will be called once if there are no divisors. It returnsin
, which is the number being tested for primality. So if the number is prime, it's argument is returned. If not, an empty stream is returned. The result of each number being tested is combined into theprimes
stream.
The primes
stream is bound to the N
stream. That means if the N
stream didn't generate its own numbers, but used some kind of asynchronous generation, like reading them from the console, everything would still work. Every time you typed in a number, the N
stream would update, and the primes
stream would automatically update if the new number was prime.
Here's some code in python, taken from [wikipedia]: [wikipedia]: http://en.wikipedia.org/wiki/List_comprehension#Python
S = [2 * x for x in range(101) if x ** 2 > 3]
Here's the same thing in Stream:
+S = range[:101] -> {in * in > 3} -> 2 *