ID:1687783
 
Warning! Highly debatable topic incoming!
(Also there might be some inconsistency in the post. This might be just ranting)


BYOND was my first Object-Oriented language. Before that I had tinkered around with BASIC-variant languages. The differences between these two are their paradigms. BYOND uses the object-oriented approach, whereas most BASIC-styled languages use the imperative approach. What are the differences between the two?

Imperative:
  • Mainly based on functions and subroutines
  • Most entities are defined on the global scope
  • Top-down language
  • Code flow is easy to understand

Object-Oriented:
  • Organized structure of code
  • Inheritance, polymorphism, object trees, etc.
  • Objects can contain fields and methods
  • Event-based


I started late as a programmer (around 17-18). I guess it just didn't really click for me until I tried out a little program called Play Basic. It started to make sense to me because code was extremely easy to understand at the time. The top-down approach made it look like I was reading a script.

When I started using C++, BYOND, and Java, I was awakened to the world of OOP. But I had a really hard time with it (and still do). It's great to think of things as objects and allow them to have methods and variables attached to them, and OOP makes use of scopes in an excellent way so that objects and variables outside of the scope naturally get deleted.

Biased argument starts here.

What I have a hard time with, however, is defining large object trees, and subtypes in which there's many different types (like creating 100 different swords, for example). In BYOND especially, if you'd want to use the map editor for most things, you'll need to define it as an object derived from /atom first. Only then would you be able to place it on the map.

I cannot seem to grasp object-oriented programming because I feel like objects should be much more flexible. I feel like making subtypes of subtypes of objects is very confusing . Variables (in most languages) are rigid. Once the code is compiled, variables cannot be added or removed. Same goes for methods.

In a procedural language, defining an object and manipulating it would look something like this:
var d = CreateItem()
SetItemName (d, "Fancy Necklace") // no type casting necessary here
SetItemValue (d, 100)
Print (GetItemName(d))

In an object-oriented language:
item d            = new item()
d.name = "Fancy Necklace"
d.value = 100
Print (d.name)


The neat thing about the procedural paradigm is that data doesn't have to be stored in an object. It can be stored in arrays and lists. Object data can be structurally organized into lists of lists as well, which makes the "object" malleable. data can be added and removed from it as well.

This sort of stemmed up from making this little thing. I wanted an object that was easy to work on, and I could grab data from it without needing to create a type in code for it. Not only that, but it saves to plaintext and I can edit the data as I see fit. And then I realized I really missed working with Basic-styled languages. because object-oriented aspects were added simply to SUPPORT the platform instead of being the foundation of it.

I often have a hard time finishing a project simply because I don't know how to program it in object-oriented style. That, and I guess I just dislike the way code looks using this method. It seems restricting to use objects for everything. But don't get me wrong. There's a lot of things about this paradigm that I absolutely love. For example, objects are great for holding data in a structured manner. "Item.name" in the exmaple above is a perfectly acceptable field. I just don't like when object trees happen to get too deep like:

Item -> EquipItem -> Weapon -> Sword -> SomeSword1

While SomeSword1 still retains the "name" variable, it suffers from being a child object so specific that I'd need to do the same thing for all other similar objects:

Item -> EquipItem -> Weapon -> Sword -> SomeSword1
Item -> EquipItem -> Weapon -> Sword -> SomeSword2
Item -> EquipItem -> Weapon -> Sword -> SomeSword3
...

What say I don't like the long type path (and we'll just say we're using DM in this case):

Item/EquipItem/Weapon/Sword/SomeSword1

If I wanted to initialize this item:
proc/GetSomeSword()
return new/Item/EquipItem/Weapon/Sword/SomeSword1()


Even if I happened to use parent_type to shorten the type path:
Item
parent_type = /datum // redundant, but shown here for consistency.

EquipItem
parent_type = /Item

Weapon
parent_type = /EquipItem

Sword
parent_type = /Weapon

SomeSword1
parent_type = /Sword

