Mochi
Dynamically typed functional programming language
Install / Use
/learn @i2y/MochiREADME
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
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
def fizzbuzz(n):
match [n % 3, n % 5]:
[0, 0]: "fizzbuzz"
[0, _]: "fizz"
[_, 0]: "buzz"
_: n
range(1, 31)
|> map(fizzbuzz)
|> pvector()
|> print()
Actor
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
# 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()
# 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
from flask import Flask
app = Flask('demo')
@app.route('/')
def hello():
'Hello World!'
app.run()
RxPY
# 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)
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
See requirements.txt
Installation
$ pip3 install mochi
Optional Installation
$ pip3 install flask Flask-RESTful Pillow RxPY # to run the examples
$ 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.
$ pip3 uninstall cffi
$ pip3 install cffi==0.8.2
Usage
REPL
$ mochi
>>>
loading and running a file
$ cat kinako.mochi
print('kinako')
$ mochi kinako.mochi
kinako
$ mochi -no-mp kinako.mochi # not apply eventlet's monkey patching
kinako
byte compilation
$ mochi -c kinako.mochi > kinako.mochic
running a byte-compiled file
$ mochi -e kinako.mochic
kinako
$ mochi -e -no-mp kinako.mochic # not apply eventlet's monkey patching
kinako
generating .pyc
$ ls
kagami.mochi
$ cat kagami.mochi
print('kagami')
$ mochi
>>> import kagami
kagami
>>> exit()
$ ls
kagami.mochi kagami.pyc
$ python3 kagami.pyc
kagami
Or
$ 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
[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
def hoge(x):
'hoge' + str(x)
hoge(3)
# => hoge3
Pattern matching
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
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
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
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
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:
