Tell Above, and Ask Below - Hybridizing OO and Functional Design

Tell Above, and Ask Below - Hybridizing OO and Functional Design
by Michael Feathers
Tell Above, and Ask Below—混合OO和函数式设计

I have an idea I’ve been holding back for a while because I think it is wrong. It’s just too general to be true, and the argument that I use for it is, well, a bit abstract, but I think there is something there. Here it goes.
Object-orientation is better for the higher levels of a system, and functional programming is better for the lower levels.
我有一个憋了很久的想法,我想它可能是错的。它太普通了以至于不太真实,并且我使用它的论点,有一点点抽象,但我还是想一吐为快。这个想法就是:对于系统的高层而言,面向对象更好,对于低层而言,函数式编程更好。

Interesting idea, but how do we get there?
有趣的主意,但这个主意是怎么产生的呢?
Well, for me it comes back to the basics of the approaches.
嗯,对我来说,最好是回溯到方法的基本原理上。
There are many different flavors of functional programming - many different definitions, but at their core is one central idea: we can make programming better by reducing side effects. When you don’t have side effects, you can more easily reason about your code. You have referential transparency - the ability to paste an expression from one place in your program to another, knowing that it, given the same inputs, will produce exactly the same outputs without causing nasty side effects someplace else. The technical name for this is ‘purity’.
有很多不同的函数式编程的味道-很多不同的定义,但这些定义都有一个核心的中心思想:通过减少副作用以写出更好的程序。当没有副作用,你可以更容易理解你自己的代码。你的引用变得透明-把一个表达式从一个地方paste到另一个地方的能力,知道给定一个相同的输入,将精确的产生出相同的输出,而不会在其他地方造成讨厌的副作用。技术上称之为“纯度”
Purity gives us more than just a bit more ease in understanding, it enables lazy evaluation. In case you haven’t run into this before, in some functional programming languages there isn’t any mention of ‘calling a function.’ Instead, you ‘apply the function.’ This is more than just different nomenclature. The expression [1..10] evaluates as 1,2,3,4,5,6,7,8,9,10 in Haskell. If you apply the function take to that sequence with an argument of 5 (take 5 [1..10]), you get 1,2,3,4,5, the first 5 elements of the sequence.
纯度给我们的不仅仅是更容易理解,它还使得延迟求值成为可能。你以前可能没有遇到这种情况,在一些函数式编程语言中,没有“调用函数”说法,取而代之的是“应用函数”。这不仅仅是命名方法上的差异。在Haskell中,表达式[1..10]求值为1,2,3,4,5,6,7,8,9,10。如果你应用函数take带5的参数,去求值此序列的话,你得到1,2,3,4,5,即序列的前5个元素。
What do you think happens when you evaluate [1..] in Haskell? Well, that gives you an infinite sequence of ints starting from 1. At an interactive prompt, you’ll have to hit Ctrl^C at some point to stop the printing. Okay, so what about this expression?
take 5 [1..]
你想过,在Haskell中求值[1..]会得到什么吗?嗯,那将给你一个从1开始的无穷整数序列。在交互式场景下,你必须在某刻按下Ctrl^C以停止打印。Ok,那如果是下面这个表达式,将发生什么?
take5[1..]

