Powered by Pydialect and unpythonic.
from __lang__ import lispython
def fact(n):
def f(k, acc):
if k == 1:
return acc
f(k - 1, k*acc)
f(n, acc=1)
assert fact(4) == 24
fact(5000)
t = letrec[((evenp, lambda x: (x == 0) or oddp(x - 1)),
(oddp, lambda x: (x != 0) and evenp(x - 1))) in
evenp(10000)]
assert t is True
square = lambda x: x**2
assert square(3) == 9
assert square.__name__ == "square"
g = lambda x: [local[y << 2*x],
y + 1]
assert g(10) == 21
c = cons(1, 2)
assert tuple(c) == (1, 2)
assert car(c) == 1
assert cdr(c) == 2
assert ll(1, 2, 3) == llist((1, 2, 3))
In terms of unpythonic.syntax
, we implicitly enable tco
, autoreturn
,
multilambda
, namedlambda
, and quicklambda
for the whole module:
- TCO in both
def
andlambda
, fully automatic - Omit
return
in any tail position, like in Lisps - Multiple-expression lambdas,
lambda x: [expr0, ...]
- Named lambdas (whenever the machinery can figure out a name)
- The underscore:
f[_*3] --> lambda x: x*3
(namef
is reserved)
We also import some macros and functions to serve as dialect builtins:
- All
let[]
anddo[]
constructs fromunpythonic.syntax
cons
,car
,cdr
,ll
,llist
,nil
,prod
dyn
, for dynamic assignment
For detailed documentation of the language features, see
unpythonic.syntax
,
especially the macros tco
, autoreturn
, multilambda
,
namedlambda
, quicklambda
, let
and do
.
The multi-expression lambda syntax uses do[]
, so it also allows lambdas
to manage local variables using local[name << value]
and delete[name]
.
See the documentation of do[]
for details.
The builtin let[]
constructs are let
, letseq
, letrec
, the
decorator versions dlet
, dletseq
, dletrec
, the block
versions (decorator, call immediately, replace def'd name with result)
blet
, bletseq
, bletrec
, and the code-splicing variants
let_syntax
and abbrev
. Bindings may be made using any syntax variant
supported by unpythonic.syntax
.
The builtin do[]
constructs are do
and do0
.
For more, import from unpythonic
, the standard library of Lispython
(on top of what Python itself already provides).
quicklambda
is powered by macropy.quick_lambda
.
Lispython is a dialect of Python implemented in MacroPy. The dialect aims at production quality.
This module is the dialect definition, invoked by dialects.DialectFinder
when it detects a lang-import that matches our module name.
The goal of the Lispython dialect is to fix some glaring issues that hamper Python when viewed from a Lisp/Scheme perspective, as well as make the popular almost-Lisp, Python, feel slightly more lispy.
We take the approach of a relatively thin layer of macros (and underlying functions that implement the actual functionality), minimizing magic as far as reasonably possible.
Performance is only a secondary concern; performance-critical parts fare better at the other end of the wide spectrum, with Cython. Lispython is for the remaining 80% of the code, where the bottleneck is human developer time.
The aforementioned block macros are enabled implicitly for the whole module; this is the essence of the Lispython dialect. Other block macros can still be invoked manually in the user code.
Of the other block macros in unpythonic.syntax
, code written in Lispython
supports only continuations
. autoref
should also be harmless enough
(will expand too early, but shouldn't matter).
prefix
, curry
, lazify
and envify
are not compatible with
the ordering of block macros implicit in the Lispython dialect.
prefix
is a first-pass (outside-in) macro that should expand first, so
it should be placed in a lexically outer position with respect to the ones
Lispython invokes implicitly; but nothing can be more outer than the
dialect template.
The other three are second-pass (inside-out) macros that should expand later, so similarly, also they should be placed in a lexically outer position.
Basically, any block macro that can be invoked lexically inside a with tco
block will work, the rest will not.
If you need e.g. a lazy Lispython, the way to do that is to make a copy of the
dialect module, change the dialect template to import the lazify
macro, and
then include a with lazify
in the appropriate position, outside the
with namedlambda
block. Other customizations can be made similarly.
Just use with continuations
from unpythonic.syntax
where needed.
See its documentation for usage.
Lispython works with with continuations
, because:
-
Nesting
with continuations
within awith tco
block is allowed, for the specific reason of supporting continuations in Lispython.The dialect's implicit
with tco
will just skip thewith continuations
block (continuations
implies TCO). -
autoreturn
,quicklambda
andmultilambda
are first-pass macros, so although they will be in a lexically outer position with respect to the manually invokedwith continuations
in the user code, this is correct because first-pass macros expand outside-in (so they run beforecontinuations
, as they should). -
The same applies to the first pass of
namedlambda
. Its second pass, on the other hand, must come aftercontinuations
, which it does, since the dialect's implicitwith namedlambda
is in a lexically outer position with respect to thewith continuations
.
Be aware, though, that the combination of the autoreturn
implicit in the
dialect and with continuations
might have suboptimal usability, because
continuations
handles tail calls specially (the target of a tail-call in a
continuations
block must be continuation-enabled; see the documentation of
continuations
), and autoreturn
makes it visually slightly less clear
which positions are in fact tail calls (since no explicit return
).
Racket is an excellent Lisp, especially with sweet, sweet expressions [1] [2] [3], not to mention extremely pythonic. The word is rackety; the syntax of the language comes with an air of Zen minimalism (as perhaps expected of a descendant of Scheme), but the focus on batteries included and understandability are remarkably similar to the pythonic ideal. Racket even has an IDE (DrRacket) and an equivalent of PyPI, and the documentation is simply stellar.
Python, on the other hand, has a slight edge in usability to the end-user
programmer, and importantly, a huge ecosystem of libraries, second to None
.
Python is where science happens (unless you're in CS). Python is an almost-Lisp
that has delivered on the productivity promise of Lisp.
Python also gets many things right, such as well developed support for lazy
sequences, and decorators.
In certain other respects, Python the base language leaves something to be desired, if you have been exposed to Racket (or Haskell, but that's a different story). Writing macros is harder due to the irregular syntax, but thankfully MacroPy already exists, and any set of macros only needs to be created once.
Practicality beats purity (ZoP §9): hence, fix the minor annoyances that would otherwise quickly add up, and reap the benefits of both worlds. If Python is software glue, Lispython is an additive that makes it flow better.
For PG's accumulator puzzle even Lispython can do no better than this let-over-lambda (here using the haskelly let-in syntax for bindings):
foo = lambda n0: let[(n, n0) in
(lambda i: n << n + i)]
Testing it:
f = foo(10)
assert f(1) == 11
assert f(1) == 12
This still sets up a separate place for the accumulator. The modern pure Python solution avoids that, but needs many lines:
def foo(n):
def accumulate(i):
nonlocal n
n += i
return n
return accumulate
The problem is that assignment to a lexical variable (including formals) is a
statement in Python. If we abbreviate accumulate
as a lambda, it needs a
let
environment to write in (to use unpythonic's expression-assignment).
But see envify
in unpythonic.syntax
, which allows this:
from unpythonic.syntax import macros, envify
with envify:
def foo(n):
return lambda i: n << n + i
or as a one-liner:
with envify:
foo = lambda n: lambda i: n << n + i
envify
may be made part of the Lispython dialect definition later, but
first it needs testing to decide whether this particular, perhaps rarely used,
feature is worth a performance hit.
No instrumentation exists (or is even planned) for the Lispython layer; you'll have to use regular Python tooling to profile, debug, and such. The Lispython layer should be thin enough for this not to be a major problem in practice.
Shading? It may be off by a bit a lot, I'm not a 2D artist.
Semantics? Lispython is obviously made of two parts: Python, and...