作者:Roads less Taken
A blend of programming, boats and life.
时间:2014,10,29
Nim is presented as an imperative language.
And yes, it has some of its roots in the Pascal line of languages, but it also has a set of powerful abstraction mechanisms making it very suitable for object oriented programming. And when I write “object
oriented” I am referring to a broader flexible sense of OO in which objects can be formulated
with attached behavior, polymorphism between different objects, some kind of reuse model of code (inheritance etc) and some type of dynamic dispatch.
nim作为一种命令语言呈现。是的,它有一些根源在pascal语言中,但是它有一套强大的抽象机制使它非常适合面向对象编程。当我写“object oriented(面向对象)”的时候,我指的是一个更广阔灵活度的OO,可以通过附加行为规划对象,不同对象之间存在多态性,某种代码重用模型(继承等)和某种类型的动态调度。
Since
I am a long time Smalltalker that
is my main reference for “good
OO” and not the … monstrous OO often seen in Java or C++. Its hard to explain the differences,
but let me just say that OO in Smalltalk is elegant and natural, very much like in Ruby or Python - but ever so more streamlined. Smalltalk is a dynamically strongly typed reflective language with a heavily closure based style.
既然我是一个长时间的smalltalker,所以我提到的主要是“good OO(好对象)”并不是。。。经常在java或者c++中见到的丑陋的对象。它很难解释不同,但是我只说smalltalk中的面向对象在是优雅和自然的,非常类似与ruby或者python中的面向对象,但是更精简。smalltalk是一个动态的强类型反射语言带有一个大量基于闭包类型。
In
this article I will try to make “sense” out of how to do OO in Nim.
在这篇文章中,我将尝试面向对象怎样在nim中有意义。
We roughly have the following mechanisms in Smalltalk related to OO, and the first 4 are the same as in Java:
在Smalltalk中我们大致有以下关于面向对象的机制,前4条与java中的相似。
1 Single inheritance class model.
单一继承的类模型
2 Normal “receiver only” dynamic dispatch.
正常的"接收器"动态调用
3 Able to do super and self calls.
能够使用super和self调用
4 Garbage collection.
垃圾收集
In addition Smalltalk adds very important salt and pepper, which Java definitely doesn’t have:
另外,Smalltalk增加了在java明确没有的重要内容:
5 Everything is an object including all datatypes, so we can inherit from them and also extend/override with methods.
一切皆对象,对象包含所有的数据类型,所以我们可以继承它们,也可以扩展或重写方法
6 Every behavior is a message invoking a method, including all operators. Only exceptions are assignment and return.
每一个行为都是一个信息调用一个方法,包含所有的操作符。唯一的例外是赋值和返回。
7 Dynamic duck typing.
动态duck typing(鸭子类型)。
8 Pervasive use of closures with support of non local return enabling expressive protocols.
随着非局部回归能够表达的协议支持闭包普遍使用
Then of course Smalltalk goes “OO Banzai!” with:
带有以下特点,smalltalk会是更完美的面向对象
9 Classes are objects too, so they have methods and inheritance and a class…
类是对象,所以它们有方法和继承
10 Class variables and class instance variables.
类变量和类的实例变量
11 Full meta level meaning your code can modify itself 100% during runtime.
全元级意味着你的代码可以在运行时100%修改。
Learning Nim is really fun, its like a “box of chocolate” with lots of slick mechanisms to learn. This also makes it slightly confusing - what stuff should I use regularly? What should I avoid? What parts combine to enable OO in Nim? Its not clear :)
学习nim确实很有趣,它就像一盒巧克力,有很多灵活的机制需要学习。这也使它有些令人迷惑-什么材料我应该经常用到?我该避免什么?那些部分相结合在nim中产生对象?这不清楚。。。
And this walk through is also not entirely clear, but its a series of experiments with concluding remarks referring back to the Smalltalk laundry list above. Could I restructure this article? Probably. Did I use too much sample code? Probably. Is the article too long? No doubt.
而且,走过也并不完全清楚,但是Smalltalk的清单上列出一系列它的实验与结语。我可以调整这篇文章吗?可能,我是否用了很多的样例代码?可能。这篇文章太长了吗?毫无疑问
But here we go…
但是,这里我们来过...
We can start with the work horse in Nim - procs. A proc is simply a regular statically typed function. This means we have static compile time binding for them - and they support “overloading” on the arguments, so we can define many procs with different implementations for different types on the arguments.
我们可以从nim中的work horse开始-proc。一个过程仅仅是一个规则的静态类型函数。这意味着我们可以静态编译时绑定它们,它们支持参数重载,所以我们定义很多对于不同参数类型有不同实现的过程。
Also, Nim introduced so called UFCS before D established that acronym, so syntactically these calls are equivalent:
同时,Nim介绍所谓UFCS在D编程语言建立缩写之前,所以,依照句法地这些调用是等价的:
1 2 |
|
Having both proc overloading and the Uniform
Function Call Syntax in combination with theneat modules system where one module can
define procs using types from other modules (pretty obvious you can do that) - then we already can do “OO looking code” in a simpler fashion. For example we can easily add a reversed()
method
to the type string
that
returns a new string with characters reversed.
具有过程重载和统一函数调用语法结合整齐的模块系统,一个模块可以使用来自其他模块的类型定义过程(很明显你可以做到它)-然后我们已经可以以一种更简单的方式实现“OO looking code”。例如,我们可以很容易的为字符串类型添加一个reversed()方法,返回一个新的反序字符串。
First, lets just verify that string doesn’t already have it. I use Aporia and I have turned on“Enable suggest feature” in Preferences, then it tries to help me:
首先,让我们验证字符串没有reversed()方法。我使用Aporia并且我已经在参数选择中打开了“启用建议特征”,然后它试图帮助我:
…so nothing there right now. Let’s hack. In the beginning below we see that reversed()
already
exists for openarray, which means both array
and seq
types.
Then we add it for string
:
...所以现在什么都没有。让我们破解。在下面开始我们看到reversed()已经存在于在openarray中了,它意味着数组和序列类型。下面我们我们为字符串添加它:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
…using
Aporia we can actually immediately see
right after we wrote that proc, that the compiler picked it up and offered it as a suggestion when we started typing in test code:
...使用Aporia,实际上在我们写了那个过程后我们可以立即看到它,编译器捕获它并提供它作为一个建议,当我们开始编写test代码的时候:
Compile
and run:
1 2 3 4 5 6 7 8 |
|
Funny
sidenote: “G?ran” didn’t work so well… UTF8 strikes! But this is fully natural given our non-UTF8 aware implementation, we end up reversing the two UTF8 bytes representing “?”…
In each “Remark” heading I try to connect back to Smalltalk and that laundry list. Most Smalltalk implementations have some mechanism for “class extensions” (able to add methods in classes outside your package) and they are quite easy in Nim, even for builtin datatypes, since both procs (and methods, continue reading) are defined separately and operate on types, not “just” objects. And we have the UFCS syntactic sugaring, making the illusion complete.
在每个备注头我尝试链接到Smalltalk和那个细目清单。多数Smalltalk实现有“类扩展”机制(能够在你包外的类中添加方法),它们在nim中相当容易,甚至是内置数据类型,因为所有的过程都是分开定义的和在类型上操作,不仅仅是对象。我们有UFCS语法糖,制造完成时的假象。
In Nim most operators are handled the same way, so we can implement things that are “hard wired” in other languages, like ==
(equality)
using the same mechanism. This nicely reverbs with Smalltalk.
在nim中很多操作者都以同样的方式处理,所以我们可以实现的事情是“硬链接“在其他语言中,如==(相等)使用相同的机制。这是与Smalltalk很好的混合。
I would say that 5 and 6 on that list have fair support in Nim.
我想说的是上面的第5和第6条在nim中有很好的支持。
distinct
types
of them. An alias is just that - same, same. But a distinct
type
creates a completely separate type - it just happens to “be” the same thing. This makes the builtin datatypes a bit more malleable and also enables our code to be very typesafe and strict.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
It seems to me that distinct types in Nim captures even more of 5 and 6 - while we can’t inherit from fundamental datatypes, we can in fact:
在我看来,在nim中的不同类型捕获的甚至比第5和第6条更多-虽然我们不能从基本数据类型继承,事实上我们可以:
1 Create distinct types from them and extend those.
可以从基本数据类型创造不同的类型然后扩展它们
2 And actually even borrow base type implementations on a proc-by-proc or op-by-op basis. But ONLY from the base type. I guess this also works for other base types than the builtins.
实际上甚至借基本类型实现一个程序的过程或者一个运算的运算是基础。但是仅仅来自基本类型。我猜想这同样能在其他基本类型上实现不仅是内置类型。
There is quite a lot of other mechanisms that I will not cover here, like overriding “.”-access or the dereferencing operator “[]” and so on. I would say all of this is enough in order to be able to work with fundamental types in “an object oriented manner” similar to how we can do it in Smalltalk.
这里有相当多的其他机制我没有提及,例如重写“.”-访问或者解引用操作符"[]"等等。我会说所有的这些是足够的为了能够在“一个面向对象方式“中使用基本数据类型就像我们在Smalltalk中能做的。
tuple
is
like a struct in C or a “record” or a “row” in a database table. Its a composition of named and typed fields
in a specific
order:
ok,让我们转到更复杂的数据。一个元组就像是C中的一个struct(结构体)或是数据库表中的一个记录或一行。它由名字和类型域以特定的顺序组成:
1 2 |
|
So its the simplest heterogenous composition of data in Nim, there is no overhead at all and they do not know their type at runtime (so dynamic dispatch on tuples is not possible) and there is no hiding of members either.
所以它是nim中最简单的异构成分的数据,它们没有开销以及它们在运行时不知道自己的类型(所以在元组上动态调度是不可能的),并且它也没有隐藏的成员。
Thus a tuple is a very basic type, and given their limitations compared to their Bigger Brotherthe object, they should be used when convenience and/or zero overhead matters more than information hiding or complex attached behavior.
因此一个元组是一个很基本的类型,跟它们的大哥对象相比给出了限制,它们应该用在使用方便或者零开销的问题上而不是信息隐藏或者更复杂的链接行为。
In the Nim standard library we find tuples as the smaller building blocks inside Collection types like KeyValuePair, a Complex number, a Point or a Rectangle in graphics, or a Name & Version combination etc. Small
and simple things, and when it comes to Collections the fact that they carry zero storage
overhead is of course important. But they are not used even nearly as much as objects are, some quick searches like grep
-r "= tuple"
in lib shows 25 hits, objects are used typically 10x more, and I probably listed half of the tuple uses in the first sentence of this paragraph :)
在nim的标准库中,我们发现元组就像是集合类型中的一个小积木就像KeyValuePair,一个复数,一个点或者一个矩形中的圆形,或者一个名字和版本的组合等。小巧并且简单的东西,当涉及到集合事实是它们具有零存储开销当然是重要的。但是它们不经常使用,甚至和对象一样多,一些快速搜索像grep -r"=tuple"在lib显示25hits.对象使用的通常是10倍以上,并且我可能已经列出元组在使用时一半可能性在这一段的第一句话。
If you are unsure if you should use a tuple or an object, I suspect you are better off with an object.
如果你不确定应该使用一个元组或者一个对象,我建议你最好使用一个对象。
元组是看似最有趣的在集合内部或类似的低级别的代码。对于大部分面向对象代码,对象是更好的选择。
Before we go further we need to look at ref
and ptr
in
Nim. Nim can work closer to the metal than say Java or Smalltalk. To be able to do that, Nim needs regular C style pointers, they are declared as ptr
Sometype
. Such a pointer variable, that directly points to a memory location, is normally used when interfacing with C or when you want to play slightly “outside the box”, for example if you wish to allocate something in memory and pass it over to another
thread - keeping track yourself when to deallocate.
在我们走的更远之前我们需要看看nim中的引用和指针。nim可以说比java或者Smalltalk更接近metal的工作。为了能够做到那样,nim需要规则的c类型的指针,它们被声明为指针类型。例如一个指针变量,它直接指向一个内存的位置,通常在链接c时或者你想在规则外稍微玩一下时使用,例如当你想在内存中分配一些东西,然后传给另一个线程-当释放的时候自己保持跟踪轨迹。
As a language that wants to cover all bases this is essential stuff to have, and of course
- this isunsafe territory, feel free to shoot yourself in your proverbial foot. But for regular
“application level” code we probably do not need to use ptr
at
all.
作为一种语言想要覆盖所有的基础,这是必不可少的原料,当然那是不安全的地区,感觉就像是用你众所周知的脚射门。但是对于定期的应用层代码我们可能根本不需要使用ptr。
Instead we use ref
which
is in fact also a pointer, but its a friendlier one that does a bit of automatic allocation and especially deallocation for us using the garbage collector. If we declare type
KeyValuePair = tuple[key: string, value: string]
without
using ref - and then use that to declare a variable x in a proc, then that variable will be allocated
in the stack frame when the proc is called, and thus also disappear when the proc returns. Its fairly logical - var xis a
tuple, not a pointer to one. Nothing is in this case allocated on the heap and no garbage collection is needed, since the whole value was inside the stack frame.
相反,我们使用的引用它实际上也是一个指针,但是它相对友好,可以自动分配,特别是可以用垃圾收集释放我们所用的内存单元。如果我们声明类型KeyValuePair = tuple[key: string, value: string],没有使用引用,然后在一个过程中使用它声明一个变量x,然后当那个程序被调用的时候,变量x将会被分配到栈帧中,因此当程序返回的时候变量会消失。它是合乎逻辑的,变量x是元组,没有一个指针指向它。在这个例子中,没有任何东西分配在堆中并且没有需要垃圾收集器,因为例子中所有的值都在栈中。
But for making data that lives longer than the current invocation of a proc, we tend to use “ref” types. For such types the value will be allocated on the heap, and the variable will hold a pointer to it, and the garbage collector tracks our references.
但是对于想让数据比当前的调用过程存活的时间更长,我们需要使用ref类型。对于这个类型值会在堆上分配,并且变量将持有一个指向它的指针,和垃圾回收器跟踪我们的引用。
A bit of training code showing that while we normally use ref
to
refer to objects, we can refer to other types like enums as well:
一点测试代码显示,既然我们通常使用引用引用对象,那么我们也可以引用其他类型像枚举:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
Since I have been working primarily in Java, C# and Smalltalk the last 20 years (ouch) I have gotten used to variables almost always being references to
objects, and objects almost always being allocated on the heap. Nim has value types, and then ref
and ptr
is
used to create reference types pointing at them. I am not a language designer though - perhaps one can express it using more precise words.
过去的20年我的主要工作是在Java,C#以及Smalltalk,我已经习惯了变量几乎总是被对象引用,和对象几乎都是在堆上分配的。Nim有值类型,然后引用和指针被用来创建引用类型指向它们。我不是一个语言设计者-也许有人可以用更精确的语言表达它。
Proc variables are automatically allocated in the stack frame, as usual. This is also true of ref
variables,
but for those we only allocate the actual pointer bits there. But where does it point? This
is where an allocation step comes into play - using new()
.
Calling new()
with
a ref variable as argument, will allocate the type that the ref variable refers to on the
heap, and then set our ref variable to point there. Obviously you can also explicitly pass the type that the ref refers to (in the example code LightImpl
)
but that makes the code more brittle and less encapsulated.
跟平常一样,过程变量会在栈框架中自动分配。引用变量也是一样,但是对于那些我们只分配实际的指针位。但是它指向哪里呢?这是一个分配步骤进场的地方-使用new().用引用变量作为参数调用new(),将在堆中分配引用变量指向的类型,然后设置我们的引用变量指向这里。显然你可以显示的传递引用指向的类型(例子代码中的LightImpl),但是那样使得代码很脆弱和更少的封装性。
I think the greater flexibility of Nim, where we can choose to allocate values or references to values has benefits in more complex scenarios - and definitely for performance. It seems fairly well “embedded” in nice mechanisms so that general application level OO code doesn’t suffer too much from this “complication”.
我认为nim更大的灵活性是,我们可以选择分配值或者引用值有好处在更复杂的情况下-并且具有更高的性能。这似乎是相当好的“嵌入式”机制,以至于通用的应用程序层面的面向对象代码并没有从这样的混乱变糟。
Ah, finally! But this is not your dad’s Java objects, or your granddad’s Smalltalk objects. First of all, we don’t have “classes” - in Nim that term is not really used. Instead we talk about an object
type.
最后的!但是这不是你父亲的Java对象,或者你爷爷的Smalltalk对象。首先,我们没有“classes”-在Nim中不是真的使用,相反我们讨论一个对象类型。
An object is similar to a tuple, but it knows its type at runtime and we can decide which members we want to be visible outside the containing module. We can also inherit from an existing object type, single inheritance. Let’s hack some code:
一个对象类似于一个元组,但是它知道它的运行时类型,并且我们可以决定我们要在包含模块外可见的成员。我们也可以从已有的对象类型继承,单一继承。让我们看一些代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
|
Phew! So the above little fruit library sets up some trivial inheritance and adds
some procs and methods to these object types which we want to play with to see how the inheritance works. Now, the following code uses the above library:
所以上面的fruit库建立了一些平常的继承和添加了一些过程和方法对这些对象类型,我们想看看这些对象类型的继承是如何工作的。现在,下面的代码使用上面的库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
|
Did you follow all that? Really? Then explain it to me! :)
你仅仅跟随了吗?真的?为我解释它!:)
Basically, mixing procs and methods … is tricksy.
So let’s shorten, remove comments, regroup code into “classes” and only use methods. To solve the “super call” we throw in a template so that we can reuse that code in two different methods. Not the same, but ok:
所以让我们缩短,移除注释,将代码重组为类”classes“,并且只使用方法。我们把解决”super call“的方法放在一个模版中,以至于我们可以在两个不同的方法中重用代码。不是相同的,但是ok:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
|
And
the code testing it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
What I learned from the above:
我从上面学到的:
1 Single inheritance works fine in Nim, no surprises really. Sure, procs and methods are defined “on their own” but there is no practical difference.
单继承在nim中工作的很好,没有惊喜,真的。当然,过程和方法被定义为自己的,但没有实际的不同。
2 Procs bind statically and calling a “super” proc is easy using type conversion. But… if the super method calls other methods… ouch! Type info lost! Better to use templates or similar as above.
使用类型转换过程静态绑定和调用一个”super“过程很容易的。但是...如果super方法调用其他方法...!类型信息丢失!更好的是使用模版或者与类似上面。
3 If we don’t use generics but instead use base types (like Fruit) for arguments, we will “lose” type information, and potentially we “miss” calling our overrides! Not a surprise to a hardcore static guy, but… as a dynamic typing dude - this is a gotcha! Using methods only of course avoids that issue.
对于参数如果我们不使用泛型而是使用基础类型(例如Fruit),我们将丢失类型信息,以及我们可能错过调用我们的重写方法。对于一个静态的类型这并不惊讶,但是作为一个动态类型-这是个问题!仅使用方法当然避免的这个问题。
4 Methods and procs can be mixed, but here there be dragons. I think.
方法和过程可以混合,但是这里有问题。我认为。
5 If we use methods we currently can’t call a “super” method. Workaround today is to factor out the base implementation, for example in a template, as done above. UPDATE: See partIV about this.
如果我们使用方法,我们目前不能调用一个”super“方法。今天的解决方法是因素的基础实现,例如在一个模版里,如上面所做的那样。更新:See part IV
about this。
6 If we use procs only and apply generics (I did but didn’t bother including that code), it works perfectly fine too in this example. But as noted, do not use type conversion to a base type to do super calls, unless you are sure what you are doing. :)
如果我们仅使用过程和泛型(我实现了但是这不包括那个代码),在这例子中它也能工作的很好。但是要注意的是,不要使用类型转换为一个基类型为了super调用,除非你确定你在做什么。
7 If you have several classes like this in the same module, take care of the order of procs - make sure your overrides are defined before their uses. Otherwise you can easily end up calling a base implementation, just because the compiler hasn’t seen the override definition yet!
如果在相同的模块中你有几个类似这样的类,小心过程的顺序-确保你的重写在使用它们之前已经定义。否则你可以很容易的以调用一个基础实现而结束,仅仅是因为编译器没有看到重写的定义!
Phew. In summary I would say the OO features are mostly there. Super calls (for both procs and methods I would say) is evidently a “hole” in the language, I think it needs some kind of solution. But coding and testing is a very nice experience, its not that hard to get into it. But I would say it will take some practice before you know what routes are available for OO in Nim.
总之,我想说的面向对象的特征主要在这里。super调用(我说的是对于过程和方法)在语言中明显的是一个”hole“。我认为它需要某种解决办法。但是编码和测试是一个很好的经验,它不是很难进去。但是我想说在你知道什么路线可用于nim中面向对象之前它需要一些练习。
I have only begun discovering Nim so I have yet to see how OO works in a larger Nim codebase. I also haven’t really explored lambdas and several of the datatypes yet, nor macros and templates and lots of other things. But I feel confident that Nim can do OO code quite well.
我只是开始发现nim所以我还没有看到面向对象怎样工作在一个更大的nim代码库中。我也没有真正的探索lambdas和几种数据类型呢,还有宏和模版以及许多其他的东西。但是我很有信心nim可以将面向对象代码做的很好。
Go Nim!
向nim出发!
Oct
29th, 2014 Languages, Nim, Nimrod, OOP, Object
orientation, Programming
文章链接:
原文地址:http://blog.csdn.net/dajiadexiaocao/article/details/46537757