Functional versus Object-Oriented: help me understand the purpose of it all

For the past several years I’ve been coding on Roblox, my approach has been purely of functional programming. I wanted to get into OOP and using class-based systems, but a friend of mine says that it’s not worth the hassle for classes. With that, I wanted to open this thread and ask about class based systems with some questions. I understand answers may vary depending on who is being asked, but personal experiences are enough for me to get the point and try something new.

  • What is the benefit of using a class-based system or OOP over functional programming?
  • Where should I use class-based systems? Where should I use functional programming?
  • How can I avoid this supposedly “evil concept” of “premature optimization”? What even is it?
  • How do I get started on creating a class-based system?

I tried to get into modules so I could possibly train into OOP/class-systems, but it hasn’t worked. I’ll probably require an explanation as if I was 5 years old. I would also appreciate some tips on that thread - it’s about using modules in code.

Often at times people say “use modules” but I don’t understand why. You can achieve the same thing that a module can do in regular scripts.

  • What’s the purpose of modules?
  • Why use modules?
  • How do I use modules?
  • What’s the purpose of stringing modules to make a programming system or game engine?

My own implementation literally just recursively requires things and it seems pointless. Modules seem pointless to me. At the same time, I want to try improving my code base so it doesn’t only not look bad, but so it’s actually efficient, well-rounded, easy to use and not too hard to maintain.

Essentially: my mind is wrought with thoughts about how I’ve set up my code base and how I code and I feel like I need to learn something different or learn it all again. I’d like to know functional versus object-oriented in as much depth as I can so I can determine how I move forward. One thing that prevents me from developing is stupid insecurity about my code.

10 Likes

The simple answer is that OOP is more expandable. If you spend time making everything Object Oriented it makes everything a lot cleaner and requires less code overall. You should generally use classes whenever you’re going to be repeating the same idea over and over.

Premature optimization is essentially putting the horse before the cart. If you design a system and optimize as you go you’ll end up with spaghetti code that’s unreadable and it’s a lot harder to maintain.

As for how you get started with class based systems, that’s a bit complicated and I’ll explain it below.

The main reason for using modules is similiar to why you would use class-based systems: it reduces repeated code and makes it cleaner. Modules also come with the added bonus of only running once (when they’re originally required) which means that resources that would be spent running it multiple times aren’t. Because modules exist in their own seperate scripts, they’re easier to maintain and update because updating that one script will update all the scripts that use that module as well.

One of the important things to be wary of with module development is dependency hell. That is, making modules that require other modules that require other modules and so on.

Consider the basic Instance system Roblox uses. When you create a new Part, you don’t have to worry about establishing a bunch of functions and properties for it because it already has them. It also shares some of these with other Instances (try print(Instance.new("Part").Destroy == Instance.new("ScreenGui").Destroy)). Hopefully you can see how this makes code more readable, as it would be a pain to have to put a function to destroy Instances into every script you made.

And now, class based systems, as a basic idea. This assumes you have knowledge of metatables and how Lua types work; if you don’t, the developer hub and Lua’s manual are great resources for that

The basic setup for a class based system is creating a basic table and a function to make a new copy of. The best and most common way of doing this is with metatables although you can do it a few other ways as well. Here’s a basic annotated setup:

local Class = {} -- This table holds all the functions, properties, and constructors for the new class

function Class.new() -- Make a new Class
   local NewClass = setmetatable({}, {__index=Class}) -- A new table is made with its __index metamethod as the Class table
   -- At this point, you could set up whatever you wanted with the new class.
   return NewClass -- The new class is returned
end

function Class:foo() -- As an example of how this classes inherit themselves
   print("bar")
end

return Class

If you called Class.new in another script and tried Class:foo(), it would print out bar. You could do this in any number of scripts (or even multiple times in the same script) and it would do the same thing. However, all the created classes would be unique tables, allowing you to change properties and things in them.

Some notes:

  • Because of how metatables work, you should always define properties for the new instance of a class within the .new constructor function.
  • Functions defined with a table using : instead of . have a natural ‘self’ variable placed within their scope that references the table that it’s being called on. This can be used to manipulate that class specifically instead of all classes.

I know this has been long and I’m not the best at explaining things, so if you have any questions, please ask! I would recommend Roblox’s StudioWidget library for examples of OOP code in action.

14 Likes

This was a very helpful explanation.