mob/verb/EquipSword()
contents += new/SomeSword1()



What if I decided to use the imperative approach?:
item
var/name = ""
var/subtype = "" // multi-use string that contains the "type" of item.
var/list/variables = list("something"=1, "anotherlist"=list()) // associative list to hold object data.

proc/CreateItem(N="SomeItem",S="helmet")
var/item/i = new()
i.name = N
i.subtype = S
return i

proc/EquipItem(mob/M, item/I, S)
if (I.subtype == S)
M.equipslots[S] = I


TL;DR: I like Imperative over Object-Oriented.
Mr_Goober wrote:
I like Imperative over Object-Oriented.

I enjoy a mixture of the two.
What I have a hard time with, however, is defining large object trees, and subtypes in which there's many different types (like creating 100 different swords, for example).
I don't see a problem with having a lot of different types, but the people who do have brought it up before.

In BYOND especially, if you'd want to use the map editor for most things, you'll need to define it as an object derived from /atom first. Only then would you be able to place it on the map.
Problem?

I just don't like when object trees happen to get too deep like:

Item -> EquipItem -> Weapon -> Sword -> SomeSword1

While SomeSword1 still retains the "name" variable, it suffers from being a child object so specific that I'd need to do the same thing for all other similar objects:

Item -> EquipItem -> Weapon -> Sword -> SomeSword1
Item -> EquipItem -> Weapon -> Sword -> SomeSword2
Item -> EquipItem -> Weapon -> Sword -> SomeSword3
...
I don't understand where the suffering comes from. Your objects are suffering from being organized and that's a problem?

The point of inheritance is so you don't give the procs to things that don't need them, but when you make every single proc global, then I guess it's a completely different story, huh?
The way I see it, all object-oriented languages are actually procedural at their core. This means that they can do anything a procedural language can, with the added benefits of objects. Unfortunately, many object-oriented languages are in denial of their true procedural nature and try to hide this fact from developers. One of the major features that separates object-oriented languages from strictly procedural ones is encapsulation. I like to think of encapsulation as the security system of object-oriented languages, and without it, they would not be much different from procedural ones. I'm always looking for new ways to get around encapsulation, so that my objects can communicate freely with each other.

I notice that you seem to be generalizing object-oriented languages, since not all of them suffer from the type path issues you have brought up. Some languages have what are known as metaclasses, which are basically classes that serve as templates for object classes. This allows classes or types themselves to be created and changed at runtime. Object-oriented languages with reflective features like this can bring out their inner procedural power.

My favorite objects are ones that are procedurally generated, especially since they more accurately reflect the real world.
After doing a bit of thinking, I realized that other tools that I've used are also procedurally-oriented, such as Game Maker (GML uses a procedurally-oriented scripting language).

I can agree with you that the OO paradigm's encapsulation makes it more difficult for objects to communicate with each other. It also makes the compiler and interpreter more strict about how to evaluate objects.

I've decided to try a different approach and use associative lists to create my objects. Here's an example of a creation function for a sprite:

proc/Sprite_Create()
var/d = Object_Create()
Object_Set(d, "position/x", 0.00)
Object_Set(d, "position/y", 0.00)
Object_Set(d, "icon", null)

var/image/a = new()
a.loc = locate(1,1,1)

Object_Set(d, "image", a)
Object_Set(d, "clients", new/list())
return d


I've also noticed that when I'm using function sets like these, I don't really have to type-cast variables in order to use the referenced members of an object. That's because objects in the modern sense don't exist in this paradigm. Instead objects are built on the fly and referenced using commands to grab their values, such as var/d = Object_Get(TheObject, TheVariableName)

EDIT:
Function calls like Object_Set(D, "position/x", 0.00) when using "/" in the string are actually creating directories (or a nested list) so that it holds data like this:
sprite
position
x = 0.00
y = 0.00

If anyone wanted to, one could grab the list using Object_Get(D, "position") This returns the associative list list("x"=0.00,"y"=0.00).