mochi 0.2.7
Dynamically typed functional programming language
License: MIT License

Mochi
=====

Mochi is a dynamically typed programming language for functional
programming and actor-style programming.

Its interpreter is written in Python3. The interpreter translates a
program written in Mochi to Python3's AST / bytecode.

Features
--------

-  Python-like syntax
-  Tail recursion optimization (self tail recursion only), and no loop
   syntax
-  Re-assignments are not allowed in function definition.
-  Basic collection type is a persistent data structure. (using
   Pyrsistent)
-  Pattern matching / Data types, like algebraic data types
-  Pipeline operator
-  Syntax sugar of anonymous function definition
-  Actor, like the actor of Erlang (using Eventlet)
-  Macro, like the traditional macro of Lisp
-  Builtin functions includes functions exported by itertools module,
   recipes, functools module and operator module

Examples
--------

Factorial
~~~~~~~~~

.. code:: python

    def factorial(n, m):
        if n == 1:
            m
        else:
            factorial(n - 1, n * m)


    factorial(10000, 1)
    # => 28462596809170545189064132121198688...

    # Or

    def factorial:
        n: factorial(n, 1)
        0, acc: acc
        n, acc: factorial(n - 1, acc * n)
        
    factorial(10000)
    # => 28462596809170545189064132121198688...

FizzBuzz
~~~~~~~~

.. code:: python

    def fizzbuzz(n):
        match [n % 3, n % 5]:
            [0, 0]: "fizzbuzz"
            [0, _]: "fizz"
            [_, 0]: "buzz"
            _: n

    range(1, 31)
    |> map(fizzbuzz)
    |> pvector()
    |> print()

Actor
~~~~~

.. code:: python

    def show():
        receive:
            message:
                print(message)
                show()

    actor = spawn(show)

    send('foo', actor)
    actor ! 'bar' # send('bar', actor)

    sleep(1)
    # -> foo
    # -> bar


    'foo' !> spawn(show)

    sleep(1)
    # -> foo

    ['foo', 'bar'] !&> spawn(show)
    # The meaning of the above is the same as the meaning of the following.
    # spawn(show) ! 'foo'
    # spawn(show) ! 'bar'

    sleep(1)
    # -> foo
    # -> bar

    def show_loop():
        receive:
            [tag, value]:
                print(tag, value)
                show_loop()

    actor2 = spawn(show_loop)

    actor2 ! ["bar", 2000]
    sleep(1)
    # -> bar 2000

    ['foo', 1000] !> spawn(show_loop)
    sleep(1)
    # -> foo 1000

    [['foo', 1000],['bar', 2000]] !&> spawn(show_loop)
    sleep(1)
    # -> foo 1000
    # -> bar 2000


Distributed Computing
~~~~~~~~~~~~~~~~~~~~~
.. code:: python

    # comsumer.mochi
    from mochi.actor.mailbox import KombuMailbox, ZmqInbox, SQSMailbox

    def consumer():
        receive:
            'exit':
                print('exit!')
            other:
                print(other)
                consumer()

    kombu_mailbox = KombuMailbox('sqs://<access_key_id>@<secret_access_key>:80//',
                                 '<queue_name>',
                                 dict(region='<region>'))
    spawn_with_mailbox(consumer, kombu_mailbox)

    zmq_mailbox = ZmqInbox('tcp://*:9999')
    spawn_with_mailbox(consumer, zmq_mailbox)

    sqs_mailbox = SQSMailbox('<queue_name>')
    spawn_with_mailbox(consumer, sqs_mailbox)

    wait_all()


.. code:: python

    # producer.mochi
    from mochi.actor.mailbox import KombuMailbox, ZmqOutbox, SQSMailbox

    kombu_mailbox = KombuMailbox('sqs://<access_key_id>@<secret_access_key>:80//',
                                 '<queue_name>',
                                 dict(region='<region>'))
    kombu_mailbox ! [1, 2, 3]
    kombu_mailbox ! 'exit'

    zmq_mailbox = ZmqOutbox('tcp://localhost:9999')
    zmq_mailbox ! [4, 5, 6]
    zmq_mailbox ! 'exit'

    sqs_mailbox = SQSMailbox('<queue_name>')
    sqs_mailbox ! [7, 8, 9]
    sqs_mailbox ! 'exit'


