Skip to content

Instantly share code, notes, and snippets.

@divs1210
Last active May 24, 2023 23:14
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save divs1210/d218d4b747b08751b2a232260321cdeb to your computer and use it in GitHub Desktop.
Save divs1210/d218d4b747b08751b2a232260321cdeb to your computer and use it in GitHub Desktop.
helps escape the Python's clutch
import types
# Helpers
# =======
def _obj():
'''Dummy object'''
return lambda: None
_FILLER = _obj()
# API
# ===
def Y(g):
'''Y combinator - makes recursive lambdas
ex: Y(lambda fact:
lambda n:
1 if n < 2 else n * fact(n - 1))(5)
gives: 120
'''
exp = lambda f: g(lambda arg: f(f)(arg))
return (exp)(exp)
def COND(cond_body_pairs, _else=lambda: None):
'''Functional if-elif-...-else expression
ex: COND((1==0, lambda: 'a',
2==0, lambda: 'b',
3==0, lambda: 'c'),
_else= lambda: 'd')
gives: 'd'
Note: All conditions are evaluated immediately!
For conditions that should be evaluated only
when required, use IF.
'''
if len(cond_body_pairs) == 0:
return _else()
cond, body = cond_body_pairs[:2]
if cond:
return body()
else:
return COND(cond_body_pairs[2:], _else)
def IF(cond, then, _else=lambda: None):
'''Functional if-then-else expression
ex: IF(1==0, lambda: 'a',
_else= lambda: 'b')
gives: 'b'
'''
return COND((cond, then), _else)
def LET(bindings, body, env=None):
'''Introduce local bindings.
ex: LET(('a', 1,
'b', 2),
lambda o: [o.a, o.b])
gives: [1, 2]
Bindings down the chain can depend on
the ones above them through a lambda.
ex: LET(('a', 1,
'b', lambda o: o.a + 1),
lambda o: o.b)
gives: 2
'''
if len(bindings) == 0:
return body(env)
env = env or _obj()
k, v = bindings[:2]
if isinstance(v, types.FunctionType):
v = v(env)
setattr(env, k, v)
return LET(bindings[2:], body, env)
def FOR(bindings, body, env=None):
'''Clojure style List comprehension.
ex: FOR(('a', range(2),
'b', range(2)),
lambda o: (o.a, o.b))
gives: [(0, 0), (0, 1), (1, 0), (1, 1)]
Bindings down the chain can depend on
the ones above them through a lambda
like in LET.
Special bindings take lambdas as values
and can be used any number of times:
* ':LET' - Temporary bindings
* ':IF' - don't produce a value if this
returns a falsey value
* ':WHILE' - break out of the innermost
loop if this returns a falsey value
'''
if len(bindings) == 0:
tmp = body(env)
return [] if tmp is _FILLER else [tmp]
env = env or _obj()
k, v = bindings[:2]
if k == ':IF':
cond = v(env)
return FOR(bindings[2:],
lambda e: body(e) if cond else _FILLER,
env)
elif k == ':LET':
return LET(v,
lambda e: FOR(bindings[2:], body, e),
env)
elif k == ':WHILE':
if v(env):
return FOR(bindings[2:], body, env)
else:
return []
elif isinstance(v, types.FunctionType):
v = v(env)
res = []
for x in v:
setattr(env, k, x)
res += FOR(bindings[2:], body, env)
delattr(env, k)
return res
# Tests
# =====
## LET form
assert LET(('a', 2,
'b', lambda o: o.a * 3),
lambda o: o.b - 1) == 5
## Y combinator (recursive lambda) and IF form
assert Y(lambda fact:
lambda n:
IF(n < 2, lambda: 1,
_else= lambda: n * fact(n - 1)))(5) == 120
## FOR comprehension
assert FOR(('a', range(3)),
lambda o: o.a + 1) == [1, 2, 3]
## Chained FOR comprehension
assert FOR(('a', range(3),
':IF', lambda o: o.a > 0,
'b', lambda o: range(3 - o.a),
':LET', ('res', lambda o: [o.a, o.b]),
':WHILE', lambda o: o.a < 2),
lambda o: o.res) == [
# filtered a == 0
[1, 0], [1, 1],
# stopped at a == 2
]
@divs1210
Copy link
Author

divs1210 commented Jun 25, 2017

Check out similar exercises:

@rebcabin
Copy link

rebcabin commented Nov 3, 2022

Brilliant! I had been replacing my need for let x = ... ,,, with ((lambda x ,,,) ...), producing monsters like this (a fast memoized Fibonacci when fed to a 2-arg Y combinator):

    ff_e = (lambda f:
            (lambda a:
             (lambda n:
              (a, 1) if n < 2 else
              ((lambda n_1:
                (a, a[n_1]) if n_1 in a else
                ((lambda fim1:
                  ((lambda m1:
                    ((lambda r1:
                      ((lambda a1:
                        ((lambda n_2:
                          (a1, r1 + a1[n_2]) if n_2 in a1 else
                          ((lambda fim2:
                            ((lambda m2:
                              ((lambda r2:
                                ((lambda a2:
                                  (a2, r1 + r2))
                                 (m2[0] | {n_2: r2})))
                               (m2[1])))
                             (fim2(n_2))))
                           (f(a1))))
                         (n - 2)))
                       (m1[0] | {n_1: r1})))
                     (m1[1])))
                   (fim1(n_1))))
                 (f(a))))
               (n - 1)))))

@divs1210
Copy link
Author

divs1210 commented Nov 3, 2022

ouch, that's gnarly! 😅

glad to be of help!

@rebcabin
Copy link

rebcabin commented Nov 4, 2022

It's from this fully-typed development of Y https://github.com/rebcabin/rebcabin.github.io/blob/main/PythonYCombinators.ipynb . I'm working my way through "Lambda, the Ultimate Imperative" as part of a new Python compiler https://lpython.org

@divs1210
Copy link
Author

divs1210 commented Nov 4, 2022

@rebcabin Nice!

I can't seem to find much to read about LCompilers or ASR online, though.

Could you point me to some literature?

@rebcabin
Copy link

rebcabin commented Nov 4, 2022 via email

@divs1210
Copy link
Author

divs1210 commented Nov 8, 2022

@rebcabin Would love to know more about the ASR stuff!

I had gone through the LCompilers website and github, but didn't find much info.

Went through the LFortran page this time, and saw some more details.

We will never extend a language, but we may subset it for speed.

This seems to not be true for LFortran, with the extended global scope and type inference.

Could I be of help in some way? Would love to contribute to LPython!

I have seen two different ways of speeding up Python using PE - PyPy tracing JIT and Graal's self-modifying AST.

The ASR stuff looks like a variant of PE from afar, correct me if I'm wrong!

@rebcabin
Copy link

rebcabin commented Nov 9, 2022

Yes, I will get back to you here with some info on contributing to LPython!

BTW, I am running with one of your ideas to build a Schemulation of Python's imperatives so that we are sure we know what we're doing when we design ASR.

https://github.com/rebcabin/rebcabin.github.io/blob/main/LambdaThePynultimateImperative002.ipynb

@certik
Copy link

certik commented Nov 10, 2022

@divs1210 we would love any contributions. You are welcome to send a PR, you can join us at the Zulip chat (https://lfortran.zulipchat.com/, we use it for LPython as well for now), and I am also happy to meet with you to get you up and running.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment