Hacky way of avoiding cyclic dependency to compose OOP objects inside each other and get the types? Is it possible to fix?

I looked and saw this hacky way of getting over cyclic dependency to get types into modules and was wondering if anyone can help me to get it working for OOP compositions???

The way to do it:
Example (signal.lua):

local package = script.Parent.Parent
local packages = package.Parent

type SignalModule = typeof(require(packages.signal))

local getModule = require(package.utilities.getModule)
local signalModule = getModule("signal")

-- i hate this
export type Connection = typeof((function()
	local module = require(packages.signal)
	return {} :: module.Connection
end)())

export type Signal<T...> = typeof((function()
	local module = require(packages.signal)
	return {} :: module.Signal<T...>
end)())

return require(signalModule) :: SignalModule

Car.lua

local Engine = require(script.Engine)

local Car = {}
Car.__index = Car

type self = {
	["_Engine"]: Engine,
	Speed: number
}

type Car = typeof( setmetatable({}, Car) )

export type Car = typeof( setmetatable({} :: self, Car) )

function Car.new(): Car
	local self = setmetatable({} :: self, Car)
	self._Engine = Engine.new()
	self.Speed = 15
	return self
end

function Car.Boost(self: Car): ()
	self.Speed += 50
end

return Car

Engine.lua

export type Car = typeof((function()
	local module = require(script.Parent)
	return {} :: typeof(module.new())
end)())

local Engine = {}
Engine.__index = Engine

type self = {
	Speed: number
}

type Engine = typeof( setmetatable({}, Engine) )

export type Engine = typeof( setmetatable({} :: self, Engine) )

function Engine.new(Car : Car): Engine
	local self = setmetatable({} :: self, Engine)
	self.Speed = 15
	return self
end

return Engine

What am I missing?

Here in the parameter (Car : Car) just resolves to any type when it should resolve to Car type:

function Engine.new(Car : Car): Engine
	local self = setmetatable({} :: self, Engine)
	self.Speed = 15
	return self
end

More info: Guide to Type-Checking with OOP

@MagmaBurnsV posted about this method and I wanted to adopt it so that I can compose objects inside of each other. Anyone have any ideas please? Any help is appreciated, thanks!

You could establish a new module and put all your types into that module so it can be used across all your classes

*Edit

Also im quite confused why you would need to pass in the car class into the engine class as it isnt currently being used

I am having trouble understanding what you mean. Is this possible???

Types.lua (has the types):

local module = {}

local Engine = {}
Engine.__index = Engine

type EngineSelf = {
    HorsePower: number
}

type Engine = typeof(setmetatable({}, Engine))
export type Engine = typeof(setmetatable({} :: EngineSelf, Engine))

function Engine.new(): Engine
    local self = setmetatable({} :: EngineSelf, Engine)
    self.HorsePower = 15
    return self
end

module.Engine = Engine

local Car = {}
Car.__index = Car

type CarSelf = {
    _Engine: Engine,
    Speed: number
}

type Car = typeof(setmetatable({}, Car))
export type Car = typeof(setmetatable({} :: CarSelf, Car))

function Car.new(): Car
    local self = setmetatable({} :: CarSelf, Car)
    self._Engine = Engine.new()
    self.Speed = 15
    return self
end

function Car.Boost(self: Car): ()
    self.Speed += 50
end

module.Car = Car

return module

In Car.lua, how would I fetch the Engine type from that?

local Types = require(script.Parent.Types)
local Engine = require(script.Engine)

local Car = {}
Car.__index = Car

type self = {
	["_Engine"]: Engine,
	Speed: number
}

type Car = typeof( setmetatable({}, Car) )

export type Car = typeof( setmetatable({} :: self, Car) )

function Car.new(): Car
	local self = setmetatable({} :: self, Car)
	self._Engine = Engine.new()
	self.Speed = 15
	return self
end

function Car.Boost(self: Car): ()
	self.Speed += 50
end

return Car

It is a bit messy but it is a proof of concept. I am looking to compose objects in objects and just did that to test if it would work but I am trying to get it to work. What do you think of the above, can we do this to get the Types or did I do it wrong? Bit sloppy but I tried.

Sorry for the late response I kind of fell asleep

im pretty sure the Engine type would already be inside of car.lua if you used Engine.new() because Engine.new() returns an Engine object of the Engine type.

if your asking for the literal type you could just export the engine type

-- a module that holds all the types

export type Engine = {
    HorsePower: number
}

export type Car = {
    _Engine: Engine
    Speed: number
}

-- car module
-- if you want to fetch the engine type you could do it liike this

local types = require(--[[path to  type module]])
type Engine = types.Engine

Hey, I have been busy trying to get this to work without cyclic dependency. I have bumped into brick walls but I found a viable method so far and it seems like this is one of the more legit methods. I have not been able to utilize other ways.

With your method of creating a separate Types module or manually typing it up, it will result in a maintenance nightmare over time. I have tried it!

This way is more automatic:

Car.__index = Car
function Car.new(): CarType
    local self = setmetatable({}, Car)

    self.Engine = Engine.new(self) :: EngineType
    self.Speed = 25

    return self
end

function Car.Boost(self: CarType): ()
    self.Speed += 25
end

function Car.GetEngine(self: CarType): EngineType
    return (self.Engine)
end

export type CarType = typeof(Car.new())

Engine.__index = Engine
function Engine.new(Car: CarType): EngineType
    local self = setmetatable({}, Engine)

    self.Car = Car
    self.Health = 0
    
    return self
end

function Engine.Repair(self: EngineType): ()
    self.Health = self.Health + 25
end

function Engine.BoostToCar(self: EngineType): ()
    local Car = self.Car
    Car:Boost()
    
    return ("BOOSTED Car.")
end

export type EngineType = typeof(Engine.new())

Having a bunch of classes in one ModuleScript and separated and then creating separate ModuleScripts to return the specific classes by table:

For example extracting Car.lua class from the MegaSetOfClassesModuleScript.lua:

local Car = (require(script.Parent.ClassesModuleScriptThatContainsAllClasses).Car)

return Car

This ensures everything updates automagically and you do not have to manually type things!
You get the benefits too and this is totally legit.

I have not found another method but I do know that a resource exists that currently is trying to get Typed working so monitoring that:

We will see what happens. LMK if you have any other suggestions though and thank you!

Looks like you opted for the functional paradigm for this instead :face_holding_back_tears: not everything has to be OOP because it has a lot of limitations…… for future reference you should break down what your really trying to accomplish when making something and identify what you would need for that. And based off of that information you could code in procedurally, functionally, objected oriented or entity component.

What I have found in coding and scripting up to now is that there is seemingly always a limitation or drawback to a paradigm. No paradigm is really free of that so does not matter what you choose. You should just choose a paradigm that matches your thinking style.

In this case, I love thinking in objects and analogies so OOP is a great use case for me. Limitations? There can be some but atp another paradigm would just be a solution searching for a problem (there is no problem really, OOP is really flexible).

If I want to do any of the paradigms that you mentioned, well I don’t have to choose one or the other. I can choose them all.

1 Like