Flask
~~~~~

.. code:: python

    from flask import Flask

    app = Flask('demo')

    @app.route('/')
    def hello():
        'Hello World!'

    app.run()

RxPY
~~~~

.. code:: python

    # usage: mochi -no-mp timer.mochi
    # original:
    # https://github.com/ReactiveX/RxPY/blob/master/examples/parallel/timer.py

    import rx
    import concurrent.futures
    import time

    seconds = [5, 1, 2, 4, 3]


    def sleep(t):
        time.sleep(t)
        return t


    def output(result):
        print('%d seconds' % result)


    with concurrent.futures.ProcessPoolExecutor(5) as executor:
        rx.Observable.from_(seconds)
                     .flat_map((s) -> executor.submit(sleep, s))
                     .subscribe(output)

    # 1 seconds
    # 2 seconds
    # 3 seconds
    # 4 seconds
    # 5 seconds

aif (Anaphoric macro)
~~~~~~~~~~~~~~~~~~~~~

.. code:: python

    macro aif(test, true_expr, false_expr):
        quasi_quote:
            it = unquote(test)
            if it:
                unquote(true_expr)
            else:
                unquote(false_expr)

    aif([], first(it), "empty")
    # => "empty"
    aif([10, 20], first(it), "empty")
    # => 10

Requirements
------------

-  CPython >= 3.2 or PyPy >= 3.2.1
-  rply >= 0.7.2
-  pyrsistent >= 0.10.1
-  pathlib >= 1.0.1
-  eventlet >= 0.17.1
-  msgpack-python >= 0.4.6
-  typeannotations >= 0.1.0

Installation
------------

.. code:: sh

    $ pip3 install mochi
    $ pip3 install flask Flask-RESTful Pillow RxPY  # to run the examples


Optional Installation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: sh

    $ pip3 install flask Flask-RESTful Pillow RxPY  # to run the examples
    $ pip3 install pyzmq # to use ZmqInbox and ZmqOutbox
    $ pip3 install kombu # to use KombuMailbox
    $ pip3 install boto # to use SQS as transport of KombuMailbox
    $ pip3 install boto3 # to use SQSMailbox

Th error of the following may occur when you run Mochi on PyPy.

..

    ImportError: Importing zmq.backend.cffi failed with version mismatch, 0.8.2 != 0.9.2

In this case, please change the version of cffi to 0.8.2 using pip on PyPy.

.. code:: sh

    $ pip3 uninstall cffi
    $ pip3 install cffi==0.8.2

Usage
-----

REPL
~~~~

.. code:: sh

    $ mochi
    >>>

loading and running a file
~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: sh

    $ cat kinako.mochi
    print('kinako')
    $ mochi kinako.mochi
    kinako
    $ mochi -no-mp kinako.mochi  # not apply eventlet's monkey patching
    kinako

byte compilation
~~~~~~~~~~~~~~~~

.. code:: sh

    $ mochi -c kinako.mochi > kinako.mochic

running a byte-compiled file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: sh

    $ mochi -e kinako.mochic
    kinako
    $ mochi -e -no-mp kinako.mochic  # not apply eventlet's monkey patching
    kinako

generating .pyc
~~~~~~~~~~~~~~~

.. code:: sh

    $ ls
    kagami.mochi
    $ cat kagami.mochi
    print('kagami')
    $ mochi
    >>> import kagami
    kagami
    >>> exit()
    $ ls
    kagami.mochi kagami.pyc
    $ python3 kagami.pyc
    kagami

Or

