1. 26
  1.  

  2. 2

    I’m surprised to see that := cannot be rewritten in other statements. Isn’t a := ... equivalent to tmp = ...; a = tmp?

    1. 6

      := can be used in lambda’s, wheras = cannot, and as you cannot re-implement lambdas in Python, you’ll need := to achieve full feature set.

      1. 3

        Why can’t lambdas be reimplemented as a simple “return foo” function defined right before the lambda usage? Something about different scope?

        1. 2

          I’d say it’s more about properties. Their __name__ is <lambda>, you can’t pickle them, etc. I’m not sure if you can re-write lambdas to functions and bring those properties back. I guess this is a case about semantics, but I think that these properties are important to keep, since there are programs that do care about it, and the author’s post was about any Python program in general.

          1. 3

            The OP doesn’t have async or comprehensions. It’s much harder to fake those. All you have to do for the lambda thing, if you really think it’s important that they have a meaningless name, is rewrite a few properties and make a replacement code object (which is immutable and annoyingly also has a name). Child’s play!

            In MVPy:

            def lit(**kw):
                return next(iter(kw))
            
            def ga(x, /, **kw):
                return getattr(x, next(iter(kw)))
            
            join = ga(str(), join=1)
            
            def concat(*a):
                return join(a)
            
            lambda_with_angles = concat(chr(60), lit(lam=1), lit(bda=1), chr(62))
            
            def lambdafy(func):
                code = ga(func, __code__=1)
                new_code = ga(code, replace=1)(co_name=lambda_with_angles)
                setattr(func, lit(__code__=1), new_code)
                setattr(func, lit(__name__=1), lambda_with_angles)
                dot = chr(46)
                qualname_bits = ga(ga(func, __qualname__=1), split=1)(dot)
                ga(qualname_bits, pop=1)()
                ga(qualname_bits, append=1)(lambda_with_angles)
                setattr(func, lit(__qualname__=1), ga(dot, join=1)(qualname_bits))
            
            x = lambda a: a
            def y(a):
                return a
            lambdafy(y)
            

            edit: I guess if I’m using Python 3.8’s / syntax I can use CodeType.replace too

            1. 5

              Async is actually fairly easy. asycnio existed even before the async/await syntax was introduced. Async functions (in 3.8, before it was different) return an object with an __await__ method that returns an iterator, that the async runtime(usually asyncio, but others exist) iterate through, and get the requests for IO from, and sends back the results to the object via a send() method. await exists for connecting the iterators together conveniently, though it was (and still is) possible to do so via yield from. The parts in the language were there for a long time already, it just took a while to put it together and make it comfortable.

              Comprehensions can usually be trivially rewritten into generators. A simple case like (<item> for <identifiers> in <source> [if <condition>]) can be rewritten into

              def gen():
                  for <identifiers> in <source>:
                      if <condition>:
                          yield <item>
              

              It of course gets a bit more complicated when you get into multiple for’s in a comprehension, but that is still not too complicated to do.

              As for lambdas, your implementation might be workable, but that’s too much details for me to look into right now.

              1. 1

                Yes, it’s easy to get the important behaviour of each, but for lambdas you were asking for something that’s indistinguishable. I believe my solution for lambdas yields something that is indistinguishable from a lambda constructed the usual way; I think it would be harder to do this with async or comprehensions.

                1. 1

                  I don’t think it is too difficult to make async to work in a way that is indistinguishable from the way it works right now. As for comprehensions, the objects are just standard generator objects, nothing fancy there to make it difficult.

        2. 2

          I think you can write any Python with lambdas without lambdas by repeatedly replacing a top-level lambda (i.e. one that isn’t nested inside another lambda) with a named function. The trick is to be sure to name other expressions and intermediate results you encounter along the way, so that you don’t reorder the evaluation of the lambda definitions you’re rewriting with respect to anything else; example below.

          I think this is hard for a human to do systematically without errors, but a compiler could do this sort of thing in its sleep, and a human could probably do this for all reasonable code, because reasonable code doesn’t tend to rely on effectful lambda definitions.

          a = [7]
          ((lambda f: (a.append(a), f)[1])(lambda f, x: f(x)))(lambda x, _=
           a[1].__setitem__(0, a[0] * 19): x(), lambda y=tuple(a): print(y[0])) # 133
          
          a = [7]
          def fn_1(f):
              return (a.append(a), f)[1]
          fn_1(lambda f, x: f(x))(lambda x, _=a[1].__setitem__(0, a[0] * 19): x(),
           lambda y=tuple(a): print(y[0])) # 133
          
          a = [7]
          def fn_1(f):
              return (a.append(a), f)[1]
          def fn_2(f, x):
              return f(x)
          var_1 = fn_1(fn_2)
          var_1(lambda x, _=a[1].__setitem__(0, a[0] * 19): x(), lambda y=tuple(a):
           print(y[0])) # 133
          
          a = [7]
          def fn_1(f):
              return (a.append(a), f)[1]
          def fn_2(f, x):
              return f(x)
          var_1 = fn_1(fn_2)
          def fn_3(x, _=a[1].__setitem__(0, a[0] * 19)):
              return x()
          def fn_4(y=tuple(a)):
              print(y[0])
          var_1(fn_3, fn_4) # 133
          
      2. 1

        I love it. But how are classes transformed into this?

        For that matter how are dictionaries and lists represented?

        Edit: ok looking into it the class description as functions is described here and the dictionary representation as a class (object) is described here. More cumbersome than I was hoping.