Functional Python with Mochi

A New Language Written in Python Working With Python


Dr. Mike Müller
Python Academy GmbH & Co. KG


EuroPython 2015
Bilbao

Mochi

  • Is Dynamically typed
  • Supports functional programming
  • Supports actor-style programming
  • Needs > Python 3.2
  • Works with PyPy
  • Translates Mochi code to

    • Python 3's AST
    • Bytecode
    • Python 3 code (partially, work in progress)

Features

  • Python-like syntax
  • Tail call elimination
  • No loop syntax
  • Re-assignments not allowed in function definition
  • Persistent data structures (Pyrsistent)
  • Pattern matching
  • Data types, like algebraic data types

More Features

  • 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 include those from itertools, recipes, functools, operator
  • REPL a.k.a. as >>>
  • IPython Notebook kernel

REPL

In [ ]:
$ mochi 
>>> 1 + 1
2
>>> def add(a, b):
...     a + b  # no return!
... 
<function add at 0x1025a0598>
>>> add(3, 4)
7
>>> exit()

Working with Modules

In [ ]:
# file add.mochi

def add(a, b):
    a + b

$ mochi 
>>> import add
>>> add(3, 4)
7

Generate Python Bytecode

In [ ]:
$ mochi -pyc add.mochi -o add.pyc

$ python3.4 
>>> import add
>>> add.add(2, 3)
5
>>> exit()

Generate Python Source Code

In [ ]:
$ mochi -py add.mochi -o add.py
$ less add.py

import mochi.core.builtins
None
if isinstance(__builtins__, dict):
    _gs83 = __builtins__.update(mochi.core.builtins.global_env)
else:
    _gs83 = __builtins__.__dict__.update(mochi.core.builtins.global_env)
_gs83
import eventlet
None
eventlet.monkey_patch()

def add(a, b):
    return (a + b)
    None
add

Use All Python Modules I

In [11]:
import numpy

a = numpy.arange(10)
a * 2
[0 1 2 3 4 5 6 7 8 9]
[ 0  2  4  6  8 10 12 14 16 18]

Use All Python Modules II

In [ ]:
app = Flask('demo')

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

app.run()

No Tails

In [ ]:
from timeit import default_timer

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

start = default_timer()
res = factorial(10000, 1)
print('time:', default_timer() - start)
print('number of digits:', len(str(res)))

$ mochi fact.mochi 
time: 0.041667957993922755
number of digits: 35660

No Tails but Patterns

In [ ]:
from timeit import default_timer

def factorial:
    n: factorial(n, 1)
    0, acc: acc
    n, acc: factorial(n - 1, acc * n)

start = default_timer()
res = factorial(10000, 1)
print('time:', default_timer() - start)
print('number of digits:', len(str(res)))

$ mochi fact_pattern.mochi 
time: 0.15391840400116052
number of digits: 35660

More Pattern Matching I

In [4]:
lis = [1, 2, 3]

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

match lis:
    [1, &rest]: rest
    _: None
pvector([1, 2, 3])
3
pvector([2, 3])

More Pattern Matching II

In [17]:
def fizzbuzz(n):
    match [n % 3, n % 5]:
        [0, 0]: "fizzbuzz"
        [0, _]: "fizz"
        [_, 0]: "buzz"
        _: n
fizzbuzz(1)
fizzbuzz(3)
fizzbuzz(5)
fizzbuzz(15)
<function fizzbuzz at 0x104851f28>
1
fizz
buzz
fizzbuzz

No Loops

In [18]:
def sum_:
    [head, &tail]: head + sum_(tail)
    []: 0

print(sum_([10, 20, 30.5]))
print(sum_([]))
<function sum_ at 0x1047487b8>
60.5
0

Pipeline Operator

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

range(1, 31) |> map(fizzbuzz) |> pvector() |> print()
<function fizzbuzz at 0x105b0f378>
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'])

Persistent Data Structures (Pyrsistent)

  • Immutable
  • Always return new objects
In [22]:
v = [1, 2, 3]
m = {'a': 100, 'b': 200, 'c': 300}
v2 = v.append(4)
pvector([1, 2, 3])
pmap({'b': 200, 'c': 300, 'a': 100})
pvector([1, 2, 3, 4])

Anonymous Function I

In [26]:
add = (x, y) -> x + y
add(1, 2)
<function _gs200 at 0x104851c80>
3

Anonymous Function II

In [27]:
add = -> $1 + $2
add(1, 2)
<function _gs201 at 0x104851e18>
3

Anonymous Function III

In [54]:
pvector(map(-> $1 * 2, [1, 2, 3]))
pvector([2, 4, 6])

Runs in an IPython Notebook

  • Room to improve
  • Basic functionality
  • No call tips yet
  • No magic functions yet
  • Bug: stderr only to console
In [6]:
[1, 2, 3]
pvector([1, 2, 3])

Status

  • Working implementation
  • Still very early phase
  • Needs more tests
  • Needs more documentation
  • Needs community

Sprint at EuroPython

  • Come sprint with us
  • Explore Mochi
  • Contribute your ideas
  • Contribute your experience

Thanks

Lead Developer

  • Yasushi Itoh
  • i2y.mochi@gmail.com

You can reach me:

  • @pyacademy
  • mmueller@python-academy.de