.. code:: sh

    $ mochi -pyc kagami.mochi > kagami.pyc
    $ python3 kagami.pyc
    kagami
    $ mochi -pyc -no-mp kagami.mochi > kagami.pyc  # not apply eventlet's monkey patching
    $ python3 kagami.pyc
    kagami

Examples for each feature
-------------------------

Persistent data structures
~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: python

    [1, 2, 3]
    # => pvector([1, 2, 3])

    v(1, 2, 3)
    # => pvector([1, 2, 3])

    vec = [1, 2, 3]
    vec2 = vec.set(0, 8)
    # => pvector([8, 2, 3]
    vec
    # => pvector([1, 2, 3])
    [x, y, z] = vec
    x # => 1
    y # => 2
    z # => 3

    get(vec, 0) # => 1
    get(vec, 0, 2) # => [1, 2]

    vec[0] # => 1
    vec[0:2] # => [1, 2]

    {'x': 100, 'y': 200}
    # => pmap({'y': 200, 'x': 100})

    ma = {'x': 100, 'y': 200}
    ma.get('x') # => 100
    ma.x # => 100
    ma['x'] # => 100
    ma2 = ma.set('x', 10000)
    # => pmap({'y': 200, 'x': 10000})
    ma # => pmap({'y': 200, 'x': 100})
    get(ma, 'y') # => 200
    ma['y'] # => 200

    m(x=100, y=200)
    # => pmap({'y': 200, 'x': 100})

    s(1, 2, 3)
    # => pset([1, 2, 3])

    b(1, 2, 3)
    # => pbag([1, 2, 3])

Function definitions
~~~~~~~~~~~~~~~~~~~~

.. code:: python

    def hoge(x):
        'hoge' + str(x)

    hoge(3)
    # => hoge3

Pattern matching
~~~~~~~~~~~~~~~~

.. code:: python

    lis = [1, 2, 3]

    # Sequence pattern
    match lis:
        [1, 2, x]: x
        _: None
    # => 3

    match lis:
        [1, &rest]: rest
        _: None

    # => pvector([2, 3])


    foo_map = {'foo' : 'bar'}

    # Mapping pattern
    match foo_map:
        {'foo' : value}: value
        _: None
    # => 'bar'


    # Type pattern
    # <name of variable refers to type> <pattern>: <action>
    match 10:
        int x: 'int'
        float x: 'float'
        str x: 'str'
        bool x: 'bool'
        _: 'other'
    # => 'int'

    match [1, 2, 3]:
        [1, str x, 3]: 'str'
        [1, int x, 3]: 'int'
        _: 'other'
    # => 'int'

    num = union(int, float)
    vector nums[num]
    vector strs[str]

    match nums([1, 2, 3]):
        nums[x, y, z]: z
        strs[x, y, z]: x
    # => 3

    Positive = predicate(-> $1 > 0)
    Even = predicate(-> $1 % 2 == 0)
    EvenAndPositive = predicate(-> ($1 % 2 == 0) and ($1 >= 0)) 

    match 10:
        EvenAndPositive n: str(n) + ':Even and Positive'
        Even n: str(n) + ':Even'
        Positive n: str(n) + ':Positive'

    # => 10:Even and Positive


    # Or pattern
    match ['foo', 100]:
        ['foo' or 'bar', value]: value
        _: 10000
    # => 100

    match ['foo', 100]:
        [str x or int x, value]: value
        _: 10000
    # => 100


    # Record pattern
    record Person(name, age)

    foo = Person('foo', 32)

    match foo:
        Person('bar', age):
            'bar:' + str(age)
        Person('foo', age):
            'foo:' + str(age)
        _: None
    # => 'foo:32'

Records
~~~~~~~