You might think that it will run forever also. After all, [1..] has to be evaluated before it is passed to take, and it never stops. With lazy evaluation, though, this doesn’t happen. You aren’t calling take, you are forming an expression through function application. When you evaluate that entire expression, the runtime does only as much evaluation of the sub-expressions as it needs to do return the first result: 1, and then it evaluates for the second result, and all the way up to the limit of 5.
你可能想,它仍然会运行不止。毕竟,[1..]在传递给take前,必须求值,而它从不停止。但通过延迟求值,这不会发生。你没有调用take,你正在通过应用函数产生一个表达式。当你对整个表达式求值的时候,运行时仅仅求它需要的子表达式,从而返回结果1,然后它求值得到第二个结果,一直到5.
Lazy evaluation can be very powerful, but here is the key: it is completely enabled by purity. If you want to see this, imagine a large functional expression with a side-effect hidden deep inside it. When exactly does it happen? There really is no telling. It depends on the context the expression is evaluated in. Ideally, you shouldn’t have to care.
延迟求值威力无比,但它的关键是:它通过纯度完全可以达到。如果你想明白这点,想像一个有着隐藏的很深副作用的大的函数表达式。它到底是什么时候发生的?你根本不知道。它依赖于表达式求值的上下文。理想情况下,你不应该关心。
Object-Orientation has some parallel affordances. Again, there are a wide variety of different definitions of OO, but I like to go back to Alan Kay’s original conception. After all, he invented the term.
面向对象有一些类似的启示。再说一次,对于OO,有很多不同的定义,但我想回归到Alan Kay的最原始的概念,毕竟这个概念是他发明的。
Alan Kay saw objects as a way of creating complex systems that is pretty much in line with how nature handles complexity in biology. In an organism, there are many cells, and the cells communicate by passing chemical messages between them. It isn’t just coincidence that Smalltalk uses the notion of a ‘message send’ rather than function call. The nice thing about object structure is that it de-emphasizes the players and maximizes the play. As Kay implied, the messages are more important than the objects. In a biological system, this goes as far as systemic redundancy. You can’t bring down the whole organism by killing a single cell. The closest we’ve gotten to that in software is Erlang’s process model which has been likened to an ideal object system.
Alan Kay认为对象是构造复杂系统的一种方式,这与自然界如何处理生物的复杂性非常相似。在一个有机体中,有很多细胞,细胞之间通过传递化学信息来进行交互。SmallTalk使用“消息发送”而不是函数调用,这不仅仅是一种巧合。对象结构的好处是,它不再强调参与者和最大化玩法。就像Kay暗示的,消息比对象更重要。在一个生物系统中,这点伴随着系统冗余。你不可能通过杀死一个细胞来消灭整个有机体。在现有软件中最接近理想对象系统的是Erlang的进程模型。
In the early 2000s, Dave Thomas and Andy Hunt wrote about a piece of design guidance that they called ‘Tell, Don’t Ask.’ The idea was that objects are really best when you tell them to do something for you rather than asking them for their data and doing it yourself. This makes perfect sense from the point of view of encapsulation. It also makes it easier on the user of the object. When you get data back, you have to understand it and operate on it. This isn’t quite as simple as as telling an object to do something with its own data.
在本世纪初,Dave Thomas和Andy Hunt发表了一条设计指导,他们称之为“Tell,Don’t Ask”。他们的观点是,最好是你告诉对象去做某些事情,而不是向它们索要数据,然后你亲自去做。这是封装的最好诠释。它还使得对象的使用更简单。当你得到数据后,你必须理解数据然后才能操作它们。这显然比告诉一个对象用它自己的数据做事更复杂。
In biology, as I mentioned before, we have chemical messages between cells. But, they are different from typical OO in one very important respect - communication between cells is asynchronous. A cell doesn’t block all of its internal activity until it receives a response. Quite often, object systems do this. We send a message to another object, and we wait for a response. A response consists of a return value (data), and when we receive it, we are in the hot seat. We have to do something with it or choose to ignore it - the flow of control is back in our hands. It’s rather easy to end up violating ‘Tell, Don’t Ask’ when you do synchronous calls. Synchronous calls often return values, and after all, a return value is the result of an implicit ‘ask’.
在生物体中,就像我前面所说的,在细胞之间传递化学信息。但是,和典型的OO相比,它们有一点非常不同的地方,就是细胞之间的通讯是异步的。一个细胞在接收到响应之前,并不会停止它内部的活动。而对象系统经常被阻塞。我们发送一条消息给另一个对象,然后等待它的响应。响应包括返回值(数据),当我们接收到响应的时候,我们处于一个相对高的层次。我们要么去处理此响应,要么选择忽略它-控制流回到了我们手上。当你做同步调用的时候,你很容易就违反了“Tell,Don‘t Ask”。同步调用经常返回值,而返回值就意味着隐式的“ask”
If we look at OO as being cell-like, it seems that much of our technology has it wrong. We can have classes and objects in a programming language, but as long as we make synchronous calls, the pieces aren’t as independent as they could be. Are there technologies that are more OO than we call OO? Yes. Messaging systems in IT architectures are all about gaining this level of independence.
如果我们像细胞那样看OO,就会发现我们很多的技术都是错的。我们在一门编程语言中,有很多的类和对象,但是一旦我们使用同步调用,这些元素就不再像原来那样独立了。难道有比我们称之为OO的更OO的技术吗?是的。IT架构中的消息系统就是为了获取这种隔离。
So, we’ve looked at object oriented design and functional programming. I think there is a real parallel between them.
至此,我们回顾了面向对象设计和函数式编程。我想它们之间可以真正的并存。
In OO, it is better to tell. When you tell, you maximize decoupling between entities. If you want to prevent re-coupling, you make your message sends asynchronous. This enabled by the tell model. In functional programming, it is better to ask. In fact, in pure functional programming, there is no other way to do things. A function which returns nothing is pointless unless it has a side effect, and we want to avoid those. Functional purity enables laziness in much the same way that OO enables asynchrony.
在OO中,Tell是更好的。当Tell的时候,你可以做到实体间的最大解耦。当你想阻止耦合的时候,你可以将消息传递异步化。这可以通过tell模型实现。在函数式编程中,ask是更好的,在纯函数式编程中,没有其他路子可选。一个什么都不返回的函数,除非他有副作用,否则毫无用处,而副作用正是我们需要避免的。函数的纯度保证延迟求值,就像OO保证异步。
Now, if we accept these premises, what makes the most sense when organizing a system? We could have a functional layer on top executes message sends internally when expressions within it are evaluated, but that could be problematic for systems understanding. Moreover, it could yield side-effects outside the functional layer and violate purity.
好了,如果我们能够接受这些前提,那当组织一个系统的时候,是什么导致最好的味道?在顶层,我们可以有一个函数层,当它内部的表达式求值的时候,执行内部消息发送,但这对于系统理解来说是有问题的。更严重的是,它会导致函数层外部的副作用和纯度的破坏。
What about the other direction? What if we put the object layer on top and allowed the objects to use functional pieces below? There isn’t any problem with that, really. Side-effect free functions are ideal for internal machinery. Object-orientation is great for the upper layer, where decoupling and information hiding are paramount.
另一个方向怎么样?如果我们把对象层放在顶部,允许对象使用低层的函数片,会怎么样?真的,问题都不复存在了。无副作用函数是一个构件内部的理想选择。面向对象更适合上层,对上层来说,解耦和信息隐藏是最为重要的。
So, that is the argument. And I know that it not always “true.” Languages like Scala allow programmers to freely mix objects and functions at any level of abstraction. You can clearly have functions which select and filter objects. Microsoft’s LINQ technology is all about having a functional layer on top of objects. Despite this, I think that my argument has a grain of truth. OO as originally conceived is very different from OO in practice. Or, to put it another way, we have OO now, but it is really more at the service and messaging levels in modern software architecture. At that level of abstraction, it seems to be true - it’s better to tell above and ask below.
这就是争议所在。我知道这不总是“真”。像Scala这样的语言,允许程序员在任何抽象层级上自由混合使用对象和函数。你可以清晰地拥有选择和过滤对象的函数。微软的LINQ技术到处是函数层位于对象之上。尽管如此,我认为我的观点更接近真相。OO最初的构思迥异于实践中的OO。或者换种说法,现在我们拥有OO,但它更多运用在现代软件架构中的服务和消息层。在这个抽象层面上,看起来就像真的了-最好的策略是tell above and ask below。

你可能感兴趣的:(编程思辨,设计)