Package: 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.FeaturesPython-like syntaxTail recursion optimization (self tail recursion only), and no loop
syntaxRe-assignments are not allowed in function definition.Basic collection type is a persistent data structure. (using
Pyrsistent)Pattern matching / Data types, like algebraic data typesPipeline operatorSyntax sugar of anonymous function definitionActor, like the actor of Erlang (using Eventlet)Macro, like the traditional macro of LispBuiltin functions includes functions exported by itertools module,
recipes, functools module and operator moduleExamplesFactorialdeffactorial(n,m):ifn==1:melse:factorial(n-1,n*m)factorial(10000,1)# => 28462596809170545189064132121198688...# Ordeffactorial:n:factorial(n,1)0,acc:accn,acc:factorial(n-1,acc*n)factorial(10000)# => 28462596809170545189064132121198688...FizzBuzzdeffizzbuzz(n):match[n%3,n%5]:[0,0]:"fizzbuzz"[0,_]:"fizz"[_,0]:"buzz"_:nrange(1,31)|>map(fizzbuzz)|>pvector()|>print()Actordefshow():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# -> bardefshow_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 2000Distributed Computing# comsumer.mochifrommochi.actor.mailboximportKombuMailbox,ZmqInbox,SQSMailboxdefconsumer():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.mochifrommochi.actor.mailboximportKombuMailbox,ZmqOutbox,SQSMailboxkombu_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'FlaskfromflaskimportFlaskapp=Flask('demo')@app.route('/')defhello():'Hello World!'app.run()RxPY# usage: mochi -no-mp timer.mochi# original:# https://github.com/ReactiveX/RxPY/blob/master/examples/parallel/timer.pyimportrximportconcurrent.futuresimporttimeseconds=[5,1,2,4,3]defsleep(t):time.sleep(t)returntdefoutput(result):print('%dseconds'%result)withconcurrent.futures.ProcessPoolExecutor(5)asexecutor:rx.Observable.from_(seconds).flat_map((s)->executor.submit(sleep,s)).subscribe(output)# 1 seconds# 2 seconds# 3 seconds# 4 seconds# 5 secondsaif (Anaphoric macro)macroaif(test,true_expr,false_expr):quasi_quote:it=unquote(test)ifit:unquote(true_expr)else:unquote(false_expr)aif([],first(it),"empty")# => "empty"aif([10,20],first(it),"empty")# => 10RequirementsCPython >= 3.2 or PyPy >= 3.2.1rply >= 0.7.2pyrsistent >= 0.10.1pathlib >= 1.0.1eventlet >= 0.17.1msgpack-python >= 0.4.6typeannotations >= 0.1.0Installation$pip3installmochi$pip3installflaskFlask-RESTfulPillowRxPY# to run the examplesOptional Installation$pip3installflaskFlask-RESTfulPillowRxPY# to run the examples$pip3installpyzmq# to use ZmqInbox and ZmqOutbox$pip3installkombu# to use KombuMailbox$pip3installboto# to use SQS as transport of KombuMailbox$pip3installboto3# to use SQSMailboxTh 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.2In this case, please change the version of cffi to 0.8.2 using pip on PyPy.$pip3uninstallcffi$pip3installcffi==0.8.2UsageREPL$mochi>>>loading and running a file$catkinako.mochiprint('kinako')$mochikinako.mochikinako$mochi-no-mpkinako.mochi# not apply eventlet's monkey patchingkinakobyte compilation$mochi-ckinako.mochi>kinako.mochicrunning a byte-compiled file$mochi-ekinako.mochickinako$mochi-e-no-mpkinako.mochic# not apply eventlet's monkey patchingkinakogenerating .pyc$lskagami.mochi$catkagami.mochiprint('kagami')$mochi>>>importkagamikagami>>>exit()$lskagami.mochikagami.pyc$python3kagami.pyckagamiOr$mochi-pyckagami.mochi>kagami.pyc$python3kagami.pyckagami$mochi-pyc-no-mpkagami.mochi>kagami.pyc# not apply eventlet's monkey patching$python3kagami.pyckagamiExamples for each featurePersistent 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]=vecx# => 1y# => 2z# => 3get(vec,0)# => 1get(vec,0,2)# => [1, 2]vec[0]# => 1vec[0:2]# => [1, 2]{'x':100,'y':200}# => pmap({'y': 200, 'x': 100})ma={'x':100,'y':200}ma.get('x')# => 100ma.x# => 100ma['x']# => 100ma2=ma.set('x',10000)# => pmap({'y': 200, 'x': 10000})ma# => pmap({'y': 200, 'x': 100})get(ma,'y')# => 200ma['y']# => 200m(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 definitionsdefhoge(x):'hoge'+str(x)hoge(3)# => hoge3Pattern matchinglis=[1,2,3]# Sequence patternmatchlis:[1,2,x]:x_:None# => 3matchlis:[1,&rest]:rest_:None# => pvector([2, 3])foo_map={'foo':'bar'}# Mapping patternmatchfoo_map:{'foo':value}:value_:None# => 'bar'# Type pattern# <name of variable refers to type> <pattern>: <action>match10:intx:'int'floatx:'float'strx:'str'boolx:'bool'_:'other'# => 'int'match[1,2,3]:[1,strx,3]:'str'[1,intx,3]:'int'_:'other'# => 'int'num=union(int,float)vectornums[num]vectorstrs[str]matchnums([1,2,3]):nums[x,y,z]:zstrs[x,y,z]:x# => 3Positive=predicate(->$1>0)Even=predicate(->$1%2==0)EvenAndPositive=predicate(->($1%2==0)and($1>=0))match10:EvenAndPositiven:str(n)+':Even and Positive'Evenn:str(n)+':Even'Positiven:str(n)+':Positive'# => 10:Even and Positive# Or patternmatch['foo',100]:['foo'or'bar',value]:value_:10000# => 100match['foo',100]:[strxorintx,value]:value_:10000# => 100# Record patternrecordPerson(name,age)foo=Person('foo',32)matchfoo:Person('bar',age):'bar:'+str(age)Person('foo',age):'foo:'+str(age)_:None# => 'foo:32'RecordsrecordMochirecordAnkoMochi(anko)<MochirecordKinakoMochi(kinako)<Mochianko_mochi=AnkoMochi(anko=3)isinstance(anko_mochi,Mochi)# => Trueisinstance(anko_mochi,AnkoMochi)# => Trueisinstance(anko_mochi,KinakoMochi)# => Falsematchanko_mochi:KinakoMochi(kinako):'kinako '*kinako+' mochi'AnkoMochi(anko):'anko '*anko+'mochi'Mochi(_):'mochi'# => 'anko anko anko mochi'recordPerson(name,age):defshow(self):print(self.name+': '+self.age)foo=Person('foo','32')foo.show()# -> foo: 32# runtime type checkingrecordPoint(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)# => TypeErrorBindingsx=3000# => 3000[a,b]=[1,2]a# => 1b# => 2[c,&d]=[1,2,3]c# => 1d# => pvector([2, 3])Data types, like algebraic data typesdataPoint: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) < Pointp1=Point2D(x=1,y=2)# => Point2D(x=1, y=2)p2=Point2D(3,4)# => Point2D(x=3, y=4)p1.x# => 1Pattern-matching function definitionsdataPoint:Point2D(x,y)Point3D(x,y,z)defoffset: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)_:Noneoffset(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)defshow:intx,message:print('int',x,message)floatx,message:print('float',x,message)_:Noneshow(1.0,'msg')# -> float 1.0 msg# => NoneFileMode=options('r','w','a','r+','w+','a+')defopen_file:strpath,FileModemode:open(path,mode)strpath:open(path,'r')Anonymous function# Arrow expression.add=(x,y)->x+yadd(1,2)# => 3add=->$1+$2add(1,2)# => 3foo=(x,y)->ifx==0:yelse:xfoo(1,2)# => 1foo(0,2)# => 2pvector(map(->$1*2,[1,2,3]))# => pvector([2, 4, 6])Pipeline operatoradd=->$1+$22|>add(10)|>add(12)# => 24None|>?add(10)|>?add(12)# => NoneLazy sequencesdeffizzbuzz(n):match[n%3,n%5]:[0,0]:"fizzbuzz"[0,_]:"fizz"[_,0]:"buzz"_:nresult=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 -> lazyseqlazy_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# The following trailing closure expression is passed to a function as the function’s first argument.result=map([1,2,3])->print($1)$1*2print(doall(result))# -> 1# -> 2# -> 3# => pvector([2, 4, 6])defforeach(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*100print(new_item)# -> 100# -> 200# -> 300# => pvector([])# Ordefforeach(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*100print(new_item)# -> 100# -> 200# -> 300# => pvector([])Short form for keyword arguments and dict keysdeffoo(a,b,c):a+b+ca=1b=2c=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})Macrosmacrorest_if_first_is_true(first,&args):matchfirst: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)# => Falsemacropipeline(&args):[Symbol('|>')]+argspipeline([1,2,3],map(->$1*2),filter(->$1!=2),pvector())# => pvector([4, 6])Including a file at compile time$catanko.mochix=10000y=20000require'anko.mochi'x# => 10000x=30000require'anko.mochi'# include once at compile timex# => 30000ModulemoduleMath:exportadd,subdefadd(x,y):x+ydefsub(x,y):x-yMath.add(1,2)# => 3$catfoobar.mochifoo='foo'bar='bar'require'foobar.mochi'[foo,bar]# => pvector(['foo', 'bar'])foo='foofoofoo'moduleX:exportfoobarrequire'foobar.mochi'deffoobar:[foo,bar]X.foobar()# => pvector(['foo', 'bar'])[foo,bar]# => pvector(['foofoofoo', 'bar'])TODOImprove documentationImprove parsingSupport type annotationLicenseMIT LicenseContributorshttps://github.com/i2y/mochi/graphs/contributors