RapydScript

For those who prefer Python over JavaScript and think CoffeeScript feels more like Perl

Alex Tsepkov

Background

  • Started using Pyjamas because I was reluctant to learn JS
  • Wrote an image-recognition drawing app (20k SLOCs)
  • Realized Pyjamas/GWT doesn't scale and JS is not so bad
  • Still had 20k lines of Python code to port
  • Wrote a porting tool based on PyvaScript
  • Realized that I liked this pseudo-language more than JS
  • Added more features to it, others started following it
  • Rewrote the core and turned it into a new language

What's wrong with JavaScript?

  • It prefers global scope
  • Its class implementation is counter-intuitive (better in ES6)
  • Everything is an object (typeof hell)
  • Funky equality ("0" == false, arrays never equal)
  • Easy to shoot yourself in the foot
  • ES6 added verbosity of Java and inconsistency of Perl to the language
  • Moving target

What's right with JavaScript?

  • Everything is an object (anonymous functions, callbacks)
  • Can run on the client and on the server
  • Today, the phrase "batteries included" is more accurate about JavaScript than Python
  • Native to every browser, truly cross-platform
  • Easy to tinker with after deployment

What can I do about it?

  • Suck it up and learn JavaScript
  • Suck it up and learn Ruby CoffeeScript
  • Suck it up and learn C# TypeScript
  • Learn RapydScript

Why RapydScript?

  • Combines clarity of Python with compatibility of JS
  • Favors Pythonic design patterns
  • Orders of magnitude faster than Python-in-a-browser frameworks (Skulpt, Brython, etc.)
  • Prevents bad JS habits and bugs ('var', 'new', 'this', etc.)
  • Minimizes context switch for Python developers
  • Full compatibility with all JS features and environments
  • Borrows safety features from Python and TypeScript

JavaScript vs RapydScript (Sanity)

1 == '1'            // true
[1, 2] == [1, 2]    // false
{} + 'foo'          // NaN
'foo' in ['foo']    // false

typeof []           // object
typeof {}           // object
typeof new Date()   // object

function test() {
    foo = 1;
}
test();
console.log(foo);   // 1

class Foo {
    bar() {
        setTimeout(function() {
            console.log(this);
            // outputs Timeout
        }, 1000);
    }
}
1 == '1'            # False
[1, 2] == [1, 2]    # True
{} + 'foo'          # TypeError
'foo' in ['foo']    # True

type([])             # Array
type({})             # Object
type(Date())         # Date

def test():
    foo = 1
test()
print(foo)        # ReferenceError


class Foo:
    def bar(self):
        setTimeout(def():
            console.log(self)
            # outputs Foo
        , 1000)

RapydScript Example

class Rectangle:
    def __init__(self, w=1, h=1):
        self.width = w
        self.height = h
        
    def getArea(self):
        return self.width*self.height

    @staticmethod
    def isRectangle(object):
        return isinstance(object, this)
    
a = Rectangle(5, 10)
a.getArea() == Rectangle.getArea(a)
print(Rectangle.isRectangle(a))

Compiled JavaScript

"use strict";
... bootstrap logic omitted ...

(function(){
    var __name__ = "__main__";
    var ՐՏ_1, ՐՏ_2, ՐՏ_3, a;
    var Rectangle = (ՐՏ_1 = function Rectangle() {
        Rectangle.prototype.__init__.apply(this, arguments);
    }, Object.defineProperties(ՐՏ_1.prototype, {
        __init__: {
            enumerable: true, 
            writable: true, 
            value: function __init__(w, h){
                var self = this;
                w = w === void 0 ? 1 : w;
                h = h === void 0 ? 1 : h;
                self.width = w;
                self.height = h;
            }
        },
        getArea: {
            enumerable: true, 
            writable: true, 
            value: function getArea(){
                var self = this;
                return self.width * self.height;
            }
        }
    }), Object.defineProperties(ՐՏ_1, {
        isRectangle: {
            enumerable: true, 
            writable: true, 
            value: function isRectangle(object){
                return object instanceof this;
            }
        }
    }), ՐՏ_1);
    a = new Rectangle(5, 10);
    ((ՐՏ_2 = a.getArea()) === (ՐՏ_3 = Rectangle.prototype.getArea.call(a)) || typeof ՐՏ_2 === "object" && ՐՏ_eq(ՐՏ_2, ՐՏ_3));
    ՐՏ_print(Rectangle.isRectangle(a));
})();

Example 2

class ColorSwatch:
    """
    Color selection and preview widget
    """
    def __init__(self):
        fg = $('<div></div>')\
            .width(36).height(36)\
            .css({'position': 'absolute', 'border': '1px solid black'})
        fg.change = def(color):
            $(self).css('background', color)
        bg = fg.clone()

Compiled JavaScript

"use strict";
function ՐՏ_extends(child, parent) {
    child.prototype = Object.create(parent.prototype);
    child.prototype.__base__ = parent;
    child.prototype.constructor = child;
}
function ՐՏ_Iterable(iterable) {
    var tmp;
    if (iterable.constructor === [].constructor || iterable.constructor === "".constructor || (tmp = Array.prototype.slice.call(iterable)).length) {
        return tmp || iterable;
    }
    return Object.keys(iterable);
}

(function(){
    var __name__ = "__main__";
    var ՐՏ_1;
    var ColorSwatch = (ՐՏ_1 = function ColorSwatch() {
        ColorSwatch.prototype.__init__.apply(this, arguments);
    }, Object.defineProperties(ՐՏ_1.prototype, {
        __doc__: {
            enumerable: true, 
            writable: true, 
            value: "Color selection and preview widget"        },
        __init__: {
            enumerable: true, 
            writable: true, 
            value: function __init__(){
                var self = this;
                var fg, bg;
                fg = $("<div></div>").width(36).height(36).css({
                    "position": "absolute",
                    "border": "1px solid black"
                });
                fg.change = function(color) {
                    $(self).css("background", color);
                };
                bg = fg.clone();
            }
        }
    }), ՐՏ_1);
})();

Feature Set

  • Inverted variable scope
  • Classes and classical inheritance
  • Modules, namespaces and imports
  • Python standard library (makes porting/reuse easier)
  • Code that makes errors more obvious
  • Deep equality without performance overhead
  • Safety via type inference: [] + 4 = TypeError
  • Pythonic syntax sugar
  • decorators
  • *args, **kwargs
  • optional arguments
  • list/dict comprehensions

RapydScript in the Wild

  • GlowScript
  • NodeBox
  • Physer
  • salvatore.diodev.fr/RapydScript
  • rapydscript.com examples
  • rapydscript.com/try-it
  • tile.zone

Useful Tools

github.com/atsepkov/vim-rapydscript

github.com/tgienger/language-rapydscript

github.com/charleslaw/RapydScript-Decompiler

github.com/loolmeh/grunt-rapydscript

github.com/vindarel/gulp-rapyd

bitbucket.org/railcar88/flask-rapydscript

github.com/traverseda/django-pipeline-rapydscript

github.com/loolmeh/generator-rui-angular

github.com/ivanflorentin/RapydFlux

github.com/icarito/rapydscriptify

github.com/jfroco/Physer

github.com/tgienger/Atom-rapydscript-auto-compile

Community

rapydscript.com

github.com/atsepkov/RapydScript

groups.google.com/forum/#!forum/rapydscript

If you want to help me out with this, reach out:

atsepkov@gmail.com

@atsepkov

github.com/atsepkov

RapydScript (Python Talk)

By Alexander Tsepkov

RapydScript (Python Talk)

For those who prefer Python over JavaScript and think CoffeeScript feels more like Perl

  • 1,556