So what I attain from this is that OOP is cleaner to the eye, requires slightly less maintenance over functional code and you can reuse parts of code whereas in functional you’d have go to through the tedious process of constantly updating every similar line of code when changing a function or so, correct? As well as the fact that the script only runs once upon being signaled to via require, this saves resources that are better allocated elsewhere or saved up. I’m curious to know why modules to do all this, when I can have a single script store reusable functions in something like _G or shared.

Where do metatables play in the setup of classes? The most I know is that you can set functions in a module and then attach those to a table which would serve as your class… I don’t know much else from that. Also, another question - what happens if you need to work with classes that inherit from another (superclasses, subclasses, etc)?

I don’t know if this is related, but in a module-based system I tried to make, I ended up having a circular require which broke both scripts. Would a class-based system solve this issue?

Also, how should I consider setting up my code? For one of my on-going projects, I tried to mashup functional and modules to achieve an engine-like system in an attempt to understand how to utilise OOP and modules. I haven’t been able to differentiate what can be used several times over something that is just used once, so it’s become quite an unsightly mess.

image

-> ReplicatedStorage
 -> Remotes - Folder with RemoteEvents, I generally stay away from RemoteFunctions
 -> SharedData - Modules that return a table of values that I may use constantly
 -> SharedWidgets - Modules for repeated code that I use on both the server and client
-> ServerScriptService
 -> Modules - Literally the same as SharedWidgets, except only the server can use them
 -> Scripts - Functional code hell, code that I didn't put in modules or code that use modules
-> ServerStorage
 -> Resources - Cluttered mess with zero organisation, though irrelevant to the topic
1 Like

All of that is correct, yes.

In terms of why you shouldn’t use _G or shared thats mostly because it’s bad practice to store things in a global environment. The more that’s there, the more likely it is something will be overwritten or overlooked. That being said there’s no real functional difference between storing something in modules versus those tables. Modules are just self-contained.

The metatables in this case come into play because it’s easier than manually defining references to every function in a class. Otherwise they’re unnecessary.

Super and subclasses can be done in a variety of ways but I prefer iterating through all of the members of the superclass to add them to the subclass. If you use next it can be done with very little performance issues.

Circular module requiring shouldn’t be an issue in a proper class system. It’ll be purely hierarchical so cross requiring should be easily avoidable.

An easy way to tell what can and cannot be used multiple times is to check what if you need to modify a function or table, or if it relies upon internal script variables to function or not. If the answer to either of those is yes you should probably just put it in your normal script.

3 Likes

You wouldn’t have happened to see my most recent rantings on how terrible OOP is? Well, before I begin that, I’d like to make some corrections ^.^

While OOP can do this for you, so does functional programming. In fact, I think it is easier to do with functional programming. Functional programming allows reusing functions just like OOP reuses classes. You can think of functional programs as a library with a single function designated as the start. When a couple lines of code look similar in different functions, they are broken out into their own function to be reused.

Functional programs are easier to design because you can build and easily refactor and optimize as you go without painting yourself into a corner with a specific class architecture. Functional programs are simply functions made up of functions, but OOPs are objects with associated functions which work by calling functions associated with other objects. Having to organize your project in terms of artificial objects means that your code no longer fully resembles the underlying data. OOP leads to poor locality of reference and cache optimization.

Your friend sounds like an experienced programmer. A decade ago OOP was the cat’s meow, and the hype was real. While still being taught in universities and applied in industry, even the US government is trying to get away from it now (says one of my professors who works on a base). OOP favors more traditional workflows, like waterfall, where functional programming with frameworks tends to be more agile friendly. What this means to you is that unless you want to spend large amounts of time planning out every detail of how your application is going to work before you start coding it, functional programming with a framework is better. I’d recommend using an Entity-Component-System (ECS) framework. I’ve noticed as of late that many developers on this forum have ECS implementations. Here are the ones that I know of:

@HaxHelper WorldSmith
@IdiomicLanguage ECS.lua (edit: fixed link)
@magnalite (code not public)
@Tiffblocks (senior Roblox employee) RobloxComponentSystem

Some other Roblox or Lua related ECS implementations:
Sergey (senior Roblox employee): ECS (c++)
twin1twin2: Roblox-ECS-Framework
soccerstar: Entity-component system

(Let me know if I missed your implementation!)

Although not my favorite, another framework commonly used on the Roblox platform (in fact, this is what the new Lua games, home, and game details pages on mobile are written in) is Roact. It is a port of Facebook’s Javascript React framework. React is for websites what ECS is for game engines. If you want a framework for OOP, MVC is common in the industry.

