Tuesday, March 7, 2023

GTF - Great Teacher Friedman

author: wangyin

The old codger who writes small books

Dan Friedman is a professor at Indiana University and one of the founders of the programming language field. His main book, The Little Schemer (formerly The Little Lisper), is one of the most influential books on programming languages. Many of the oldest people in the programming language world learned Lisp/Scheme from this "little man's book" and then decided to enter the field.



Friedman's understanding of programming languages is arguably the highest standard in the world, but unfortunately, due to his low profile, he is misunderstood by many. Many people thought that he only knew Scheme, a "backward language with a backward type system". Some people think that he is only playing with himself and not "advanced", and that his research is not "cutting-edge" behind closed doors. I also misunderstood him, and even before we met, I concluded that he must be a young guy based on the covers of these books. As it turned out, when I first met him, he had already passed his 60th birthday.

Researchers in programming languages often chase after "new concepts" without realizing that many of these new concepts had been thought of by Friedman decades earlier. For example, Haskell's lazy evaluation model was first proposed in his 1976 paper "CONS should not Evaluate its Arguments", which he co-wrote with David Wise.

Although he wrote The Little Schemer, Friedman's knowledge was not limited to Scheme, and he continued to experiment with a variety of other language designs, including functional languages with static type systems like ML, logical languages, object-oriented languages, languages for theorem proving, and so on. After each experiment, he almost always wrote a book revealing the best parts of these languages.

People who think that ML is much more advanced than Scheme should check out Friedman's book, The Little MLer:

People who want to really understand Java design patterns can check out this book: A Little Java, A Few Patterns:

What do you know?

Unfortunately, Friedman never became my official mentor for personal reasons (he was "above and beyond" and almost completely unconcerned about when his students would graduate), but he really was the person who taught me the most in this life. So I wanted to write some short stories about him. Maybe you can get a glimpse of what a world-class educator looks like. Before I came to IU, a mentor told me that Dan Friedman was like Gandalf from Lord of the Rings, and I found out that he was.

When I first met Friedman in the office, he said to me, "Come on, tell me what you know." I proudly said, "I took a graduate programming language class at Cornell, I use ML and Haskell, I've read Paul Graham's On Lisp, Peter Norvig's Paradigms of Artificial Intelligence Programming, I've read Paul Graham's On Lisp, Peter Norvig's Paradigms of Artificial Intelligence Programming, some of Richard Gabriel's articles ......" He looked at me and smiled calmly: "That's good, you've got some foundation ...... "

It was only a few years later that I realized that his kind smile was hiding a secret that was hard to reveal: how naive I was at the time. It was only under this kind of guidance from him that I gradually understood that there is no end to the depth of knowledge. He was actually far above these people, but out of modesty, he couldn't bring himself to say this.

Reverse Run

Dan Friedman is well past retirement age, yet he continues to teach. His undergraduate programming language course, C311, is a "starred course" at IU. What I admire most about him is his childlike curiosity and spirit of discovery. Almost every year of C311, he would invent something different to enrich the course. Sometimes it was a new logic programming language (called miniKanren), sometimes it was a little trick (like compiling Scheme into C without stack overflows), and so on.

Friedman has always been dedicated and obsessively passionate when working on something. Since he started designing a logic programming language called miniKanren, Friedman has developed a mantra: "Does it run backwards? Because programs in logical languages (like Prolog) can "run backwards". A program written in a normal programming language will give you an output if you give it an input. But logical languages are special in that if you give it an output, it can run backwards, giving you all possible inputs. But Friedman really went off the rails. No matter what someone was talking about, he would often end up asking, "Does it run backwards?" and making you laugh.

Friedman has a "weakness" that everyone in his field knows - he doesn't like static type systems. In fact, most Scheme experts do not like static type systems. For this reason, he was misunderstood and even despised by the "type experts", but he took it all in stride.

Once in his advanced course B621, he gave us a problem: implementing the Hindley-Milner type system used by ML and Haskell in Scheme. This type system generally works by feeding a program, and it outputs a type after type inference of the program. If there is a type error in the program, it will report an error. Having implemented this in ML at Cornell before, and having a better understanding of abstract interpretation since coming to IU, I was able to make this very quickly and more elegantly than I had done at Cornell.

He was so happy to know that I had done it that he asked me to tell the class (just 8 or 9 people) what I had done. When I finished proudly, he asked, "Does it run backwards? If I give it a type, can it automatically generate a program that matches that type?" I froze and cried, "No. ......" He slumped back on the couch and smiled triumphantly, "My system can! Ha ha! I wrote that type of system back then is shorter than you this. I already knew how to do these type systems, but I just didn't like it. Hahahahaha ......"

My further research into type systems later revealed that the Hindley-Milner type system did have a lot of unnecessary problems that caused him to dislike it. He's such a crotchety old man. He likes to put you up and then knock you down to show you that the sky is the limit :-)

miniCoq

You'll never imagine the limits of Dan Friedman's thinking. Just when you thought he was a functional language expert, he designed miniKanren, a logical programming language, and wrote books like The Reasoned Schemer to teach logical programming. Just when you think he doesn't understand type systems, he starts tinkering with the then-hottest Martin-Löf type theory and starts designing machine proof systems. And he did all this out of pure interest. He never cared how far others had gone in this direction, but often managed to simplify others' designs in unexpected ways.