.. code:: python

    record Mochi
    record AnkoMochi(anko) < Mochi
    record KinakoMochi(kinako) < Mochi

    anko_mochi = AnkoMochi(anko=3)

    isinstance(anko_mochi, Mochi)
    # => True
    isinstance(anko_mochi, AnkoMochi)
    # => True
    isinstance(anko_mochi, KinakoMochi)
    # => False

    match anko_mochi:
        KinakoMochi(kinako): 'kinako ' * kinako + ' mochi'
        AnkoMochi(anko): 'anko ' * anko + 'mochi'
        Mochi(_): 'mochi'
    # => 'anko anko anko mochi'


    record Person(name, age):
        def show(self):
            print(self.name + ': ' + self.age)

    foo = Person('foo', '32')
    foo.show()
    # -> foo: 32

    # runtime type checking
    record Point(x:int, y:int, z:optional(int))
    Point(1, 2, None)
    # => Point(x=1, y=2, z=None)
    Point(1, 2, 3)
    # => Point(x=1, y=2, z=3)
    Point(1, None, 3)
    # => TypeError

Bindings
~~~~~~~~

.. code:: python

    x = 3000
    # => 3000

    [a, b] = [1, 2]
    a
    # => 1
    b
    # => 2

    [c, &d] = [1, 2, 3]
    c
    # => 1
    d
    # => pvector([2, 3])

Data types, like algebraic data types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: python

    data Point:
        Point2D(x, y)
        Point3D(x, y, z)

    # The meaning of the above is the same as the meaning of the following.
    # record Point
    # record Point2D(x, y) < Point
    # record Point3D(x, y, z) < Point

    p1 = Point2D(x=1, y=2)
    # => Point2D(x=1, y=2)

    p2 = Point2D(3, 4)
    # => Point2D(x=3, y=4)

    p1.x
    # => 1

Pattern-matching function definitions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: python

    data Point:
        Point2D(x, y)
        Point3D(x, y, z)

    def offset:
        Point2D(x1, y1), Point2D(x2, y2):
            Point2D(x1 + x2, y1 + y2)
        Point3D(x1, y1, z1), Point3D(x2, y2, z2):
            Point3D(x1 + x2, y1 + y2, z1 + z2)
        _: None

    offset(Point2D(1, 2), Point2D(3, 4))
    # => Point2D(x=4, y=6)
    offset(Point3D(1, 2, 3), Point3D(4, 5, 6))
    # => Point3D(x=5, y=7, z=9)

    def show:
        int x, message: print('int', x, message)
        float x, message: print('float', x, message)
        _: None

    show(1.0, 'msg')
    # -> float 1.0 msg
    # => None

    FileMode = options('r', 'w', 'a', 'r+', 'w+', 'a+')

    def open_file:
        str path, FileMode mode: 
            open(path, mode)
        str path:
            open(path, 'r')

Anonymous function
~~~~~~~~~~~~~~~~~~

.. code:: python

    # Arrow expression.
    add = (x, y) -> x + y
    add(1, 2)
    # => 3

    add = -> $1 + $2
    add(1, 2)
    # => 3

    foo = (x, y) ->
        if x == 0:
            y
        else:
            x

    foo(1, 2)
    # => 1

    foo(0, 2)
    # => 2

    pvector(map(-> $1 * 2, [1, 2, 3]))
    # => pvector([2, 4, 6])

Pipeline operator
~~~~~~~~~~~~~~~~~

.. code:: python

    add = -> $1 + $2
    2 |> add(10) |> add(12)
    # => 24
    None |>? add(10) |>? add(12)
    # => None

Lazy sequences
~~~~~~~~~~~~~~

