OOP via FP : functional nature of classes and objects

oop-via-fp-:-functional-nature-of-classes-and-objects

Topic for today

Today I would like to tell you, why such OOP concepts as classes and objects are just particular cases of functions, using examples of code in Ruby and in my own language, Jena.
Why Ruby? Despite that it is an object-oriented language, it also provides a good foundation for functional approaches. In that, I hope, my article will convince you, though it is a secondary point today.

Jena references

If you are not familiar with Jena, you may read dedicated article about this language.
If you want to try Jena, you are welcome to visit its github repository.

Object as a table of functions

Let us watch on an example of a class declaration in Ruby :

class Human
  def walk
    puts "walking!"
  end

  def stand
    puts "standing!"
  end
end

h = Human.new()
h.walk
h.stand

Class is a popular idea, that has wide spread in modern languages, such as Python, Java, C#, C++.

What exactly do we see here?
Class Human seem to be a function, that creates objects.
But, also, object h here has two functions, and we may call them by their names.

May we try to implement the same behaviour, using only functions? Let us try :

def Human
  return {
    :walk => -> do puts "walking!" end,
    :stand => -> do puts "standing!" end,
  }
end

h = Human()
h[:walk].call
h[:stand].call

I feel important to explain, what I am doing here. Function Human creates a hash-map, where symbols are associated with
anonymous functions. Yes, :walk and :stand are symbols, it is a part of Ruby syntax, very uncommon in other languages.
As you may see, I chose Ruby for a reason. This language has one thing in common with Jena — symbol literals.

Honesty and clarity

More honest implementation of an object through a function would be this one :

def Human
  map = {
    :walk => -> do puts "walking!" end,
    :stand => -> do puts "standing!" end,
  }
  return ->key do map[key] end
end

h = Human()
h.call(:walk).call
h.call(:stand).call

Now we are not using a dedicated syntax, hiding object implementation behind a function. You may say, that Human class code does look better, and code with functions is verbose and complicated. It is true, because syntax of Ruby (and most of other object-oriented languages) is designed to make use of classes and objects easy, sacrificing for that ease and clarity of functions.

Let me demonstrate the same behaviour, implemented in Jena :

Human = () -> {
  .walk:() -> println "walking!",
  .stand:() -> println "standing!",
} =>
h = Human() => [
  h.walk(),
  h.stand(),
]

This code does exactly the same thing that the last Ruby code does : creates a Human function, that returns table of functions, calls it, storing result as h and, after that, calls walk and stand functions, taking them from table, using .walk and .stand symbols as keys.

Mutable state

You may say, that objects have a mutable state, and, sometimes, it may be very useful. Methods may change attributes of an object, but may functions do the same?
Short answer is yes. Let us write some Ruby code

class Human
  @x

  def initialize
    @x = 0
  end

  def moveLeft
    @x -= 1
  end

  def moveRight
    @x += 1
  end

  def status
    puts "x = #{@x}"
  end
end

h = Human.new()
h.moveLeft()
h.moveRight()
h.moveRight()
h.status()

How would look the same code in our approach, where object is a function?

def Human
  x = 0
  map = {
    :walkLeft => -> do x -= 1 end,
    :walkRight => -> do x += 1 end,
    :status => -> do puts "x = #{x}" end,
  }
  -> key do map[key] end
end

h = Human()
h.call(:walkLeft).call
h.call(:walkRight).call
h.call(:walkRight).call
h.call(:status).call

And, in Jena it would be implemented next way :

Human = () -> x = box(0) => {
  .walkLeft:() -> x.apply(-1),
  .walkRight:() -> x.apply 1,
  .status:() -> print ("x = " + (x.get)),
} =>
h = Human() => [
  h.walkLeft(),
  h.walkRight(),
  h.walkRight(),
  h.status(),
]

Dynamic languages, such as Ruby, provide us flexibility in control of program state, we don’t have to explicitly allocate memory for a mutable state, local variables already do present a state.

Performance

Our function-based objects have one serious flaw : low performance. We are building a functions table each time we create a new object, can we optimize that? Yes, of course we can, all we need is to separate an object state from functions table :

$table = {
  :walkLeft => -> obj do obj[:x] -= 1 end,
  :walkRight => -> obj do obj[:x] += 1 end,
  :status => -> obj do puts "x = #{obj[:x]}" end,
}
def Human
  state = { :x => 0 }
  -> key do
    -> do
      $table[key].call(state)
    end
  end
end

h = Human()
h.call(:walkLeft).call
h.call(:walkRight).call
h.call(:walkRight).call
h.call(:status).call

And Jena :

Human = table = {
  .walkLeft: obj -> obj.x.apply(-1),
  .walkRight: obj -> obj.x.apply 1,
  .status: obj -> print ("x = " + (obj.x.get)),
}
=> state = { .x:(box 0) }
=> () -> key -> () -> table key state =>
h = Human() => [
  h.walkLeft(),
  h.walkRight(),
  h.walkRight(),
  h.status(),
]

Looks overloaded? I agree. But, we may simplify that. Let us create a class factory function, so we may distract from building table and state functions each time we want to create a class :

Class = table ->
  constructor ->
  args -> state = constructor args =>
    key -> arg -> table key args state arg =>

Human = Class {
  .walkLeft: () -> obj -> () -> obj.x.apply(-1),
  .walkRight: () -> obj -> () -> obj.x.apply 1,
  .status: () -> obj -> () -> println("x = " + (obj.x.get)),
} args -> { .x:(box (args.x)) }  =>

h = Human{.x:5} => [
  h.walkLeft(),
  h.walkRight(),
  h.walkRight(),
  h.status(),
]

As you see, now we even can pass constructor arguments to build our object, it is a full complete object-oriented class, implemented by functions only.

Other reasons to choose functions over objects

For many of readers solution with classes looks more familiar and pleasant, but only because you already have experience using that solution.
Functions are coming from a mathematical world, their abstraction and distraction from implementation details are attractive indeed. Functions have simple and consistent concept behind, while objects are very specific and domain-oriented.
Finally, class in most of the object-oriented languages is a separate syntax, what brings more complexity and involves a special logic for constructors, methods and attributes.

That’s all for today. Thank you for reading, and please, share your thoughts in comments.

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
use-pgvector-for-searching-images-on-azure-cosmos-db-for-postgresql

Use pgvector for searching images on Azure Cosmos DB for PostgreSQL

Next Post
how-recommerce-startup-beni-uses-ai-to-help-you-shop-secondhand

How recommerce startup Beni uses AI to help you shop secondhand

Related Posts