Once the department held a "lightening talk" for professors, and each professor had only five minutes to present his or her research. When Friedman's turn came, he walked up slowly and calmly and said, "I'm not in a hurry. I only have a few words to say. I don't know if I can drag it out for 5 minutes ......" Everyone laughed. He went on to say, "My favorite things right now are Curry-Howard correspondence and theorem proving. I think the current machine proof system is too complex, for example, Coq has nnnnn lines of code. I want to simplify Coq and get a miniCoq ...... in x years."

miniCoq... the whole room laughed when they heard this word. Why? Think about it yourself. Since then, "Dan Friedman's miniCoq" has been the running joke among programming language students at IU.

But Firedman doesn't brag. He always does what he says he will do. He has written a simple theorem proving tool called JBob (not miniCoq due to public pressure), and is writing a book called The Little Prover to teach the most important theorem proving ideas. He started teaching them to undergraduates on C311. I read the first draft of that book and benefited greatly from the best ideas that are not covered in many Coq textbooks. It taught me not only how to use a theorem proving system, but how to design one. I said to him, "You always have something new to teach us. Every two years, we have to take your class all over again!"

C311

When I first transferred from Cornell to IU, Dan Friedman asked me to take his graduate programming language class, B521, and I tried not to take his class on the grounds that I had taken programming language classes at Cornell. I know you have taken this kind of class at Cornell. I also know that Cornell is a much better school than IU. But every teacher's teaching method is different, and you should come to my class. My friends and I are professors here, not because we like the school, but because our families and friends are here." Later, because of the overlap with Amr Sabry's (my current advisor) course B522, he made a special arrangement for me to sit in the undergraduate C311 class but take credit for the graduate course. It turned out that there was basically no difference in the content of the two courses, except that the graduate students had more assignments.

In the first class, he said something that I still remember: "The Little Schemer and Essentials of Programming Languages are the reference textbooks for this class, but I never talk about what's in my book in class." Right off the bat, I realized that the class was very different from what I had learned at Cornell. Although there were concepts like closure and CPS that I had learned at Cornell, in his class I saw a completely different side of those concepts, so much so that I felt like I didn't understand them at all! This is because I learned them at Cornell for homework, but in Friedman's class, I used them to accomplish real-world goals, so I really got to appreciate the meaning and value of these concepts.

An example is that a few weeks into the course, we started writing an interpreter to execute a simple Scheme program. Then we transformed this interpreter into a CPS, introducing global variables as "registers" and converting the continuation generated by the CPS into a data structure (i.e. a stack). What we end up with is an abstract machine, which is essentially the equivalent of a CPU or a virtual machine (like a JVM) in a real machine. So we actually "invented" the CPU from scratch! From here, I really understood the nature of registers, stacks, etc., and why we need them. From here, I really understood the nature of registers, stacks, etc. and why we need them. I really understood why the von Neumann architecture was designed the way it was. Then he sent us to a paper by his good friend Olivier Danvy on how to derive different kinds of abstract machine models from different interpreters via CPS transformations. This was the first time I felt the immense power of the theory of programming languages for the real world, and it made me understand that machines are not the essence of computation. Machines can be implemented with any feasible technology, such as integrated circuits, lasers, molecules, DNA ...... But no matter what is used as the material for the machine, the semantics we want to express, the essence of computation, remains the same.

And that's not all my C311 class was about. In the second half of the semester, we started learning miniKanren, a logic programming language that he had designed for teaching purposes. This language is similar to Prolog, but it takes away many of the shortcomings of Prolog and makes it much easier to understand. The textbook is "The Reasoned Schemer" which was given to us for free. At the end of the book, two pages long, is the entire miniKanren language implementation! I was a quick learner and then started tinkering with this implementation, redesigning some parts and then adding some features I wanted. Teaching like this gave me the ability to design a logical language and not just stay a user. This is something that is impossible to do when learning Prolog, because the complexity of Prolog can leave a beginner with no way to get started and stuck in the user stage.

I'm lucky I listened to him and took the class, otherwise I wouldn't be the person I am today.

Independent thinking

Dan Friedman is an independent thinker who doesn't follow the crowd. He likes to simplify something to fit into a few lines of procedure and to understand the issues involved very clearly. His book is a unique "question-and-answer" structure, much like the way Confucius or Socrates taught. His teaching style is also very unique. This is already evident in the undergraduate course C311, but it is only in the graduate course B621 that it all comes out.

One of the most satisfying programs I have written, Automatic CPS Transformation, was generated in C311. In the C311 assignments, Friedman often included some "brain teaser" questions that could be scored. Since I already had some basic knowledge, I had the energy to do those puzzles. The first questions weren't too hard until I started learning CPS, when one of them came up: "Write a program called CPSer, which automatically converts Scheme programs into CPS form." All the other questions in that assignment asked for a "manual" conversion of a program into CPS form, but this intellectual question asked for an "automatic" one - a program to convert another program. The other questions on that assignment required "manual" conversion of programs into CPS form.

I find it very interesting. If I could write an automatic CPS conversion program, then I could use it for all other topics! So I started tinkering with this thing, and the initial idea was to "simulate" a manual conversion process. Then I realized that it was a monster, just a few dozen lines of program, either here or there something was wrong. Pressing a bug here and there popped up again, never seen anything so troublesome. I've been fighting with it for almost a week, forgetting to eat and sleep. Often into a dead end, only to start over, I do not know how many times overturned and restarted. By the time the assignment was due, I had it out of the way. I ended up using it to generate all the other answers, and the resulting CPS code was indistinguishable from the manually converted one. Of course I got a perfect score again this time (because every time I did the intelligence questions, I always scored above 100).

