1. 4
    Write code, not compilers compilers plt python catern.com
  1.  

  2. 8

    To be quiet honest I didn’t get what the author tried to say. I’m not sure if the author understands what message he tries to deliver. And the blog post needs some additional clarification. It makes little sense even with the “example”.

    1. 3

      It makes more sense if you know a lot of Haskell, where this sort of thing is somewhat common. You basically define your functions as a sort of DSL that operates over a typeclass (which is Haskell’s term for an interface, essentially) and then you can do different things depending what type you pick.

      But since most people don’t know Haskell, the post falls flat.

      1. 2

        It’s a very common idiom in Lisp. The way that I was told to write Lisp code was to write a clear description of the problem and then a compiler that transformed it into the solution. I’ve not written more than a token amount of Lisp code, but when I’ve read code in this style it’s been very clear and comprehensible. If someone is arguing against this, I’d expect it to come with a lot of justification, rather than just assertion.

        1. 1

          The problem is that Python, the language used in the examples, is not Lisp. Most languages aren’t like Lisp or Haskell in this way either.

          1. 1

            Do you have any examples of code like this, for the sake of learning?

            1. 1

              Principles of Artificial Intelligence Programming uses this technique; here’s a grammar for a subset of English.

          2. 1

            It makes more sense if you know a lot of Haskell

            Well, I believe I’m doing advanced Scala. I’m kinda familiar with TF, pure FP and these sorts of things. But still.

            To convert a function into some other type (executable code, or a pretty-printed program, or something else), the easiest way is to call the function, passing certain arguments, such that the desired type is returned from the function.

            This makes little sense. Probably this guy is trying to say that something about memoization and purity?.. Well, I have a function, (a, b) => a+b, how can I “convert it into some other type” so I can interpret it on different platforms?

            A function does not need to be parsed and statically analyzed to figure out what it does. The function itself will tell you: Just call it and see.

            What?.. Is he trying to say that I may infer function AST by building a mapping table for particular inputs and outputs?..

            The function will speak to you

            What “will speak to you” stands for?

            in a language determined by the arguments you pass it.

            Is he trying to say that a function may interpret an indirect program expressed by a data structure? Why to use such an obscure language then? Why not to show some examples and not to say something about TF?

            The fewer global variables and types that a function refers to, the more the function can speak in the language we desire, rather than a hard-coded predetermined langauge.

            What do “less/more” mean in this context? What kind of a “global variable” the author is speaking about? What about “global types”? What are “local types”? How evil is an “global variable” of type IO[A, Nothing, B]?

            How “Write code, not compilers” related to the content of the post?

        2. 9

          This is a very unconvincing argument, literally so – you spend the precious few bytes asserting, not convincing or explaining or hinting or anything that would help the reader translate these abstract statements into something precise. This isn’t a koan, it’s 1/5th of a blog post.

          1. 2

            It has been useful on its own for some. There are concrete examples linked at the end if you don’t find it sufficient. Would you prefer if those concrete examples were included directly at the end of the post? I feel like that wouldn’t really be aesthetically appropriate…

            1. 9

              In my opinion (and I can’t state that this is my opinion strongly enough), Alan Kay’s early talks were more often about leading an audience to a conclusion rather than a conclusion outright. He then spent the remaining years yelling at the industry for failing to read his mind correctly (or failing to get his conclusions). Koans aren’t always appropriate. Often times, it’s hard enough to get people to accept good ideas stated directly.

          2. 3

            In the linked examples, what is the distinction between this technique and IoC/dependency injection? (is there an intended distinction?)

            The test implementation also seems like it would fail if you appended more than once.

            1. 1

              I have that same question. Not only the examples are similar to what I would expect from somebody who happens to be explaining the dependency inversion principle, also the title of the posted article is reminiscent of the good old “rely on abstraction, not on concretions” concept.

              Most likely I am missing something, but other than a “koan” that works as tool for thought even if it is not immediately comprehensible, the article leaves room for confusion by linking jargons in such an odd manner.

            2. 3

              To take a positive look at this idea, imagine that we are working with applicative mini-languages whose programs are always trees, as in the linked example. Then, there’s only two interesting ways to call a tree: either as a katamorphism or a paramorphism.

              For a fully-worked example, I happen to have a raytracer whose programs are specified as these sorts of trees. The program is written as a plain module in the host language, invoking a tree-builder object CSG. Then, instead of a single monolithic compiler, I have several katamorphisms:

              • expandCSG to represent complex objects in terms of simpler objects
              • costOfSolid to estimate the cost of rendering an object
              • asSDF to compile to a signed distance function for rendering
              • copyCSGTo to send an object tree to a subprocess

              Implemented in a support library and entrypoint. Note that this particular compiler is compiling to closures or using closures for code generation, rather than true staged compilation. (We have all the tools for staged compilation but I haven’t written a good-enough specializer to make it practical yet.)

              1. 2

                A function does not need to be parsed and statically analyzed to figure out what it does. The function itself will tell you: Just call it and see.

                The function will speak to you in a language determined by the arguments you pass it.

                I like this idea, but isn’t it limited to straight-line code? Or, maybe it depends on which primitives your programming language lets you customize.

                Suppose you want to pretty-print this function:

                def square(x):
                    return x*x
                

                You can do it in Python because you can customize __mul__. But you probably can’t do it in Javascript.

                Or suppose you want to pretty-print this function:

                def abs(x):
                    if x < 0:
                        return -x
                    else:
                        return x
                

                You can customize __lt__, __neg__, and even __bool__, but you can’t make the if take both branches.

                1. 4

                  Yeah, what you can get this way are traces. Not pretty-prints.

                  1. 2

                    Or suppose you want to pretty-print this function:

                    The trick here is to avoid primitive control flow.

                    For example, if you expose a list that the user can iterate over with a for-loop, that for-loop won’t be visible to you.

                    But if you expose a “foreach” method to which the user passes a function - that works just fine.

                    Or as another example, instead of returning a boolean that indicates whether a function is successful, return a Result object (like in Rust) which has a .or_else or .map method used to extract the value. Then you can take both branches by both calling, and not calling, the function passed to the .or_else or whatever method.

                    Of course, this is not always the most natural way to do things, but it can get pretty far. And in functional languages, that kind of design is fairly normal.

                    1. 5

                      Python doesn’t want you to avoid primitive control flow. If you write something you’d need monads (and therefore higher-kinded types), people will tell you it’s not idiomatic Python.

                      Since there isn’t any do-notation, it’d also going to look super weird, even before we start composing effects with monad transformers.

                      1. 1

                        But you can do quite a lot without monads or do-notation, as the examples show.

                      2. 1

                        I’m failing to see the point of all this. I have this function (Lua, where ‘//’ is integer division):

                        function dayofweek(date)
                          local a = (14 - date.month) // 12
                          local y = date.year - a
                          local m = date.month + 12 * a - 2
                          local d = date.day
                                  + y
                                  + y // 4
                                  - y // 100
                                  + y // 400
                                  + 31 * m // 12
                          return (d % 7) + 1
                        end
                        

                        Straightforward code, so no issues with flow control. In Lua, you can override each of the operators, but that still fails to capture the assignments, and even the return statement.

                        1. 3

                          It depends on what you want out. You’ll lose the assignments, but you could capture an expression tree of the arithmetic that needs to be done and print that or code generate from it or whatever.

                          Suppose that date is some object with overloaded arithmetic and it all falls out. The final return is of an expression including d, which referenced date, so if you have enough overloads you’ll get what you wanted out.

                          Kind of off-topic, but I like how a system with open classes and where all operations are really functions lets you do this stuff easily. For a prosiac example, consider the Measurements.jl package for Julia. It defines a type that contains a number and the uncertainty associated with that number. Overloaded functions allows you to pass an object of this type into any numeric function you like and get out a new measurement that has the uncertainty correctly propagated, but you can go beyond numeric functions and overload methods in plotting libraries and elsewhere and inexpensively get error propagation working with libraries that never considered it.

                        2. 1

                          The trick here is to avoid primitive control flow.

                          In case you are advocating indirection through operating on data structures, self-introspectable code, etc, I believe you should name particular concepts, otherwise you are doing an evil job.

                          Also In case you mean that - probably your post should be titled “Write intepreters, not compilers”?..

                        3. 1

                          If could possibly be implemented by allowing overloading branching on the if condition. Probably better if if itself is an expression.

                          The real problems are state changes and loops. Maybe if you statically rewrote loops into recursive calls, and just avoided mutation?

                          1. 1

                            Loops can be avoided cleanly - see my sibling comment about using foreach instead of loops.

                            State changes are fine if you’re doing those through methods that are part of an interface (rather than, say, directly reassigning a mutable variable to a different value)

                        4. 1

                          While I agree with the spirit, programmers have historically been very, very bad at assessing side-effects.