In my post Implementing numbers in “pure” Ruby I established some ground rules that allowed us to use some basic ruby stuff like equality operator, booleans, nil
, blocks and so on.
在我的文章《在“纯” Ruby中实现数字》中,我建立了一些基本规则,使我们可以使用一些基本的Ruby类东西,例如相等运算符,布尔值, nil
和块等。
But what if we had absolutely nothing, even basic operators like if
and while
? Get ready for some pure OOP-madness.
但是,如果我们什么都没有,即使基本运算符也喜欢if
and while
怎么办? 准备一些纯粹的面向对象的疯狂。
- We can define classes and methods. 我们可以定义类和方法。
We should assume that ruby doesn’t have any classes predefined. Imagine we start from scratch. Even stuff like
nil
is not available.我们应该假设ruby没有预定义任何类。 想象我们从头开始。 甚至没有
nil
东西。The only operator we can use is the assignment operator (
x = something
).我们可以使用的唯一运算符是赋值运算符(
x = something
)。
Conditionals are important. They are the very essence of logic for our programs. So how do we do without them? I’ve come up with a solution: we can incorporate boolean logic inside EVERY object.
条件很重要。 它们是我们程序逻辑的本质。 那么,没有他们怎么办? 我想出了一个解决方案:我们可以在每个对象中合并布尔逻辑。
Think about it, in dynamic languages like Ruby logical expressions don’t actually need to evaluate into some “Boolean” class. Instead, they treat everything as true except for some special cases ( nil
and false
in Ruby, false
, 0
and ''
in JS). So incorporating this logic doesn't seem that unnatural. But let's dive right into it.
考虑一下,在像Ruby逻辑表达式这样的动态语言中,实际上不需要评估为某个“布尔”类。 相反,除了某些特殊情况(在Ruby中为nil
和false
,在JS中为false
, 0
和''
),他们将所有内容都视为true。 因此,合并这种逻辑似乎并不自然。 但是,让我们深入了解它。
基础班 (Basic classes)
Let’s create a very basic class that will be the ancestor to everything we build in the future:
让我们创建一个非常基础的类,它将成为将来我们构建的所有内容的始祖:
class BaseObject
def if_branching(then_val, _else_val)
then_val
end
end
The method inside is our logical foundation. As you can see, straight away we assume that any object is true so we return “then-branch”.
内部的方法是我们的逻辑基础。 如您所见,我们立即假设任何对象都是真实的,因此我们返回“ then-branch”。
What about false? Let’s start with null, actually.
假的呢? 实际上,让我们从null开始。
class NullObject < BaseObject
def if_branching(_then_val, else_val)
else_val
end
end
Same thing but it returns the second parameter.
同样,但它返回第二个参数。
In Ruby, almost every class inherits from Object
class. However, there's another class called BasicObject
which is even higher up in the hierarchy. Let's copycat this style and introduce our alternative to Object
:
在Ruby中,几乎每个类都继承自Object
类。 但是,还有一个名为BasicObject
的类,它在层次结构中甚至更高。 让我们模仿这种风格,并介绍我们替代Object
:
class NormalObject < BaseObject
end
Now, everything we define later on should inherit from NormalObject
. Later on, we can add global helper methods there (like#null?
).
现在,我们稍后定义的所有内容都应继承自NormalObject
。 稍后,我们可以在其中添加全局帮助器方法(例如#null?
)。
如果表达式 (If-expressions)
This is enough for us to create our if-expressions:
这足以让我们创建if表达式:
class If < NormalObject
def initialize(bool, then_val, else_val = NullObject.new)
@result = bool.if_branching(then_val, else_val)
end
def result
@result
end
end
And that’s it! I’m serious. It just works.
就是这样! 我是认真的。 它只是工作。
Consider this example:
考虑以下示例:
class Fries < NormalObject
end
class Ketchup < NormalObject
end
class BurgerMeal < NormalObject
def initialize(fries = NullObject.new)
@fries = fries
end
def sauce
If.new(@fries, Ketchup.new).result
end
end
BurgerMeal.new.sauce # ==> NullObject
BurgerMeal.new(Fries.new).sauce # ==> Ketchup
You may be wondering, how is that useful if we can’t pass any code blocks around. And what about the “laziness”?
您可能想知道,如果我们不能传递任何代码块,这有什么用? 那“懒惰”呢?
Consider this:
考虑一下:
# Pseudo-code
if today_is_friday?
order_beers()
else
order_tea()
end
# Our If class
If.new(today_is_friday?, order_beers(), order_tea()).result
In our example, we will order beers AND tea disregarding the day of the week. This is because arguments are evaluated before being passed to the constructor.
在我们的示例中,我们将不考虑星期几订购啤酒和茶。 这是因为参数在传递给构造函数之前先经过评估。
This is very important because our programs would be incredibly inefficient and even invalid without it.
这非常重要,因为如果没有它,我们的程序将效率极低,甚至无效。
The solution is to wrap a piece of code in another class. Later on I will refer to this kind of wrappers as “callable”:
解决方案是将一段代码包装在另一个类中。 稍后,我将这种包装称为“可调用的”:
class OrderBeers
def call
# do something
end
end
class OrderTea
def call
# do something else
end
end
If.new(today_is_friday?, OrderBeers.new, OrderTea.new)
.result
.call
As you can see, the actual behaviour is not being executed until we explicitly use #call
. That's it. This is how we can execute complex code with our If
class.
如您所见,在我们显式使用#call
之前,不会执行实际的行为。 而已。 这就是我们可以通过If
类执行复杂代码的方式。
布尔值(仅因为我们可以) (Booleans (just because we can))
We already have logical values (nulls and everything else) but it would be nice for expressiveness to add explicit boolean values. Let’s do that:
我们已经有了逻辑值(空值和其他所有值),但是对于表达性而言,添加显式布尔值会很好。 让我们这样做:
class Bool < NormalObject; end
class TrueObject < Bool; end
class FalseObject < Bool
def if_branching(_then_val, else_val)
else_val
end
end
Here we have an umbrella class called Bool
, TrueObject
with no implementation (any instance of this object is already considered true) and FalseObject
that overrides #if_branching
in the same way NullObject
does.
在这里,我们有一个伞类,称为Bool
,没有实现的TrueObject
(该对象的任何实例都被视为true)和FalseObject
, #if_branching
以与NullObject
相同的方式覆盖#if_branching
。
That’s it. We implemented booleans. I also added logical NOT operation for convenience:
而已。 我们实现了布尔值。 为了方便起见,我还添加了逻辑NOT操作:
class BoolNot < Bool
def initialize(x)
@x = x
end
def if_branching(then_val, else_val)
@x.if_branching(else_val, then_val)
end
end
As you can see, it just flips parameters for the underlying object’s #if_branching
method. Simple, yet incredibly useful.
如您所见,它只是翻转基础对象的#if_branching
方法的参数。 简单,但非常有用。
循环 (Loops)
Okay, another important thing in programming languages is looping. We can achieve looping by using recursion. But let’s implement an explicit While
operator.
好的,编程语言中的另一重要内容是循环。 我们可以通过使用递归来实现循环。 但是,让我们实现一个显式的While
运算符。
In general, the while
operator looks like this:
通常, while
运算符如下所示:
while some_condition
do_something
end
Which could be described like this: “if the condition is true, do this and repeat the cycle again”.
可以这样描述:“如果条件为真,则执行此操作并再次重复该循环”。
The interesting thing to point out is that our condition should be dynamic — it should be able to change between iterations. “Callables” to the rescue!
需要指出的是,我们的条件应该是动态的-它应该能够在迭代之间进行更改。 “ Callables”进行救援!
class While < NormalObject
def initialize(callable_condition, callable_body)
@cond = callable_condition
@body = callable_body
end
def run
is_condition_satisfied = @cond.call
If.new(is_condition_satisfied,
NextIteration.new(self, @body),
DoNothing.new)
.result
.call
end
# Calls body and then runs While#run again.
# This way looping is done recursively (too bad no tail-call elimination)
class NextIteration < NormalObject
def initialize(while_obj, body)
@while_obj = while_obj
@body = body
end
def call
@body.call
@while_obj.run
end
end
class DoNothing < NormalObject
def call
NullObject.new
end
end
end
样例程序(Sample program)
Let’s create some lists and a function that counts how many nulls in a given list.
让我们创建一些列表和一个函数,该函数计算给定列表中有多少个空值。
清单 (List)
Nothing special here:
这里没什么特别的:
class List < NormalObject
def initialize(head, tail = NullObject.new)
@head = head
@tail = tail
end
def head
@head
end
def tail
@tail
end
end
We also need a way to walk it (no #each
+ block this time!). Let's create a class that will be handling it:
我们还需要一种行走的方法(这次没有#each
+阻止!)。 让我们创建一个将处理它的类:
#
# Can be used to traverse a list once.
#
class ListWalk < NormalObject
def initialize(list)
@left = list
end
def left
@left
end
# Returns current head and sets current to its tail.
# Returns null if the end is reached
def next
head = If.new(left, HeadCallable.new(left), ReturnNull.new)
.result
.call
@left = If.new(left, TailCallable.new(left), ReturnNull.new)
.result
.call
head
end
def finished?
BoolNot.new(left)
end
class HeadCallable < NormalObject
def initialize(list)
@list = list
end
def call
@list.head
end
end
class TailCallable < NormalObject
def initialize(list)
@list = list
end
def call
@list.tail
end
end
class ReturnNull < NormalObject
def call
NullObject.new
end
end
end
I think the main logic is quite straightforward. We also needed some helper-runnables for #head
and #tail
to avoid null-pointer errors (even though our nulls aren't actually nulls, we still risk calling a wrong method on them).
我认为主要逻辑很简单。 我们还需要一些用于#head
和#tail
helper-runnable来避免空指针错误(即使我们的空值实际上不是空值,我们仍然有可能对其调用错误的方法)。
计数器 (Counter)
This is just an increment that will be used for counting:
这只是用于计数的增量:
class Counter < NormalObject
def initialize
@list = NullObject.new
end
def inc
@list = List.new(NullObject.new, @list)
end
class IncCallable < NormalObject
def initialize(counter)
@counter = counter
end
def call
@counter.inc
end
end
def inc_callable
IncCallable.new(self)
end
end
We don’t have any numbers and I decided not to waste time implementing them so I just used lists instead (see my post on implementing numbers here).
我们没有任何数字,因此我决定不浪费时间实施它们,因此我只使用列表(请参阅此处有关实现数字的文章)。
An interesting thing to note is #inc_callable
method. I think if we are to try and implement our own "language" with those basic classes, it could be a convention to add methods with_callable
postfix to return a "callable" object. This is somewhat like passing functions around in functional programming.
需要注意的有趣事情是#inc_callable
方法。 我认为,如果我们尝试使用这些基本类来实现自己的“语言”,则可能会添加一个带有_callable
后缀的方法以返回“可调用”对象的约定。 这有点像在函数式编程中传递函数。
计算列表中的空值 (Counting nulls in list)
First of all we need a null-check. We can incorporate it within NormalObject
and NullObject
as a helper #null?
(similar to Ruby's #nil?
):
首先,我们需要一个空检查。 我们可以将其合并到NormalObject
和NullObject
作为辅助#null?
(类似于Ruby的#nil?
):
class NormalObject < BaseObject
def null?
FalseObject.new
end
end
class NullObject < BaseObject
def null?
TrueObject.new
end
end
Now we can finally implement our null-counter:
现在,我们终于可以实现空计数器了:
#
# Returns a counter incremented once for each NullObject in a list
#
class CountNullsInList < NormalObject
def initialize(list)
@list = list
end
def call
list_walk = ListWalk.new(@list)
counter = Counter.new
While.new(ListWalkNotFinished.new(list_walk),
LoopBody.new(list_walk, counter))
.run
counter
end
class ListWalkNotFinished < NormalObject
def initialize(list_walk)
@list_walk = list_walk
end
def call
BoolNot.new(@list_walk.finished?)
end
end
class LoopBody < NormalObject
class ReturnNull < NormalObject
def call
NullObject.new
end
end
def initialize(list_walk, counter)
@list_walk = list_walk
@counter = counter
end
def call
x = @list_walk.next
If.new(x.null?, @counter.inc_callable, ReturnNull.new)
.result
.call
end
end
end
And that’s it. We can pass any list to it and it will count how many nulls that list has.
就是这样。 我们可以将任何列表传递给它,它将计算该列表有多少个空值。
结论 (Conclusion)
Object-Oriented Programming is incredibly interesting concept and, apparently, very powerful. We’ve, essentially, built a programming language (!) by using only pure OOP with no additional operators. All we used was class definitions and variables. Another cool thing is that we have no primitive literals in our language (e.g. we don’t have null
, instead we just instantiate NullObject
). Oh, wonders of programming...
面向对象编程是一个非常有趣的概念,并且显然非常强大。 本质上,我们仅通过使用纯OOP而没有其他运算符来构建了一种编程语言(!)。 我们只使用类定义和变量。 另一个很酷的事情是,我们的语言中没有原始文字(例如,我们没有null
,而是实例化了NullObject
)。 哦,编程的奇迹...
The code is available in my experiments repo.
该代码在我的实验存储库中可用。
Originally published at https://weird-programming.dev on September 17, 2020.
最初于2020年9月17日发布在https://weird-programming.dev 。
翻译自: https://medium.com/swlh/programming-only-with-classes-7005630f0e2b