I was walking back to Lindley Hall (the IU Computer Science building) with Friedman after class on the day the assignments were given out. On the way, he asked me, "Did you do the brain teaser?" I said, "Yes, I did. It's a good thing. It helped me get all my other work done." He was a little surprised and a little skeptical: "Are you sure you got it right?" I said, "Not sure it's exactly right, but the converted homework program all looks like it was done by hand." After walking back to the office, he gave me a 30-page paper, "Representing control: a study of the CPS transformation," by his good friends Olivier Danvy and Andrzej Filinski. I then learned that these are two of the most powerful people in this field. It was this paper that solved the problem that had been pending for more than a decade. In fact, automatic CPS transformations can be used to implement efficient compilers for functional languages, and Andrew Appel, a famous professor at Princeton University, wrote a book called "Compiling with Continuations", which is dedicated to this problem, and Amr Sabry's (my current advisor) PhD thesis was a Amr Sabry's (my current supervisor) PhD thesis was a transformation (called ANF) that was simpler than CPS. With this, he almost wiped out the whole CPS field and got a tenure-track professorship. Within 10 years of his dissertation, no CPS paper appeared.

Friedman, you're so good at making a question like that an "intelligence question"! I jokingly told him, "I promise, I won't open source this program, or your C311 students will be able to use it to cheat." When I got home, I started reading the paper by Danvy and Filinski. The idea of this 1991 paper was based on a tedious derivation of a 1975 paper by Gordon Plotkin, and it ended up being almost identical to my program, except that my program could handle more complex schemes than just lambda calculus. I was completely unaware of Plotkin's approach, and thus was completely uninfluenced by his ideas, and got the best results straight away. This was the first time I realized the power of my own mind.

The next semester, when I went to Friedman's advanced course B621, he gave us the same questions. After two weeks, no one else really got it right. At the end he said to the class, "Now I'd like Wang Yin to tell you how he did it. You have to listen carefully. This program is worth $100!"

Here is a reduced version of my program for the lambda calculus. I never thought that it would take many people 10 years to figure out just 30 lines of code.

