加载中

About a week ago I started looking at Elixir. Elixir had been one of those things that I was vaguely aware of but had not yet time to look at in any detail.

This all changed when I discovered the announcement that Dave Thomas was publishing Programming Elixir.  Dave Thomas edited my Erlang book and did great work in introducing Ruby, so when Dave gets excited about something then this is a sure sign that something interesting is in the wind.

Dave was excited about Elixir, in the introduction to his new book he says:

I came across Ruby in 1998 because I was an avid 
reader of comp.lang.misc (ask your parents). I 
downloaded it, compiled it, and fell in love. 
As with any time you fall in love,
it’s difficult to explain why. 
It just worked the way I work, 
and it had enough depth to keep me interested.

Fast forward 15 years. All that time I’d been 
looking for something new that gave me the same feeling.

I came across Elixir a while back, but for some 
reason never got stuck in. But a few months ago I 
was chatting with Corey Haines. I was
bemoaning the fact that I wanted to find a 
way to show people functional programming concepts 
without the kind of academic trappings those books 
seem to attract. He told me to look again at Elixir. I
did, and I felt the same way I felt when I first saw Ruby.

大约一周前我开始学习Elixir. 关于这个我也只是有些模糊的印象但还没有仔细去看。

但在Dave Thomas 出版了 Programming Elixir之后一切都发生了改变.  Dave Thomas 帮我修订过Erlang这本书并且是Ruby的倡导者, 只要Dave对什么产生了兴趣那绝不是空穴来风.

Dave 对Elixir很感兴趣, 在他的书里这样写道:

I came across Ruby in 1998 because I was an avid 
reader of comp.lang.misc (ask your parents). I 
downloaded it, compiled it, and fell in love. 
As with any time you fall in love,
it’s difficult to explain why. 
It just worked the way I work, 
and it had enough depth to keep me interested.

Fast forward 15 years. All that time I’d been 
looking for something new that gave me the same feeling.

I came across Elixir a while back, but for some 
reason never got stuck in. But a few months ago I 
was chatting with Corey Haines. I was
bemoaning the fact that I wanted to find a 
way to show people functional programming concepts 
without the kind of academic trappings those books 
seem to attract. He told me to look again at Elixir. I
did, and I felt the same way I felt when I first saw Ruby.

I know the feeling. Gut feeling  precedes logic. I know when things are right, I don’t know how or why I know, but the explanation of why things are right often comes weeks or years later. Malcolm Gladwell in his bookBlink: The Power of Thinking Without Thinkingtalks about this.  Experts in a particular field can often instantly know that something is right, but they can’t explain why.

When I saw that Dave had his eye “on the ball” I wanted to know why.

Surprise number two, Simon St. Laurent was also writing a book on Elixir. Simon did a good job with Introducing Erlang… and we’d exchanged several mails, so something was in the air. With both Pragmatic Press and O'Reilly racing to get into  Elixir I knew something was happening on the Erlang VM, and I didn’t know about this. Boy am I out of touch.

I mailed Dave and Simon and they kindly sent me copies of their books so I could start learning …  Thanks guys …

我能体会.纯粹的感官体验. 就像我知道一件事是对的但还不知道原因,几周甚至几年后这个问题总能回答出来. Malcolm Gladwell在Blink: The Power of Thinking Without Thinking一书中曾探讨过这个问题. 某些领域专家们总能凭直觉判断事情的正确与否,但却给不出具体原因.

但我发现Dave 的描述后,我很想知道为什么他会这样.

无独有偶, Simon St. Laurent也出了本Elixir的书. Simon的 Introducing Erlang… 一书表现不俗,我和他还通过邮件沟通过几次,还有有些熟悉的. 从Pragmatic Press 和O'Reilly 出版社都在争着出版Elixir可见一斑。关于Erlang VM, 我确实一窍不通。

我给Dave和Simon 发了封邮件,之后他们借给我了样书,现在可以开始阅读了 …  谢了 …

I downloaded elixir last week and started playing …

It didn’t take long, but pretty soon my gut feeling kicked in. This is good shit.  The funny thing is that Erlang and Elixir are the same thing under the surface.  They “feel” the same to me. In fact this is literally true, they both compile to instructions for the EVM (Erlang Virtual Machine) - actually nobody call this the EVM They just say the “Beam” VM but I thing we should start calling this the EVM to distinguish it for the JVM.