11 Likes

A short word of warning: WorldSmith isn’t quite production-ready, and despite being (mostly) stable, there exist quite a few nasty bugs in the latest public release. However, I am very close to fleshing out the full feature set and completely quashing those bugs, so stay tuned…

1 Like

The ECS system you linked (I’m looking specifically at WorldSmith) right now looks to follow that general OOP/class-based system that I’ve commonly seen though. I also assume that WorldSmith is supposed to be contained within a module and that requiring it returns an entity which can further be utilised by the methods and such it possesses.

local A = {}
A.__index =  A

function A.new()
    return setmetatable({}, A)
end

return A.new()

So really, I’m still at a loss here. There is still modules involved. Is this not a class or OOP? Where do I appropriately determine the divide between where I should be using modules/OOP and where I should be keeping functional programming? At the very least right now I have modules but I don’t think I’m using them properly. The most they do is hold constants that I use across the server and client, but I don’t have any OOP or class code on the go right now.

-- Example 1: Reusable Functions
return function()
    return true
end

-- Example 2: Table of Items
local Module = {}

Module.VariableInTable = true
Module.Yes = function()
    return "No"
end

return Module

--^ or
return {
    something = true,
    other = function()
        return false
    end
}

I would like to add that due to the nature of Roblox’s already OOP Instance and data type systems, it’s more viable and recognizable to use classes for data types simply because most people will be able to use them and recognize what they are.

Entity Component-Systems in general are wonderful because most systems are static and the challenge comes in updating them in a reasonable manner. With Roblox that’s significantly less of an issue because everything can be modified on the go with relatively minor performance issues and Lua is a dynamical language by nature.

The only real reason I would suggest using Roact is if you have experience with React and wish to port that knowledge to Roblox. Otherwise you’re better off doing things using the built-in methods and functions because they’re what most people use and they’re generally easier to understand.

The question you’re asking @colbert2677 is exactly why ECS is generally not great for actually designing a framework in Roblox. Lua itself is not optimized or designed for that particular workflow so you have to mimic it on a lower level that ends up being psuedo-class based.

EDIT: I’d like to clarify I don’t mean anything against Roact or ECS. I just don’t think ECS is good for someone who’s looking to make a framework in Roblox. Roact is useful for what it is and that’s wonderful. I would also recommend using it if designing serious UI like the mobile app because it will help you move out into actual web development. I was more referring to Roblox development as a whole.

2 Likes

WorldSmith creates a singleton, a service. It is close to a class, but not quite. It contains state that needs to be initialized like in an object constructor, but it does not enable the use of inheritance and you cannot create multiple instances of it. The line does get a bit blurry at times, but all of WorldSmith could easily be implemented by moving all of the data to local variables in the module and removing the new function. Most modules, including non-OOP ones return a table that contains multiple functions. That doesn’t make it a class though.

One of the greatest advantages of ECS is its dynamic nature. It decouples your code. It also allows as wikipedia put it, “the behavior of an entity can be changed at runtime by adding or removing components.” Scott Bilas as the GDC 2002 gave a talk on ECS and listed these two advantages:

  • No programmer required for designers to modify game logic
  • Circumvents the “impossible” problem of hard-coding all entity relationships at start of project

It is about as flexible as you can get. Components take “Composition over Inheritance” to new heights and is responsible for many of these advantages. Here is an article that talks about the data oriented nature rather than object oriented nature of ECS:
https://www.richardlord.net/blog/ecs/why-use-an-entity-framework.html

An ECS implementation is built of three basic things:

  1. Entities (basically a number or string, a unique identity)
  2. Components (your data)
  3. Systems (your functions)

It should be emphasized that Rodux is being used by Roblox employees to design UI that runs on the Roblox engine. ECS is a commonly used modern game engine architecture, hailed much more efficient and flexible than traditional OOP engine designs. OOP in Lua isn’t going to be faster than ECS in Lua.

5 Likes

EntityManager is indeed a type of class (a singleton to be more precise), but the reason I did it that way is basically because I wanted to and because it’s an appropriate pattern for the context. In other words, it’s an implementation detail - not a logical necessity.

Not quite - EntityManager is intended to be required by the server and each client, and its main purpose is to hold data structures containing information about entities and components. Neither entities or components are classes; they are pure data, and this brings us to the crux of the matter:

ECS is about dealing with your game in terms of data, not objects. It’s quite a bit different than OOP (some even think that they are directly opposed). That being said, I encourage you to do further research!

Additionally, it seems like you may be experiencing analysis paralysis. Don’t worry about about creating the “perfect” or “most optimal” architecture. Go with what makes the most sense to you, and then refactor mercilessly once any problems with your initial design become apparent

3 Likes

Thus the psuedo- part of me calling it a pseudo-class. :sweat_smile:

This thread isn’t really the sort of place to get into the nitty gritty of it all because that would be derailing and insulting to Colbert. That being said, I have my opinion and as you said, one isn’t going to be faster than the other. Lua is just built to go into OOP easier in my experience. There’s syntactic sugar for it and the very nature of Lua tables make it easier to deal with.

The beautiful part of a dynamically typed language like Lua of course is that you can have the best of both worlds because there’s nothing stopping you from having classes you modify on the fly. It may not be the ideal way of doing it because it will be incomprehensible to most people, but if it works then it works.

3 Likes

Basically the whole purpose of frameworks, modules, and OOP is to help you organize your code according to best practices:

  1. Low coupling (code isn’t dependent upon other code)
  2. Information hiding (know what, not how)
  3. Code Reuse (say no to the programer’s bane: ctrl-c)
  4. Least Privilege (related to low coupling, prevents accidents)

They break up your code into bit size chunks so you don’t have to comprehend your entire program at once while working on it. They reduce bugs, and provide a paradigm for thinking about your features. When working with multiple people, it helps provide some standardization and predictability. There is no one software architecture to rule them all, and they each have their place.

Here is some good reading on the topic:

Note that most OO and functional languages can implement an ECS framework, although I’d think ECS is cleaner (arbitrary, I know) in functional languages.

3 Likes

Explained like I’m 5, thanks. I’m just sitting here and looking over all the responses and I thank you guys for helping out.

So essentially, what I’ve gathered is that the use of modules is intended to break up your code into pieces and keep a sense of organisation, as well as given the fact that it’s easier to work with when working on other projects. From there, its up to how you write your code or style it, then? I feel like I’m far too over-analytical about how I organise and write my code and that I may be over-complicating a simple idea. I may be looking for a Code Review topic then, but this thread has been helpful enough for me to understand each individual coding style and a comparison against one another.

I don’t quite understand this, though. If you could elaborate, that would be awesome.

Thanks for your responses, everyone.

1 Like

There are lots of articles on information hiding, it is a common concept. Here is the Wikipedia article on it:

If I was to summarize it, I’d say that it allows a program to use a function or object without worrying about its implementation details. For example, in Lua we have tables which can store any value as a key. We don’t know how it does this (its implementation is hidden from users) but we know how to use it. Lua can be seen as a clean interface that hides the implementation details and register based machine under the hood.

2 Likes

Basically, yep! The only bad way to style your code is the way that makes it so you personally can’t read it. If you’re interested to know the best practices and generally accepted ways to do that, Code Review or Developer Discussion is probably the place for it. Otherwise, focus on whatever is easiest for your workflow.

Except if you put all your ends in one line. We will have words if you do that. :wink:

3 Likes

Imperative is the fastest paradigm for Lua hands down. Functional and object oriented paradigms are relatively slower because of limitations on the Lua compiler; other than that it doesn’t really matter what you use.

I have no clue what you said, at all.

1 Like

I was referring to your first concern regarding programming paradigms (functional vs oop). Since basically all design and style stuff was covered by the others, I’m just mentioning the performance differences in them.

No, not in that way. The terms you used without a given explanation are confusing to me. I’m only familiarised with Lua as per the Roblox sandbox of it (RbxLua).

  • What is the “imperative paradigm”?
  • Where does the Lua compiler come into play here?

Also, the question I originally posed isn’t only “which one should I use over the other”. I’m trying to get an understanding of each respective paradigm and how it operates.

1 Like

Paradigm refers to the style. Functional programming, object oriented programming, and imperative programming are 3 of them. Lua’s compiler just works better with imperative by a slight margin because of it being friendlier to how the language is built.

Imperative is just statement after statememt that changes what the program is doing.

Functional is an immutable state which the program interacts with using predefined function logic.

Object oriented involves each state having its own class behavior.

You could really use them interchangeably, but they do have their own strengths. Imperative is straight forward and good for dictating stuff like game logic, while functional is arguably better for building readable data oriented systems, and object oriented is usually for keeping track of actual entities and how they behave.

7 Likes