The only OOP that you can't be ashamed of | Libraries

Im crying

I just see people using OOP all the time without chaining inheritance. I think I don’t care if a person chose to use metatables when they need to inherit 25 objects from each other. It’s more about why they chose to do it, but that’s not the point of the post.

I just see people using OOP all the time without chaining inheritance.
I mean, of course, there’s first-level inheritance from the prototype object (“class”) to the object
However, in the context of real OOP, this is not fully inheritance.

The point is that you don’t need metatables in such cases.
People just shove them wherever they want, as long as their function is in the table. It gets to the point where people write methods that no one should see right there, without even realizing that they could write a separate function that accepts self as 1 arg

I think it should be obvious to anyone who has been writing code for at least a year that OOP is nothing more than a wrapper around prototype.method(object)

Why it instead of metatables

  1. Faster creating for objects
  2. Faster method using
  3. Easier to juniors-
  4. less code
  5. Easier to make types with this
    and a lot more!

So, the concept is based on a singleton. Yes, it’s the same as Roblox services or libraries like string or vector.
Essentially, a singleton is an object that is unique in its kind. Any table that does not duplicate is likely to be a singleton. It does not even require a constructor, simply using {anyKey = anyValue}. Even this is a singleton, and therefore an object, and any table is an object

but what do I mean by library?

