Programming only with classes
Post image

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.

But what if we had absolutely nothing, even basic operators like if and while? Get ready for some pure OOP-madness.

Ground rules

No if-operator? Seriously? Even CPUs have it!

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.

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”.

What about false? Let’s start with null, actually.

class NullObject < BaseObject
  def if_branching(_then_val, else_val)
    else_val
  end
end

Same thing but it returns 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:

class NormalObject < BaseObject
end

Now, everything we define later on should inerit from NormalObject. Later on we can add global helper methods there (like #null?).

If-expressions

This is enough for us to create our if-expressions:

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 without it our programs would be incredibly inefficient and even invalid.

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.

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.

That’s it. We implemented booleans. I also added logical NOT operation for convenience:

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 underlying object’s #if_branching method. Simple, yet incredibly useful.

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.

In general, the while operator looks like this:

while some_condition
  do_something
end

Which could be described like this: “if 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!

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:

#
# 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).

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.

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?):

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…

The code is available in my experiments repo.