.. code:: python

    def fizzbuzz(n):
        match [n % 3, n % 5]:
            [0, 0]: "fizzbuzz"
            [0, _]: "fizz"
            [_, 0]: "buzz"
            _: n


    result = range(1, 31) |> map(fizzbuzz)
    pvector(result)
    # => pvector([1, 2, fizz, 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz', 16, 17, 'fizz', 19, 'buzz', 'fizz', 22, 23, 'fizz', 'buzz', 26, 'fizz', 28, 29, 'fizzbuzz'])
    pvector(result)
    # => pvector([])
    pvector(result)
    # => pvector([])


    # Iterator -> lazyseq
    lazy_result = range(1, 31) |> map(fizzbuzz) |> lazyseq()
    pvector(lazy_result)
    # => pvector([1, 2, fizz, 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz', 16, 17, 'fizz', 19, 'buzz', 'fizz', 22, 23, 'fizz', 'buzz', 26, 'fizz', 28, 29, 'fizzbuzz'])
    pvector(lazy_result)
    # => pvector([1, 2, fizz, 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz', 16, 17, 'fizz', 19, 'buzz', 'fizz', 22, 23, 'fizz', 'buzz', 26, 'fizz', 28, 29, 'fizzbuzz'])
    pvector(lazy_result)
    # => pvector([1, 2, fizz, 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz', 16, 17, 'fizz', 19, 'buzz', 'fizz', 22, 23, 'fizz', 'buzz', 26, 'fizz', 28, 29, 'fizzbuzz'])

Trailing closures
~~~~~~~~~~~~~~~~~

.. code:: python

    # The following trailing closure expression is passed to a function as the function’s first argument.
    result = map([1, 2, 3]) ->
        print($1)
        $1 * 2

    print(doall(result))

    # -> 1
    # -> 2
    # -> 3
    # => pvector([2, 4, 6])


    def foreach(closure, seq):
        doall(filter(closure, seq))

    # The following trailing closure expression is passed to a function as the function’s first argument.
    foreach([1, 2, 3]) (item) ->
        new_item = item * 100
        print(new_item)

    # -> 100
    # -> 200
    # -> 300
    # => pvector([])

    # Or

    def foreach(seq, closure):
        doall(filter(closure, seq))

    # The following trailing closure expression is passed to a function as the function’s final argument.
    foreach([1, 2, 3]) @ (item) ->
        new_item = item * 100
        print(new_item)

    # -> 100
    # -> 200
    # -> 300
    # => pvector([])

Short form for keyword arguments and dict keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: python

    def foo(a, b, c):
        a + b + c
        
    a = 1
    b = 2
    c = 3

    # This is the same as foo(a=a, b=b, c=c)
    foo(=a, =b, =c))
    # => 6

    # This is the same as {'a': a, 'b': b}
    {=a, =b}
    # => pmap({'a': 1, 'b': 2})

Macros
~~~~~~

.. code:: python

    macro rest_if_first_is_true(first, &args):
         match first:
             quote(True): quasi_quote(v(unquote_splicing(args)))
             _: quote(False)

    rest_if_first_is_true(True, 1, 2, 3)
    # => pvector([1, 2, 3])
    rest_if_first_is_true("foo", 1, 2, 3)
    # => False

    macro pipeline(&args):
        [Symbol('|>')] + args

    pipeline([1, 2, 3],
             map(-> $1 * 2),
             filter(-> $1 != 2),
             pvector())
    # => pvector([4, 6])

Including a file at compile time
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: sh

    $ cat anko.mochi
    x = 10000
    y = 20000

.. code:: python

    require 'anko.mochi'
    x
    # => 10000

    x = 30000

    require 'anko.mochi' # include once at compile time
    x
    # => 30000

Module
~~~~~~

.. code:: python

    module Math:
        export add, sub
        
        def add(x, y):
            x + y
        
        def sub(x, y):
            x - y

    Math.add(1, 2)
    # => 3

.. code:: sh

    $ cat foobar.mochi
    foo = 'foo'
    bar = 'bar'

.. code:: python

    require 'foobar.mochi'
    [foo, bar]
    # => pvector(['foo', 'bar'])

    foo = 'foofoofoo'

    module X:
        export foobar
        require 'foobar.mochi'
        def foobar:
            [foo, bar]

    X.foobar()
    # => pvector(['foo', 'bar'])

    [foo, bar]
    # => pvector(['foofoofoo', 'bar'])

TODO
----

-  Improve documentation
-  Improve parsing
-  Support type annotation

License
-------

MIT License

Contributors
------------

https://github.com/i2y/mochi/graphs/contributors