Why do Erlang and Elixir have the same “semantics”? The reason has to do with the underlying machine. The garbage collection behavior, the non-shared concurrency model, the underlying error handling and code loading mechanism are identical. They must be identical: they run on the same VM. This is also why things like Scala and Akka will never be like Erlang. Scala and Akka run on the JVM so under the covers, things like garbage collection and code loading are fundamentally different.

上周我下载了elixir 开始学习…

没多久我觉得就上手了. 确实好东西. 有趣的是Erlang和Elixir 实际上是同源的.事实上也确实这样,他们都会被EVM (Erlang Virtual Machine)编译 - 大家管这个EVM 叫 “Beam” VM 但为了和JVM区别就管他叫EVM 吧.

Erlang和Elixir为啥有相同的“语意”? 这得从底层谈起. 垃圾回收, 独立并发机制, 错误处理和代码装载机制都是一样的. 还有: 他们都运行在相同的VM里. 这也是Scala 和 Akka区别于 Erlang的原因. Scala和Akka是运行在JVM 上的, 垃圾回收和代码装载机制从本质上就不同.

What Elixir brings to the table is a complete different surface syntax, inspired by Ruby. What you might call a “non-scary” syntax, and a load of extra goodies.

Erlang’s syntax derived from Prolog and was heavily influenced by smalltalk, CSP and the functional programming. Elixir is heavily influenced by Erlang and Ruby.  From Erlang it brings pattern matching, higher order functions and the entire process and error handling “let it crash” philosophy. From Ruby it brings sigils, and shortcut syntaxes. It also adds a few goodies of its own, the |> pipe operator, reminiscent of Prologs DCGs and Haskell monads (though less complicated, more like the good old unix pipe operator) and the macro quote and unquote operators, which come from the lisp quasiquote and comma operators.

Elixir also brings a new underlying AST to the table, instead of the Erlang AST where everything form has its own representation, the Elixir AST has a far more uniform representation, which makes meta-programming far easier.

Elixir最明显的特点就是语法的表示,和Ruby很像.  简单易懂,还有很多外部资源。

Erlang’s 的语法源自 Prolog 还受到smalltalk, CSP 和功能性编程语言的影响. Elixir 受到Erlang 和Ruby的影响. 类型匹配, 功能优先级错误处理机制都是从Erlang来的.sigils, 语法简写从Ruby 来。 当然也有自己的创新,  |> 管道操作, Prologs DCGs 和Haskell monads (简化了一些类似于Unix的管道操作) 宏引用,是从lisp quasiquote得来,还有逗号操作符.

Elixir更新了AST机制 , 和Erlang AST独有展示的模式不同, Elixir AST 统一了模式这使得meta-programming 更简单.

The implementation is surprisingly solid, though some things don’t work as I expected. String interpolation (which is a great idea) works in a hit-and-miss manner.

I’d thought that:

IO.puts "...#{x}..."

evaluated x and injected a pretty-printed representation of x into the string.  But it only works for some simpler forms of x and not all x.

Since you can call any Erlang function from Elixir this was easy to fix.

IO.puts “…#{pp(x)}…” always works. I just defined pp(x) as

def pp(x) do 
    :io_lib.format("~p", [x])
    |> :lists.flatten
    |> :erlang.list_to_binary
end

In Erlang this would be:

pp(X) ->
  list_to_binary(lists_flatten(li_lib:format("~p),[X])))

which is “obviously” equivalent to the Elixir version. Actually the Elixir version is easier to read. The |> operator in the above means pipe the output of io_lib:format into lists:flatten and then list_to_binary. Just like the good ol' Unix pipe operator.

实现起来也中规中矩,只是有几处需要注意. 字符串插入 (主意不错) :

IO.puts "...#{x}..."

获取x并按格式打印出来. 但只对简单模式的x起作用.

 这可以通过从Elixir调用Erlang的方法来弥补

IO.puts “…#{pp(x)}…” 就可以. 只是需要把 pp(x) 改成

def pp(x) do 
    :io_lib.format("~p", [x])
    |> :lists.flatten
    |> :erlang.list_to_binary
end

Erlang 描述如下:

pp(X) ->
  list_to_binary(lists_flatten(li_lib:format("~p),[X])))

这和Elixir的描述一样. Elixir的写法也更容易阅读. |> 操作符用来把io_lib:format 的结果输入到 lists:flatten 然后再到list_to_binary. 跟unix的管道符|一样

Elixir breaks a few Erlang holy cows - variables can be re-bound in sequences.  This is actually ok, the resulting forms can still be normalized into a static-single-assignment (SSA) form. While this is OK in sequences it would totally verboten-nicht-do-not-do-it in loop constructs. But this is fine, Elixir has no loops, only recursion. Actually it could not have loops with mutable variables since this would be impossible to compile into anything remotely sensible in the EVM. SSA variables in sequences are fine, the EVM knows how to optimize these. Loops no, so Elixir doesn’t go there. It would even boil nicely down into LLVM assembler - but that’s another story too long to tell here.

Elixir区别与Erlang在 - 变量可复用.  结果集始终可以表示为static-single-assignment (SSA) . 但在循环结构里千万别这么做.好在 Elixir只用了递归而没有循环结构. 如果循环结构里引用了可变的参数,那远端EVM就没法编译了. 当在SSA顺序结构中使用变量时, EVM 就知道如何处理.循环结构Elixir就没办法了. 其根源可追溯到LLVM汇编- 那就是另一个问题了.

The Three Laws of Programming Language Design

  • What you get right, nobody mentions it.

  • What you get wrong, people bitch about.

  • What is difficult to understand you have to explain to people over and over again.

Some languages get some things so right that nobody ever bothers to mention them, they are right, they are beautiful, they are easy to understand.

The wrong stuff is a bitch. You boobed, but you are forgiven if the good stuff outweighs the bad. This is the stuff you want to remove later, but you can’t because of backwards compatibility and some nitwit has written a zillion lines of code using all the bad stuff.

编程语言设计的三定律

  • 你做对的,无人为你提。

  • 你做错的,有人跟你急。

  • 难点必须不断重复解释。

有些语言在一些方面做得很好,它们正确,优雅,易于理解,但没人不辞麻烦地提及这些。

错误的地方非常糟糕。你成了笨蛋,如果好处比重大于坏处,你可能被原谅。那些你想在以后消除的坏处,却因为向后兼容性或者是有些傻子(或曰你的狂粉)已经用所有那些坏处写了无数行代码等原因,而不能动。

The difficult to understand stuff is a real bummer. You have to explain it over and over again until you’re sick, and some people never get it, you have to write hundred of mails and thousands of words explaining over and over again why this stuff means and why it is so. For a language designer, or author, this is a pain in the bottom.

I’m going to mention a few things that I think fall into these three categories.

Before I start I’ll just say that Elixir has got a heck of lot of things right, and the good things far outweigh the bad things.

The nice thing about Elixir is that it’s not too late to fix the bad things. This can only be done before zillions of line of code get written and before trillions of programmers start using it - so there’s only a few days to fix this.

难以理解的内容是真正倒霉的事情。你必须一遍又一遍地解释,直到你吐血,可还是有些人永远不懂,你必须写上百邮件和数千文字来一遍又一遍地解释这些内容是什么意思以及它们为什么会如此。对于一个语言的设计者或作者来说,这是一个痛苦的深渊。

我将要提到的几件事就是我认为落入这三类情况中的。

在我开始前,我要说 Elixir 做对了好多好多的事情,好处远远大于坏处。

关于 Elixir 的好处是,及时改正它的坏处还不算晚。这只能在无数代码行被写下和众多程序员开始使用它之前才能做到——所以只有少数时间来解决这些问题了。

No versions in the source files

XML files always start

  <?xml version="1.0"?>

This is great. Reading the first line of an XML file is like listening to the opening bars of Rachmaninoff’s third piano concerto. A sublime experience. All praise to the XML designers, hallowed be their names, give these guys some Turing prizes.

Putting the language version in all source files is essential. Why is this?

Early Erlang did not have list comprehensions. Suppose that we give a modern Erlang module to an old Erlang compiler and ask it to compile it. The modern code has list comprehensions, but the old compiler doesn’t know about list comprehensions so the old compiler thinks this is a syntax error.

If a version3  Erlang compiler is given a file that starts:

-version(5,0).

Then it should say

** auuuuugggghhhhhh **

   Oh bother and blast, I am mere version 3 compiler
   and cannot see into the future.

   You have given me a version 5 program. This means 
   my time on earth has come.

   You will have to kill me. You will uninstall me, 
   and install a version five compiler. I will be
   no more. I will cease to exist.

   Goodbye old friend.

   I have a headache. I'm going to have a rest...
**

It’s the first law of data design:

 All data that might change in the future should be 
 tagged with a version number.

and a module is data.

在源文件中没有版本

XML文件总是这样开始的:

  <?xml version="1.0"?>

这样很好。读取XML文件的第一行就象是听拉赫玛尼诺夫的第三钢琴协奏曲的第一小节(译者注:指其富有辨识度)。这是一个令人赞叹的经验。赞美XML设计师,愿他们的名字得到荣光,给这帮伙计一些图灵奖吧。

把语言的版本放入所有源文件中是必要的。这是为什么呢?

早期的Erlang没有列表推导式。假设我们对一个新版的Erlang模块用一个旧的Erlang编译器去编译。新版的代码含有列表推导式,但旧的编译器不知道列表推导式,所以旧编译器会认为这是一个语法错误。

如果加上版本号, version3  的 Erlang 编译器碰到这样开始的文件:

-version(5,0).

它会知道是一个更高版本的文件,并可能这样提示:

**  天~~~~啊~~~~~~  **

   哦,烦炸了,我只是第三版的编译器,看不到未来的变化。

   你刚刚给我一个第五版的程序,这说明我在地球上的寿命已过。

   你将不得不杀掉我,把我卸载,然后安个第五版的新编译器。曾经玉树临风的我现在没了价值,我将不再存在。

   再见吧,老朋友。

   我感觉头痛。我要休息一下……

**

这是数据设计的第一法则:

 所有未来可能会改变的数据应标记有版本号。

模块就是数据。

Funs and defs are not the same

When I started writing “Programming Erlang” Dave Thomas wondered why you couldn’t type function in the shell.

If a module contains this:

fac(0) when N > 0 -> 1;
fac(N)            -> N* fac(N-1).

You can’t just cut and paste this into the shell and get the same result. Dave asked why and said that this was stupid.

In Lisp and so on you can. Dave said something like “this is going to confuse people” - he was right and it does confuse people. There must be hundreds to thousands of messages on forums asking about this.

I have explained why so many times that my hair has gone grey, it’s true my hair is now grey because of this.

It’s because there is a bug in Erlang.

Modules in Erlang are sequences of FORMS.

The Erlang shell evaluates a sequence of EXPRESSIONS.

In Erlang FORMS are not EXPRESSIONS.

double(X) -> 2*X.            in an Erlang module is a FORM

Double = fun(X) -> 2*X end.  in the shell is an EXPRESSION

The two are not the same. This bit of silliness has been Erlang forever but we didn’t notice it and we learned to live with it.

In an Elixir Module you can write

def triple(x) do
   3 * x;
end

My bet is that thousands of programmers will cut and paste this from their text editor into the shell and it will say

ex> def triple(x) do 3*x; end
** (SyntaxError) iex:66: cannot invoke def outside module

If you don’t fix this you’ll spend the next 20 years explain why - just like we did in Erlang.

Funs and defs用法不同

在写 “Programming Erlang” 一书时Dave Thomas问function为什么不能写到 shell里.

如果代码这样:

fac(0) when N > 0 -> 1;
fac(N)            -> N* fac(N-1).

直接复制到shell里是不能运行的. Dave觉得这样很奇怪.

Lisp里这样做是没问题的. Dave 觉得这很会让人迷惑-确实如此。估计论坛里里关于此的话题也会很多.

我解释这个问题已经无数遍了,从黑发到白发那么长的时间里都在解释.

根源就是Erlang的一个bug.

Erlang的模块是一系列的 FORMS.

Erlang shell解析的是一些列 EXPRESSIONS.

但Erlang的 FORMS 不是EXPRESSIONS.

double(X) -> 2*X.            in an Erlang module is a FORM

Double = fun(X) -> 2*X end.  in the shell is an EXPRESSION

上面两个是不同的.这可能是Erlang一个永远的痛,但我们也会接受的.

在Elixir模块可以这么写

def triple(x) do
   3 * x;
end

估计很多人都会直接复制到shell里直接运行

ex> def triple(x) do 3*x; end
** (SyntaxError) iex:66: cannot invoke def outside module

如果你不知道这个根源可能就要花费大量的时间去弄明白问题的本质 - 就像 Erlang.

返回顶部
顶部