(define cps
  (lambda (exp)
    (letrec
        ([trivs '(zero? add1 sub1)]
         [id (lambda (v) v)]
         [C~ (lambda (v) `(k ,v))]
         [fv (let ((n -1))
               (lambda ()
                 (set! n (+ 1 n))
                 (string->symbol (string-append "v" (number->string n)))))]
         [cps1
          (lambda (exp C)
            (pmatch exp
              [,x (guard (not (pair? x))) (C x)]
              [(lambda (,x) ,body)
               (C `(lambda (,x k) ,(cps1 body C~)))]
              [(,rator ,rand)
               (cps1 rator
                     (lambda (r)
                       (cps1 rand
                             (lambda (d)
                               (cond
                                [(memq r trivs)
                                 (C `(,r ,d))]
                                [(eq? C C~)         ; tail call
                                 `(,r ,d k)]
                                [else
                                 (let ([v* (fv)])
                                   `(,r ,d (lambda (,v*) ,(C v*))))])))))]))])
      (cps1 exp id))))

And that's not all there is to B621. Every week Friedman writes a difficult topic on the board. He doesn't let you read books or papers. He sometimes doesn't even tell you the names of the concepts in the topics, or gives them new names so you can't even look them up. He requires you to solve these problems completely on your own and explain them to the rest of the class on the board the next week. He has no clear grading scale, so you feel absolutely no pressure to get a grade.

The questions included some difficult ones, such as the predecessor of church numeral. This problem took Stephen Kleene (Turing's senior) three months to figure out. Unfortunately, I learned Kleene's approach at Cornell, and it created a mindset that made this exercise meaningless to me at the time. Instead, there was a math student in my class who, surprisingly, made an even simpler method than Kleene's within a week. His complete code (expressed in traditional lambda calculus syntax) was as follows. 

λn w z. ((n λl h. h (l w)) (λd.z)) (λx.x)

Other issues included compilers from lambda calculus to SKI combinator, logical (reversible) CPS transformations, implementing the Hindley-Milner type system, and more. I found that even when I thought I understood something, after some thought, it was possible to go even further.

Of course, reinventing things didn't get me published, but it gave me something more important, which is the ability to think independently. Once you "think" something up, rather than "learn" it from someone else, then you know how the idea came about. This is much more useful than learning the idea directly, because you know all the details and mistakes made in it. And the most important thing is the intuition you get from it. If you go directly to someone else's book or paper, you will hardly get this intuition, because people usually write papers to bury their intuition under a bunch of symbolic formulas, so that you can't see the real idea behind it. If you get the intuition, the next time you encounter a similar problem, you may quickly use the existing intuition to solve a new problem.

And all this has happened to me. For example, after hearing about ANF, I got the ANF transformation without reading Amr Sabry's paper and only changed my original CPSer program a little bit, and the whole process took only ten minutes. And in R. Kent Dybvig's compiler course, I used the intuition inside the CPS transformations to modify and merge several passes of the compiler framework provided by Dybvig, making them several times shorter and generating better code.

I still do, and like to deliberately reinvent things and explore more than one area. This gives me intuition, is no longer limited by other people's ideas, saves time reading papers, and is a bit more fun. A problem, when I believe I can figure it out, can usually be solved. Although I often don't take the things I make with my head to heart, calling them "reinventions," surprisingly, I have recently discovered that there are some real inventions hidden in them. I am going to slowly uncover some of these ideas and publish them as papers or products.

As the saying goes, "It's better to give a man a fish than to teach him to fish." Dan Friedman, thank you for teaching me how to fish. 


Monday, March 6, 2023

A comprehensive evaluation of the Go language

author: wangyin

I've written some negative comments about the Go language before. In hindsight, while most of those reviews were true, they were not convincing to some people because they were so vocal and did not point out specific problems. After a few months of actually using Go to build websites, I think it's time to give it a more "objective" review.

Positioning and advantages

Go does have its advantages over C and C++, that's obvious. It also has a few advantages over Java, but it's relatively more of a shortcoming. So my preference for Go is a little bit lower than Java.

The strengths of the Go language over C and C++ are of course its simplicity and garbage collection. Since C and C++ are designed with a lot of historical legacy issues, Go does seem more elegant and simple. The code in Go also seems to be simpler than Java code that makes heavy use of design patterns. Also, Go's garbage collection mechanism is much less taxing on the programmer's mind than the full manual memory management of C and C++.

Note, however, that these "advantages" are relative to languages like C. If compared to some other languages, Go's advantages may be insignificant, or even a step backwards in history.

Syntax

Go's simplicity is reflected in some aspects of its syntax and semantics, Go's syntax is slightly better than C and has a few more convenient designs than Java, but there are also "regressions". And these regressions are not seen by many as regressions, but rather as progress. I will now list a few that come to mind at the moment.

Progress: Go has syntax support for a struct literal-like construct, for example, you can write code to construct an S struct like this:

S { x: 1, y: 2, }

This is a nice convenience improvement over Java's ability to create objects only with constructors. These things may be borrowed from the design of languages like JavaScript.

Backwards: the type is placed after the variable without the separator. If the variable and its type were written like Pascal, e.g. x : int, that might be fine. Go, however, writes x int without the colon and allows writing like x, y int. This syntax, when combined with var, the function argument, has a disruptive effect. For example, you can write a function that starts like this:
func foo(s string, x, y, z int, c bool) {
  ...
}
Note the position of x, y, z. It's actually quite confusing. Because when I see x, I can't immediately see what type it is from the symbol (, y) that follows it. So the way I recommend writing it in Go is to keep x and y completely separate, like in C and Java, but with the type written after it: x, y, z.
func foo(s string, x int, y int, z int, c bool) {
    ...
}

This makes it clearer, although I'm willing to write more colons. Each argument is in "name type" format, so I can see at a glance that x is int. It's a few more words, but it saves the overhead of "eyeball parse code".

Backwards: type syntax. go uses syntax like []string to represent types. Many people say that this syntax is very "consistent", but after some time I have not found what they call consistency. It's actually hard to read because there is no clear separator between the parts of the type, and when paired with other symbols like *, you need to know some precedence rules and then go through a lot of trouble to do the "eyeball parse". For example, you often see types like []*Struct in Go code, and note that *Struct has to be combined before it can be used as a "type parameter" for []. This syntax lacks enough separators to act as "boundary signals" for reading, which makes it hard to read once the types that follow become complex. For example, you can have types like *[]*Struct or *[]*pkg. So it's not really as clear and simple as writing it like C++'s vector<struct*>, and even less clear and simple than Java's or Typed Racket's type writing.

Backwards: excessive "syntax overloading" with keywords like switch, for, etc. The switch keyword in Go actually contains two different things. It can be a normal switch (Scheme's case) in C, or it can be a nested branching statement like Scheme's cond. These two statements are actually semantically completely different, but Go's designers have combined them to make it look simple, which actually causes more confusion. This is because, even if you combine them, they are still two different semantic constructs. The result of combining them is that every time you see a switch you need to distinguish the two different structures from each other by the difference in their "heads", adding to the overhead of the human brain. The right way to do it is to separate them, like Scheme does. In fact, I sometimes make the same mistake when I design languages, thinking that the two things are "essentially" the same, so I combine them into one, only to find out after a while that they are actually different. So don't underestimate Scheme, many things you think are "new ideas" have actually been abandoned by its very strict committee in the long history.

There are other syntax design issues in Go, such as forcing { after a line and not allowing line breaks, nesting assignments at the beginning of if statements, and so on. These attempts to make the program look short actually reduce the fluency of the program.

So all in all, Go's syntax can hardly be called "simple" or "elegant", it's actually below Java in simplicity.

Tool Chain

Go provides some convenient tools. For example, gofmt, godef, etc., make Go code programming an improvement over editing C and C++ with Emacs or VIM alone. Using Emacs to edit Go already enables some IDE features, such as precise definition of jumps, etc.

These tools are nice to use, but they are quite different from IDEs like Eclipse, IntelliJ and Visual Studio. Compared to IDEs, Go's toolchain lacks various basic features, such as listing all the locations where a variable is referenced, renaming and other refactor features, a good debugger (GDB is not so good), and so on.

Go's tools don't feel very mature, and sometimes you find that there are several different packages for the same problem, and it's hard to figure out which one is better. And they are not so reliable and easy to configure, they need to be tossed around. Every little feature you have to find a package from everywhere to configure. Sometimes a tool doesn't work after it's been configured, and you have to wait a long time to find out what the problem is. This kind of unorganized and unplanned tool design is hard to surpass the consistency of professional IDE vendors.

Go provides a convenient package mechanism for importing Go code directly from a GitHub repository. But I've found that often this package mechanism is more of a hassle and a dependency. So Go advocates have designed tools like godep to get around these problems, and godep itself has caused some odd problems, causing new code to not actually compile and generating inexplicable error messages (probably due to a bug in godep).

I find that a lot of people who see these tools are always very enthusiastic that they will make the Go language dominant, but they are still very far from it. And with so many problems with such a young language, I think all these troubles will add up to a lot of trouble over the years.

Memory Management

Go has a garbage collection (GC) mechanism compared to the completely manual memory management approach of C and C++. This mechanism greatly reduces the burden on the programmer's mind and the chance of program errors, so Go is an improvement over C/C++.

Go's garbage collector is a very primitive mark-and-sweep, which is still in its infancy compared to language implementations such as Java, OCaml, and Chez Scheme.

Of course if you do run into GC performance problems, you can partially improve the efficiency of memory collection by doing a lot of tuning. I've seen articles written about how they do this, but the existence of such articles shows that Go's garbage collection is still very immature, and GC is something I don't think should be left to the programmer most of the time, otherwise it loses a lot of its advantages over manual management. So Go code still has a long way to go if it wants to be in a more real-time situation.

The Go language is becoming more and more ambiguous to me because of the lack of advanced GC with high level abstraction, so Go can't really replace C and C++ for constructing the underlying system.

No "generics"

Go lacks generics compared to C++ and Java, and while some people hate Java's generics, it's not a bad thing in itself. generics is actually what's called parametric polymorphism in functional languages like Haskell, and it's a very useful thing, but it's been copied by Java and sometimes doesn't do everything right. It's a very useful thing, but it's been copied from Java and sometimes not done all right. Because generics allows you to use the same piece of code to handle many different data types, it provides a convenient way to avoid duplication, replace complex data structures, etc.

Since Go doesn't have generics, you have to write many functions over and over again, each with a different type. Or you could use the empty interface {}, which is really the C equivalent of the void* pointer. With it, the type of the code cannot be checked statically, so it is not as rigorous as generics.

Compared to Java, many of Go's data structures are "hard code" into the language, even creating special keywords and syntax to construct them (such as hash tables). Once you need to define similar data structures yourself, you need to rewrite a lot of code. And since there is nothing like Java collections, there is no easy way to swap out complex data structures. This is a big obstacle for constructing programs like PySonar that require a lot of experimentation to choose the right data structure and need to implement special hash tables and other data structures.

The lack of generics is one problem, but a more serious problem is the blind rejection of such language features by Go's designers and its community. When you mention them, Go supporters will tell you with disdain, "I don't see the point of generics!" This attitude is more harmful than the shortcomings of the language itself. After a long time the designers of Go began to think about adding generics, and then because Go's syntax was designed to cut corners, and because the exceptions created by the lack of generics (such as Go's map syntax design) are already heavily used, I think it has become very difficult to add generics.

Go, like Unix, has been burdened with a heavy history of not learning from its predecessors in its early days.

Multiple Return Values

Many people think that Go's multi-return-value design is an improvement, but there's a lot of fishy stuff going on here. Not to mention that it's not new at all (Scheme has had let-values for a long time), Go's multi-return values are used in a lot of wrong places - Go uses them to represent error messages. For example, the most common structure in Go code is:
ret, err := foo(x, y, z)
if err != nil {
    return err
}

If the call to foo produces an error, then err is not nil. go requires you to use it after defining the variable, otherwise it reports an error. This way it "happens" to avoid the case where an error err occurs and is not checked. Otherwise, if you want to ignore errors, you have to write:

ret, _ := foo(x, y, z)

This way, when foo goes wrong, the program will automatically pawn off at that position.

I have to say that this "misrepresentation", while seemingly feasible, is very uncritical from a type system perspective. It is not designed for this purpose at all, so you can easily find ways to make it fail. And since the compiler only checks to see if err is "used", it doesn't check to see if you've checked "all" possible error types. For example, if foo may return two errors, Error1 and Error2, you can't guarantee that the caller has completely eliminated the possibility of these two errors before using the data. So this error checking mechanism is not as rigorous as Java's exceptions.

Also, ret and err are defined at the same time, and only one of them is not nil at a time. This "or" relationship is not guaranteed by the compiler, but by programmer's "convention". These combinations create quite a bit of confusion, making you unsure whether return is intended to return an error or a valid value every time you see it. If you realize that this "or" relationship actually means that you should only use one return value for them, you know that Go is actually misusing multiple return values to represent possible errors.

In fact, if a language has a "union type" type system like Typed Racket and PySonar support, this multiple return value doesn't make sense. Because with a union type, you can use only one return value to represent valid data or errors. For example, you could write a type called {String, FileNotFound} to indicate that a value is either a String or a FileNotFound error. If a function is likely to return an error, the compiler forces the programmer to check for all possible errors before using the data, thus avoiding all of the above confusion. For those interested in union type, you can check out Typed Racket, which has the most powerful type system I've seen to date (beyond Haskell).

So it's safe to say that Go's multiple return values are actually "mistyping" half of what you hit, and then continuing to mistype instead of aiming for the bullseye.

Interface

Go uses an object-oriented design based on interfaces, which you can use to express concepts that you want to abstract.

However, this interface design is not without its problems. For one thing, unlike Java, implementing a Go interface does not require explicit declarations (implementations), so you may "happen" to implement an interface. This uncertainty is counterproductive to understanding the program. Sometimes you modify a function and find that it doesn't compile, complaining that some location is not passing the required interface, but the error message doesn't tell you exactly why. It takes a bit of poking around to find out why your struct no longer implements an interface that was previously defined.

Also, some people use interfaces, many times just to pass some function as an argument. I sometimes don't understand why something that is so simple for a functional language has to be implemented in Go by defining a separate interface. This makes the program less clear than a functional language, and it's also very inconvenient to modify. There are a lot of redundant names to define and redundant work to do.

A related example is Go's Sort function. Every time you need to sort an array of some type T, like []string, you need
  1. Define another type, usually called TSorter, such as StringSorter
  2. Define three methods for this StringSorter type, called Len, Swap, Less
  3. Cast your type like []string to StringSorter
  4. Call sort.Sort to sort this array
Think about how simple sort is in a functional language. For example, Scheme and OCaml can both be written directly like this:
(sort '(3 4 1 2) <)
Here Scheme passes the function < directly to the sort function as an argument, without wrapping it in any interface. Did you notice that the three methods in Go's interface are supposed to be passed directly to Sort as three arguments, but due to design pattern and other limitations, Go's designers "packaged" them as an interface? And since Go doesn't have generics, you can't write these three functions like a functional language, accepting the "elements" of the comparison as arguments, but must use their "subscripts". Since these methods only take subscripts as arguments, Sort can only sort arrays. Also, since Go is designed to be more "low-level", you need two other arguments: len and swap.

In fact, this interface-based design is a far cry from functional languages. It's also a step backwards from Java's interface design.

goroutine

Goroutine is arguably the most important feature of Go. Many people use Go because they hear that goroutine can support so-called "big concurrency".

First of all, this kind of concurrency is nothing new. Everyone who understands programming language theory knows that goroutines are really just user-level "continuations". System-level continuations are often called "processes" or "threads", and continuations are something that functional language experts know all too well, such as my former mentor Amr Sabry, who was one of the top experts on My former mentor Amr Sabry was one of the top experts on continuation.

The Node.js kind of "callback hell" is actually a common technique within functional languages called continuation passing style (CPS). Since Scheme has call/cc, it can theoretically achieve large concurrency without the CPS style code. So as long as functional languages support continuation, it is easy to achieve large concurrency, and perhaps even more efficient and useful. For example, an implementation of Scheme, Gambit-C, could be used to implement something with large concurrency, and Chez Scheme might be possible, but that remains to be confirmed.

Of course there may be differences in efficiency, but I'm just saying that goroutines aren't really as new, revolutionary, or unique as many people think. Other languages can add it if they have enough motivation.

defer

Go implements a defer function to avoid forgetting to cleanup after a function error. However, I've found that this defer function has a tendency to be abused. For example, some people make defers for actions that are not cleanup, and after a few defers have accumulated, you can no longer tell at a glance which piece of code runs first and which runs second. The position in the front of the code can actually run later, violating the natural position of the code order relationship.

Of course you can blame the programmer for not understanding the true purpose of defer, but once you have something like this in place, someone will want to abuse it. People who are eager to try to take advantage of every feature of a language are particularly fond of doing this sort of thing. I'm afraid it will take many years of experience before someone writes a book to educate people about this problem. Until there is a unified "code specification", I predict that defer will still be heavily abused.

So we should think about whether defer has more advantages or disadvantages to avoid possible resource leaks.

Library Code

The design of Go's standard library has a strong Unix flavor in it. The library code has a lot of inconveniences compared to Java and other languages. Sometimes it introduces functional language approaches, but due to the limitations of Unix thinking, not only does it fail to take advantage of the benefits of functional languages, but it also leads to a lot of complexity in understanding them.

One example is the way Go handles strings. In Java, each character in a string is by default a Unicode "code point". However, in Go, each element of the string type is a byte, so you have to cast it to a "rune" type each time to properly traverse each character and cast back. This way of thinking about everything as a byte is the Unix way of thinking, and it causes overly primitive and complex code.

HTML template library

I have used Go's template library to generate some pages. It's a "mostly usable" way of templating, but it's quite inadequate compared to many other mature technologies. I was surprised to learn that the code enclosed in Go's template is not the Go language itself, but a rather weak language, sort of a degenerate Lisp, except that the parentheses are replaced with things like { {...} }.

For example, you could write a web template like this:
{{define "Contents"}}
{{if .Paragraph.Length}}
<p>{{.Paragraph.Content}}</p>
{{end}}
{{end}}

Since each template accepts a struct as populated data, you could use code like .Paragraph.Content, however this is not only ugly, but makes the template inflexible and poorly understood. You need to put all the data you need into the same structure to access it from inside the template.

Anything longer than one line of code, while perhaps expressible in this language, is generally avoided by writing some "helper functions" in the .go file to avoid the weaknesses of the language. Use them to generate data to put into a structure and then pass it to the template to be able to express some of the information that the template needs. Each of these helper functions requires a certain amount of "registration" information to be found by the template library. So all this complexity adds up to make Go's HTML template code quite cumbersome and confusing.

I've heard that someone is working on a new HTML template system that supports direct Go code embedding. These efforts are just getting started, and it's hard to say what they'll end up being. So to make a website, I'm afraid it's best to use a more mature framework in another language. 

Summary

Elegance and simplicity are both relative. While Go surpasses C and C++ in many respects, and is better than Java in some respects, it does not compare to the elegance of Python, which is inferior to Scheme and Haskell in many respects, so all in all, Go's simplicity and elegance are on the lower end of the spectrum.

Since there are no obvious advantages, but there are various problems that are not found in other languages, I prefer to use a language like Java for practical projects at the moment. I don't feel that Go and its toolchain can help me write sophisticated code as fast as PySonar. I've also heard of people using Java for large concurrency and don't see any significant shortcomings compared to Go.

Alan Perlis says that language design should not be about piling up features, but about trying to reduce weaknesses. From this perspective, the Go language introduces one or two new features, while introducing a fair number of weaknesses.

Go may temporarily have particular strengths in some individual cases that can be used alone to optimize certain parts of a system, but I would not recommend using Go to implement complex algorithms and entire systems.


Sunday, March 5, 2023

The Importance of Design

author: wangyin
time: 2015-3-17

I once talked about design in an article that many people may not have paid attention to because the title was not prominent enough. I feel the need to bring it up now, because design has a crucial place in my mind, but is belittled by many computer scientists and programmers.

I consider myself not only a computer scientist and programmer, but to a large extent I am also a designer. I am not only a designer of programming languages, but a designer of many other things. Not only do my designs often work better than other people's, but I often see the problems in other people's designs directly. I write code that is not only easy for me to read, but also easy for others to understand. Sometimes I've been tasked with fixing bugs in the previous person's code, and I can't understand them. In such cases, my solution is to overturn and rewrite. After I rewrite the code, it is not only bug-free, but also much cleaner.

Many people have their own design problems, too complex and not easy to use, but in the end, the responsibility is put on the user, using techniques similar to "the emperor's new clothes", so that the user can not say. The serious traffic accident mentioned in a previous article is a design problem, but many people attribute it to "human error". It's so hard to draw attention to design when people are killed, not to mention the annoyances of the software industry that have nothing to do with lives. Some people write overly complex code with many bugs, yet they seem to think they can assess the intelligence of others, and feel in their hearts that they are experts and that anyone who can't read their code is a fool.

Many programmers intentionally distinguish between "users" and themselves, as if programmers should be superior to others, not by the standards of users. So they think that programmers should be able to use all kinds of difficult tools, difficult operating systems, programming languages, editors, ...... They think that as long as you pursue these things "ease of use" or "intuitive They think that as long as you are looking for the "ease of use" or "intuitiveness" of these things, it means you have an IQ problem. As long as you say something is too complicated and another thing is better, they will tell you, "Only experts use this, you use that for rookies." These people don't understand that programmers are actually users too, and they are users of their own code, every time they call a function they wrote, they are their own users. But the winning line of this culture of contempt for users has brought about a situation where the entire industry is not only overly complex in design, but also proud of its complexity.

Often people proudly claim how many thousands of lines of code their projects have, as if the number of lines of code is the measure of a software's quality, and the more lines the better the quality, yet the opposite is true. As the author of The Little Prince said, "A designer knows he has reached perfection, not when he can add nothing more to it, but when nothing can be removed."

If you care about design as much as I do, but find that the people around you like to show that they can figure out the complicated stuff and tell you that the easy stuff is for rookies, then you need a friend. A book is man's best friend because its author can reach across time and space to give you the support and encouragement you need most. That's how I felt when I read this book, The Design of Everyday Things (DOET for short), published in 1988. I thought, finally, someone understands me! Interestingly, its author, Don Norman, was once an Apple Fellow and author of the foreword to the book The Unix-Haters Handbook.


DOET not only contains and supports the basic ideas in my blog posts "The Essence of Hacker Culture" and "Programming Languages and ......", but also offers a more incisive and workable solution than "What is "User Friendly"?

I think this should be a must-read book for every programmer. Why is it a must-read for every programmer? Because although this book is a must-read for design professionals, computers and their programming languages and tools are actually the "hardest hit" by the lack of design thinking, as the author points out. After reading it, you will find that many so-called "human errors" are actually caused by the poor design of the tools. A well-designed tool should require very little or no documentation. This book will provide you with the principles and inspiration to improve everything. You will recover your humanity.

It is worth mentioning that although Don Norman used to be an Apple Fellow, I think there is still a gap between the degree of user-friendliness of Apple product design and the height of Uncle Norman's thinking. Because after I read this book, I immediately found some design problems of iPhone.

If you're like me and don't want to read with your eyes, you can buy an audiobook from Audible to listen to.

Saturday, March 4, 2023

It's time to start a 3d engine project designed for mobile devices

author: cloudwu
time: 2017-12-18

First, the company we started in late 2011 was wholly acquired by Alibaba Culture and Entertainment Group. The full team of the original Jinyue was transformed into the Alibaba Entertainment Game Business Group.

When the dust of the acquisition settled, I found that I could look at the future from a new perspective and re-design a 3d engine to start again. In Jane Yue has always wanted to do but can not do this thing, because there is no spare capacity, must give priority to the product profitability; and for Ali, to invest resources to do such a short-term no revenue, but in the long run seems to be very meaningful thing is natural.

There are already many excellent 3d game engines in the world, such as the most popular Unity and the excellent reputation of Unreal, and many good quality open-source engine, and what is the point of making another one from scratch?

Here's how I look at it.

Unity and Unreal are great, but they weren't designed with mobile devices in mind as a core platform at the beginning. They have a long history of development, and while the details are certainly perfected in a way that later generations cannot match, they also have a lot of historical baggage. Especially on the mobile platform requires special consideration for memory tightness and energy savings, more than running faster and more gorgeous effects.

In addition, in terms of national conditions, we need mobile games that require more flexible resource management and update solutions, which has been Unity's weakness. unity as a closed source engine, it is difficult for users to make fundamental improvements.

We have partnered with Unity and purchased all the source code. Now we also have a dedicated team to maintain the Unity source code and support other product teams. In this case, it doesn't make sense to copy a new Unity: we can develop on top of the Unity source code if there is a need. So I wanted something completely new.

My vision for this new engine, which I have already started, is as follows.
  1. As soon as possible based on MIT open source protocol development, including the engine's runtime part and the entire tool chain. The closed source approach is not sustainable, and the benefits of open source have been well documented in the skynet project. In addition, since we have a cooperation with Unity and a non-disclosure agreement, we can open source as soon as possible and keep the development in open source mode: we will not use a single line of Unity's source code.
  2. Optimized for mobile devices, the rendering part will be based on Opengl ES 3.0, facing the market after 2 years. Increasing hardware requirements will streamline a lot of unnecessary designs, just as engines 10 years ago would have had both fixed and programmable pipelines, but now no engine is considering fixed rendering pipelines anymore. Putting a lower limit on Opengl ES 3.0, we can safely use uniform ETC2 mapping (without considering the Apple-specific pvr format), use Instance to render multiple objects, use MRT technology to do deferred rendering of lighting models, and so on.
  3. The big difference with engines like Unity is that I start with the engine runtime and editor designed as a C/S structure, i.e. the editor and the project are running in different locations. During development, developers are required to run the project on the real machine, so that the real mobile device becomes a real second display window, instead of developing on the PC like Unity, and only uploading the package to the device when necessary. In this way, developers are naturally always paying attention to how the game runs on the real device, whether it heats up badly, whether the frame rate is enough, whether there is not enough memory, whether the operation is reasonable, etc. throughout the development process. At any time, it is easy and fast to plug and unplug different hardware devices to do testing, eliminating the complicated packaging and uploading process.
  4. Emphasize the ease of use of the tool. Imitate Unity's component mechanism, but do not copy it. Use ECS structure to build the basic framework of the engine.
In terms of implementation, I made some technical choices.
  1. Use bgfx for the underlying rendering layer instead of developing it myself from scratch. Because this is a particularly dirty job that requires someone with enough experience and ability to maintain it. The game engine doesn't need much performance in the rendering layer, so it's not appropriate to focus on that. bgfx has spent a long time doing multi-platform multi-api integration, and the author has long years of game development experience, so it's very trustworthy.
  2. The whole framework is built on lua, only in the very high performance part of C/C++ to package into a cohesive library for Lua calls. No C/C++ is needed to implement any framework code. bgfx has a perfect C99 interface that can be perfectly wrapped for lua calls, and I've already done almost all of that. For the animation module, I chose the ozz-animation library, which is also highly cohesive and can be used in lua with a little encapsulation. This principle will be maintained for the rest of the development.
  3. The editor development is based on iup, a gui framework that can drive native UI controls, which is very easy for Lua programmers to use. Although the native controls are sometimes less aesthetically pleasing, I believe that aesthetics are less important for a good editor.
  4. The editor and the game project communicate based on a custom simple protocol. Essentially it is a pure engine app running on a mobile device without any resources or business code, taking over the underlying IO operations, mapped to the development machine. When the app reads the script at runtime, it actually reads the code on the development machine via usb or wifi; the same is true for resource loading. As long as the cache synchronization mechanism is done well, there is almost no difference between running the app and running the resources locally. The input device is also the mouse and keyboard of the development machine mapped to the mobile device through the protocol, so we don't need to tap the screen of the phone while developing. We can also implement some debugging function interface for the game project and put it on the development machine directly, which is much more comfortable to use than making a debugging console on the phone.
Before open source, I will gradually introduce the completed work on the blog, and open the full source code on github when the prototype of the engine is ready to use.

Good games don't ask for age

author: cloudwu
source: https://blog.codingnow.com/2008/06/xcomufo.html
time: 2008-6-4

Yesterday, I was talking to a colleague about some old games. I couldn't resist searching a handful of them and found UFO : Enemy Unknown that I had forgotten to play when I was in high school. That's the story of 94 years.

I was introduced to the game by accident, no one had introduced me to it, no magazine had mentioned it, and I didn't know that MicroProse, founded by Sid Meier, had released such a big game as Civilization. To be honest, I didn't find Civilization very interesting at all.

As with the first time I came across Immortal Sword and Sorcerer, I just got the game on a CD, saw it was just released, and tried to install it. Since it's all in English, there are also some sci-fi colorful words, so you need to hold the dictionary to play. The good thing about the game is that a few years later, when I took the CET-4 exam, there was a central word in the reading comprehension that was craft aircraft, and I looked very familiar with it, and then I passed the exam.

Today, you can find MicroProse's Golden Edition for the windows version of Phantom Menace 1 online. There are no more bugs in the 1.0 version I played back then (one bug caused a program anomaly in the late game during the mega saucer battle, so I played for two years without passing the game), and the balance has been rebalanced. Remember the early version, the soldiers can be upgraded to be more fierce than tanks.

ps . This version has a bug in windows xp (I think it's caused by X-Mode) that needs to be fixed with a patch made by the player. See the X-COM: UFO Defense Bug FAQ for details.

Downloaded and accidentally played overnight (choose the highest difficulty), 320x200 graphics resolution, in the 17-inch LCD monitor full screen are not difficult to see. Gameplay is also better than most of today's games, playing with great depth. Definitely not biased because of my nostalgia. The sense of operation is also very good, in addition to not support the mouse wheel. Oh that era of the mouse does not seem to have a scroll wheel yet.

Recommended for students who love single-player strategy games to revisit this old classic.

Today is really a special day ah. google three days two head to change the logo pattern, but also today it is prudent to write a tip on the google blackboard. Excerpt of the last sentence.

"Today, we are honoring those who have worked tirelessly and tried to make progress for humanity by changing our home page logo."

Oh, a decade has passed. In those days, I hadn't grown up.