A library is a table that is a collection of functions.
But these functions can also include a constructor. vector.create(), buffer.create()
Let me show you.
[https://create.roblox.com/docs/reference/engine/libraries]

local Gun = {
	create = function(Damage, MaxAmmo)
		return {
			Damage = Damage,
			Ammo = MaxAmmo,
			MaxAmmo = MaxAmmo,
			IsReady = true
		}
	end,
	
	Shoot = function(gun)
		if not gun.IsReady then return end
		
		gun.IsReady = false
		gun.Ammo -= 1
		print("BOOM!")
		
		task.delay(0.5, function()
			gun.IsReady = true
		end)
	end,
}


local gun = Gun.create(10,12)
Gun.Shoot(gun) -- "BOOM!"

Let’s also add types, which is a headache when using setmetatable.
In fact, it doesn’t really matter anymore, as the type is automatically inferred. Regardless, when everything is typed, it becomes much easier to write code.

type gun = {
	Damage: number,
	Ammo: number,
	MaxAmmo: number,
	IsReady: boolean
}

type Gun = {
	create: (Damage: number, MaxAmmo: number) -> gun,
	Shoot: (gun) -> ()
}

and then we add typing:

  1. local Gun: Gun = { ... }
  2. Shoot = function(gun: gun)

Even if you don’t like this method, you’ll probably agree that it’s a breeze to use it for GUI animations, as you don’t have to rewrite anything.

In fact, this is very similar to how I do it in C. I find it much less cumbersome than setmetatable
ask questions if anything

7 Likes

Using metatables in general is strange. Luau is not class oriented, it’s prototype oriented. There’s no need to think about perfomance at first. Metatables are generally wrong in Lua and Luau because they provide class oriented OOP. Furthermore, it’s not OOP, it’s more of procedural programming.

No it’s not. It is a lot easier to use Object:Method() rather than library.Method(obj) (unless you meant creating a “library” outputs shorter and easier to understand code)

Misconception of what “OOP” is but eh, if that’s how you interpret it then alright.

That is a fact. Sadly all of beginning people mistake OOP (due to Luau looking “easy”) and do a lot of mistakes in terminology and code which gives extra overhead. 99.99% times of using metatables is not needed at all.

4 Likes

Yeah I made the mistake on relying alot on oop for no reason, to the point where I got into the habit of creating god objects. But I recentley started to stray away from oop and only use it if its really nessecary. But yes its such a easy mistake to fall into due to there not being alot of information on the downsides of metatables.

Maybe there is more now today but 2 years ago I had no idea that relying on metatables 24/7 was just such a bad idea because no one told me the downsides of it.

It’s still leaning towards prototype-based regardless. Everyone uses “class” terminology due to similarities with other languages with explicit class syntax. Metatables are never “wrong” to use.

There’s also nothing wrong with designing systems incorporating OOP. Using strictly one paradigm is the true wrong thing to do. Stuff like pure ECS doesn’t fit for Roblox games, but it’s neat for other specific stuff. The trap most people fall into is going into a project thinking about how they will model the data instead of implementing behavior and letting the data arise naturally around that. Whether that be OOP or a more data-oriented approach.

There’s no perfect way to code.

1 Like


Don’t do deep inheritence chains.

Personally I only use first-level inheritance, because that’s all I need, if any more is needed, then I’ve messed up somewhere during the design. It saves on a bit of memory and makes inheritence easy, whilst still being pretty cheap.

P.S.: I don’t shove OOP everywhere, I only use it when it makes sense to — objects.

Also not sure where the “metatables are hard to typecheck” sentiment is coming from.
This works perfectly fine:

local object = {
	foo = function(obj: Object): ()
		print(obj.Name)
	end
}

object.__index = object -- could separate metamethods into separate table or use UDTF to prevent this from showing in autocomplete

type Object = setmetatable<{
	Name: string,
}, typeof(object)>

local function newObject(name: string): (Object)
	return setmetatable({Name = name}, object) 
end

I can’t click on the quote button, so I’ll just number the answers
0. Any table, as I wrote, is an object, so whenever you use tables, you’re using the OOP paradigm. In functional programming, it would be if you got rid of tables and just imported functions. It’s difficult to do this in module format. But yes, if we put aside the terms, its FP style

  1. I was referring to creation, use, and understanding, and in two of those, this method is undeniably superior, and in use, it’s barely a bit more complex, simply because we write classes more often than we use libraries.
  2. I probably didn’t express myself correctly when I wrote this about the whole OOP. I meant it in the context of Lua.
    And in it, I’m right, obj:Method() is nothing more than proto:Method(obj) because __index works exactly like that

I spammed the term “prototype” a few years ago, and people had to explain the difference, but I didn’t want to spend time on it. There’s nothing wrong with calling a prototype a class if the post doesn’t explain the terminology. A prototype is an object, but a class isn’t. People who have only seen TS and Luau don’t need to understand the difference. I already have a post about it.

I saw you on ds server, I think.

No. Tables are not essentially only object. Tables are data stuctures which can be labeled as objects if they meet the criteria.

local function map(tbl, fn)
  local result = {}
  for i, v in tbl do
    result[i] = fn(v)
  end
  return result
end

local numbers = {1, 2, 3, 4}
local doubled = map(numbers, function(x) return x * 2 end)

This isn’t OOP but functional programming. However, by your definition, it would be OOP.

Functional Programming is not equal to not using tables at all. non OOP separates data and behavior, while OOP unites them in objects which can be represented through tables. That is their key difference. Furthermore, I wasn’t talking about FP.

__sub, __add, __iter, __newindex and etc.? __index is not the only thing programmers use in Luau OOP. vector, as an example, uses __sub, __mul, __div, __add and etc. Surely they are implemented in C++ but it doesn’t change the fact that they’re declared in luau as well, thus used in luau OOP.

It’s not OOP since object’s behavior and data is separated.

If it’s any more complex => it’s not easier as declared in initial post.

cool

The true OOP is lack of OOP.
Stop having a small worldview.

Cut ze fluff, eat ze bug :raised_fist:

https://devforum.roblox.com/t/%CE%B4-the-lost-art-of-optimization-what-do-we-gain-when-we-drop-oops-training-wheels/4123198

3 Likes

vector or Vector3?
I’m pretty sure vector is just hardcoded in VM rather than having a metatable-
NVM :skull:

local a
xpcall(function():()
	return (vector.zero::any)[nil]
end,function():()
	a = debug.info(2,"f")
end)
warn(a)

image

NVM again, it’s an error function; the vector is truly hardcoded, it seems.

image



Although I was wrong, the arithmetics are hardcoded

1 Like