Last active
August 29, 2015 14:15
-
-
Save Morgul/28ee0b0925c63c28c9c5 to your computer and use it in GitHub Desktop.
My Perfect Programming Language
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//---------------------------------------------------------------------------------------------------------------------- | |
// General | |
//---------------------------------------------------------------------------------------------------------------------- | |
// For the most part, it will look like javascript | |
var foo = "bar"; | |
var bar = "foo"; | |
if(foo == "baz") | |
{ | |
something.doStuff(foo); | |
} | |
// Anonymous functions even work | |
function(thing1, thing2) | |
{ | |
return thing1.doStuff(thing2); | |
} | |
// Scoping works exactly like javascript | |
function(thing1) | |
{ | |
var foo = "3"; | |
// This always refers to the current execution context; meaning the function object in this case | |
this.bar = "3"; | |
function inner(thing2) | |
{ | |
foo = 4; | |
print("scope:", thing1, thing2, foo); // foo is equal to 4 | |
print(this.bar) // 'undefined', because now the execution context is the inner function | |
} | |
print("foo:", foo) // foo is equal to "3"; | |
return inner(thing1 + 2); | |
print("foo:", foo) // foo is equal to 4; | |
} | |
//---------------------------------------------------------------------------------------------------------------------- | |
// Atoms | |
//---------------------------------------------------------------------------------------------------------------------- | |
var foo = `bar`; | |
var baz = `bar`; | |
// An atom always equals itself, but only itself | |
foo == baz; // true | |
foo === baz; // true | |
foo == `bar`; // true | |
foo === `bar`; // true | |
foo == 'bar'; // false | |
foo == atom('bar'); // true | |
//---------------------------------------------------------------------------------------------------------------------- | |
// Literals | |
//---------------------------------------------------------------------------------------------------------------------- | |
// Supports several literal types | |
var someString = "This is a string"; | |
var someInt = 3; | |
var someFloat = 3.4; | |
var someList = ['This is also a string, in a list.']; | |
var someDict = { foo: 'This is in a dictionary' }; | |
var someTuple = { "Don't confuse a tuple", "for a dict", 4 }; | |
//---------------------------------------------------------------------------------------------------------------------- | |
// Types | |
//---------------------------------------------------------------------------------------------------------------------- | |
// Supports (optional) types, as it's strongly typed; but uses duck typing like python | |
int foo = 3; | |
string bar = 4; // throws a TypeError | |
// Functions can be typed, too | |
void function doStuff(int numTimes, string msg) | |
{ | |
while(numTimes) | |
{ | |
print(msg); | |
numTimes--; | |
} | |
} | |
//---------------------------------------------------------------------------------------------------------------------- | |
// Pattern Matching | |
//---------------------------------------------------------------------------------------------------------------------- | |
// Pattern matching happens when two function with the same name have different signatures. It is like an extension to | |
// more traditional function overloading. Some of the things it can do that function overloading can't is supporting | |
// literals, or pulling out parts of a structure. (Modeled off Erlang) | |
// If stuff is a string, this function is called | |
function doStuff(string stuff) | |
{ | |
doStuff(int(stuff)); | |
} | |
// If stuff is an int, this is called | |
function doStuff(int stuff) | |
{ | |
print('moar stuff!', stuff + 1); | |
} | |
// if stuff is anything else, this is called | |
function doStuff(stuff) | |
{ | |
print('wrong stuff!'); | |
} | |
// Match a 2 length tuple, and pull the components out into vars | |
function doMoreStuff({ thing1, thing2 }) | |
{ | |
print("thing1:", thing1); | |
print("thing2:", thing2); | |
} | |
// Same as above, but with type checking | |
function doMoreStuff({ int thing1, list thing2 }) | |
{ | |
print("thing1:", thing1); | |
print("thing2:", thing2); | |
} | |
// This matches an empty tuple | |
function doMoreStuff({}) | |
{ | |
print("nothing to do"); | |
} | |
//---------------------------------------------------------------------------------------------------------------------- | |
// Classes | |
//---------------------------------------------------------------------------------------------------------------------- | |
// Classes are first class citizens, however, they should be considered optional. As much as I love object oriented | |
// programming, there are problems that can be more easily solved by a functional approach. This language should | |
// support both. | |
// classes work mostly like es6 classes, combined with c# and python | |
class SomeClass extends BaseClass | |
{ | |
// Fields are supported | |
foo = "bar"; | |
// Private fields are a thing | |
private _bar; | |
// As are properties | |
bar { | |
get() | |
{ | |
return this._bar; | |
} | |
set(val) | |
{ | |
this._bar = val; | |
} | |
} | |
// This is the constructor function | |
constructor(args) | |
{ | |
this.args = args; | |
// The `base` variable is available in any class member function; it referes to the base class. | |
// In this example, we're calling our base class's constructor | |
base.constructor(args); | |
} | |
// Member function | |
doStuff(int numTimes) | |
{ | |
while(numTimes) | |
{ | |
// We can use 'this' to refer to the class instance. | |
print("Doing stuff:", this.args); | |
numTimes--; | |
} | |
} | |
// Static functions do _not_ have access to the class instance; and | |
// there is only one copy of them in memory, ever. They can be called | |
// directly on the class, ex: SomeClass.doStaticStuff(); | |
static doStaticStuff() | |
{ | |
print("Static stuff!"); | |
} | |
// We support python style decorators | |
@myDecorator('with args!', 3, this.bar) | |
@simpleDecorator | |
final() | |
{ | |
print("This is the end!"); | |
} | |
} | |
//---------------------------------------------------------------------------------------------------------------------- | |
// Actors | |
//---------------------------------------------------------------------------------------------------------------------- | |
// An actor is a light-weight process that executes concurrently to other processes. Actors receive messages sent to them, | |
// and do work based on those messages. You create one by extending the Actor class, and overriding it's receive function. | |
// For thread-safety reasons, an actor only has access to itself, or anything sent via a message. This is one example where | |
// scope tricks will not work. (ex: you can't declare a variable outside an actor, and have it accesible inside the actor.) | |
// Need to look into zero-copy message passing; if possible we should use that, otherwise, deal with copies. | |
// Actors take the place of event emitters in Node.js; since they support message passing, _and_ can be scheduled on | |
// different cores, they are the building blocks of loosely coupled concurrency. | |
class MyProcess extends Actor | |
{ | |
// You can pattern match the receive function obviously | |
receive({ sender, message }) | |
{ | |
// The send function is how you send messages to actors | |
send(sender, "I got your message!"); | |
} | |
receive(`stop`) | |
{ | |
// The exit function stops the process, and return with the specified reason. | |
exit(`normal`); | |
} | |
// Since actors are class instances, they can have other member functions, too. | |
doStuff(thingToDo) | |
{ | |
print("Doing stuff:", thingsToDo); | |
} | |
} | |
// You can create a new instance of an actor in two ways. First, manually: | |
var pid = new MyProcess(); | |
pid.start(); | |
// Or, you can use the spawn function | |
var pid = spawn(MyProcess, args); | |
// You can also stop a pid manally (with a reason) | |
pid.stop(`normal`); | |
// Or, you can use the kill function | |
kill(pid, `normal`); | |
// Also, it would be easy to build something like Erlang's gen_server: | |
class GenServer extends Actor | |
{ | |
receive({`call`, from, request}) | |
{ | |
this.handle_call(from, request); | |
} | |
receive({`cast`, request}) | |
{ | |
this.handle_cast(request); | |
} | |
receive(else) | |
{ | |
this.handle_info(else); | |
} | |
handle_call(from, request) | |
{ | |
throw new Error("Unhandled call.", from, request); | |
} | |
handle_cast(request) | |
{ | |
throw new Error("Unhandled cast.", request); | |
} | |
handle_info(info) | |
{ | |
throw new Error("Unhandled info.", info); | |
} | |
static call(Actor pid, request) | |
{ | |
send(pid, {`cast`, pid, request}); | |
} | |
static cast(Actor pid, request) | |
{ | |
send(pid, { `cast`, request }); | |
} | |
static info(Actor pid, request) | |
{ | |
send(pid, request); | |
} | |
} | |
// While that's a simple example, it should illustrate how such a thing could be done. | |
//---------------------------------------------------------------------------------------------------------------------- | |
// Modules | |
//---------------------------------------------------------------------------------------------------------------------- | |
// A module is an importable chunk of code, which can expose function, classes, or objects. The modules are es6 moudules. | |
import "lodash"; | |
import { GenServer, GenFSM } from "servers"; | |
import * as foo from "foo" | |
import "/foo/bar/baz"; | |
export function doStuff(){ print("stuff!"); } | |
// See here for more: http://www.2ality.com/2014/09/es6-modules-final.html | |
//---------------------------------------------------------------------------------------------------------------------- | |
// Other things I'd like to see | |
//---------------------------------------------------------------------------------------------------------------------- | |
// Some of the other things I'd like to see would be: | |
// * List comprehensions (modeled off Python) | |
// * String manipulations (modeled off Python) | |
// * Additional types: sets, deques, orderedDicts, arrays (aka typed lists) | |
// * First-class Promise support (maybe make Promise an actor?) | |
// * Node-like threaded event loop; maybe use libuv? | |
// * Erlang-like lightweight process scheduling | |
// * Erlang/node-like REPL console, with attach/detatch support | |
// * Erlang-like supervisor/monitor |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment