Jump to content




Classic - pretty OOP in CC


38 replies to this topic

#21 thecrimulo

  • Members
  • 87 posts
  • Locationhere

Posted 06 July 2016 - 12:43 AM

 Kouksi44, on 05 July 2016 - 09:17 PM, said:

 Exerro, on 05 July 2016 - 08:07 PM, said:

 Lupus590, on 05 July 2016 - 07:12 PM, said:

 Kouksi44, on 05 July 2016 - 05:31 PM, said:

-snip-

any way to allow "/" instead of "." ?
why do you have to use "." ?

Lua's require() uses ".", and I personally prefer it as it's more namespacey and less path/to/file-ey.

On the topic of the library:

This is awesome! It's really similar to how I did classes in Sheets, so naturally I'd think it has nice syntax and functionality. However, this has the added benefit of the magic metatable stuff for giving properties to fields of the class, which I have to admit is really clever and looks almost native.

My one real reservation is the name 'init' for the constructor. I've wanted to use a function called 'init' to initialise various things of a class in the past, so I think it'd be much better to use the name of the class as a constructor (i.e. Button:Button() not Button:init()).

Glad you like it!

A previous version of classic used the classname for the constructor but I think i changed it because I thought it looked a bit weird to write a function called
 function Button:Button () 

However I might change this again in the next update.

One thing I just realized :

Right now, you should avoid using ClassTypes or declaring class members that hold Objects in general.
Accessing those members won't work as copying those Objects once the class instance is created is a bit buggy.

So I can't pass an object as argument? I have something like this:
...
class "Window" implements "IWindow" {
  prop1;
  prop2;
  noObjectHereReally;
}
function Window:init(prop1,prop2)
  self.prop1 = prop1
  self.prop2 = prop2
end
interface "IDecorator" {
  draw = function(self, windowObject) -- Its only required here
	-- Code using values from windowObject
  end;
}
class "Decorator" implements "IDecorator" {
  prop1;
  prop2;
  noObjectHereReally;
}
function Decorator:init(prop1,prop2)
  self.prop1 = prop1
  self.prop2 = prop2
end
It ruins a bit my work :/

Edited by thecrimulo, 06 July 2016 - 12:43 AM.


#22 Kouksi44

  • Members
  • 89 posts
  • LocationMunich, Germany

Posted 06 July 2016 - 01:46 AM

 thecrimulo, on 06 July 2016 - 12:43 AM, said:

 Kouksi44, on 05 July 2016 - 09:17 PM, said:

 Exerro, on 05 July 2016 - 08:07 PM, said:

 Lupus590, on 05 July 2016 - 07:12 PM, said:

 Kouksi44, on 05 July 2016 - 05:31 PM, said:

-snip-

any way to allow "/" instead of "." ?
why do you have to use "." ?

Lua's require() uses ".", and I personally prefer it as it's more namespacey and less path/to/file-ey.

On the topic of the library:

This is awesome! It's really similar to how I did classes in Sheets, so naturally I'd think it has nice syntax and functionality. However, this has the added benefit of the magic metatable stuff for giving properties to fields of the class, which I have to admit is really clever and looks almost native.

My one real reservation is the name 'init' for the constructor. I've wanted to use a function called 'init' to initialise various things of a class in the past, so I think it'd be much better to use the name of the class as a constructor (i.e. Button:Button() not Button:init()).

Glad you like it!

A previous version of classic used the classname for the constructor but I think i changed it because I thought it looked a bit weird to write a function called
 function Button:Button () 

However I might change this again in the next update.

One thing I just realized :

Right now, you should avoid using ClassTypes or declaring class members that hold Objects in general.
Accessing those members won't work as copying those Objects once the class instance is created is a bit buggy.

So I can't pass an object as argument? I have something like this:
...
class "Window" implements "IWindow" {
  prop1;
  prop2;
  noObjectHereReally;
}
function Window:init(prop1,prop2)
  self.prop1 = prop1
  self.prop2 = prop2
end
interface "IDecorator" {
  draw = function(self, windowObject) -- Its only required here
	-- Code using values from windowObject
  end;
}
class "Decorator" implements "IDecorator" {
  prop1;
  prop2;
  noObjectHereReally;
}
function Decorator:init(prop1,prop2)
  self.prop1 = prop1
  self.prop2 = prop2
end
It ruins a bit my work :/

Passing Objects as arguments works perfectly fine.
Trying to do the following would not work :
class "Test" {
  member = SomeClass () -- can't be accessed by an object
}
local obj = Test ()
obj.member:someFunction ()
"member" holds an instance of "SomeClass" during the creation of the instance. Every object gets its own copy of the classtable but adding already defined objects to an instance is buggy without using the same object for all instances.

This will be fixed in the next update.

#23 jv110

  • Members
  • 108 posts
  • LocationIn front of my PC

Posted 11 July 2016 - 05:06 AM

IMO
class "uh" {
  l = "sh";

  f = function(self)
    self.l = "f"
  end;

  git = function(self)
    self.l = "git"
  end;
}
looks a lot better than
class "uh" {
  l = "sh";
}

function uh:f()
self.l = "f"
end

function uh:git()
self.l = "git"
end


#24 thecrimulo

  • Members
  • 87 posts
  • Locationhere

Posted 11 July 2016 - 12:03 PM

 jv110, on 11 July 2016 - 05:06 AM, said:

IMO
class "uh" {
  l = "sh";

  f = function(self)
	self.l = "f"
  end;

  git = function(self)
	self.l = "git"
  end;
}
looks a lot better than
class "uh" {
  l = "sh";
}

function uh:f()
self.l = "f"
end

function uh:git()
self.l = "git"
end
Actually I prefer the second one more than the first

#25 Kouksi44

  • Members
  • 89 posts
  • LocationMunich, Germany

Posted 12 July 2016 - 03:12 PM

 jv110, on 11 July 2016 - 05:06 AM, said:

IMO
class "uh" {
  l = "sh";

  f = function(self)
	self.l = "f"
  end;

  git = function(self)
	self.l = "git"
  end;
}
looks a lot better than
class "uh" {
  l = "sh";
}

function uh:f()
self.l = "f"
end

function uh:git()
self.l = "git"
end

It doesn't matter wether you define functions directly in the class table or add them later on, both ways do work with the library.

I pushed an update to pastebin that should solve the problem with members holding objects of a class.

Using :
class "Test" {
  t = SomeClass();
}

does work now. Every instance of the class "Test" will hold an exact clone of any objects in the classtable.

Edit:
I just noticed that I left some helper functions uncovered in the mainpost.
E.g. if you ever need to check wether a table is an instance you can use the
 typeof(obj) 
function. That function will return "instance" if the passed table is an instance of a class. If it's not then it will simply return the type of the passed argument.
Another thing that was added recently is the
 someObject.invokeMethod(sMethod,...)
method (Must be called with a dot). It allows access to the internal Object functions that the library makes use of to manually set/get values and get member properties / types. I don't know if that function will be useful that often, but if the the neccessity to call internal Object functions should ever arise - here you go!
Just keep in mind that these internal functions are normally called from within the library and are able to break an object if they are used in the wrong manner.

Methods you can access through invokeMethod() :
Spoiler

Edited by Kouksi44, 12 July 2016 - 03:55 PM.


#26 Kouksi44

  • Members
  • 89 posts
  • LocationMunich, Germany

Posted 14 July 2016 - 01:52 PM

Bug Fix and some changes :

Trying to get a private member will no longer error and simply return nil. This is because of a change in the inheriting system.

Currently if you had a class structure like that,
Spoiler

setting the publicMember through an object of SubClass resulted in a new field in SubClass that held the new value, instead of the value of the super class being set to the new value.
After this update an object will always check if the index it is setting to a new value is present in the super object and if so, will try to set that one instead ( that means if the member of the super object is public and not final )

#27 Kouksi44

  • Members
  • 89 posts
  • LocationMunich, Germany

Posted 16 July 2016 - 04:08 PM

New update:

Finally added properties with getters and setters to classic !

They can be declared exactly like any other modifiers are added to members:
class "Test" {
  prop = property - "Hello World";
}

E.g. trying to do the following:
local obj = Test()
print(obj.prop)

Will call the getter associated with that property:
function Test:getProp()
  return self.prop.."!"
end

If you find any bugs or have an idea what to add next just tell me !

#28 Kouksi44

  • Members
  • 89 posts
  • LocationMunich, Germany

Posted 10 September 2016 - 06:18 PM

Small update: You can now choose to use
 SomeObject:someFunction(...) 
or
 SomeObject.someFunction(...) 
when calling methods of an object. The library will automatically supply the correct self reference of the object to the method.

Edit: Found another bug, pastebin has been updated again.

Edited by Kouksi44, 10 September 2016 - 07:10 PM.


#29 Adn

  • Members
  • 4 posts

Posted 11 September 2016 - 06:46 PM

I plan to develop an Apt-like package management for CC (https://en.wikipedia..._Packaging_Tool). My goal is to create an application for easy installation of popular CC/Lua scripts without the need to do research for the pastebins. I'm currently collecting useful and popular scripts and libraries and your idea of this OOP library is very nice. That's why I plan to add it as "libclassic" for other scripts. It would be nice if you tell me your opinion about my idea and if you don't want to see classic in my repository.

#30 Lyqyd

    Lua Liquidator

  • Moderators
  • 8,464 posts

Posted 11 September 2016 - 07:46 PM

You might check out packman, it may be what you're looking to create.

#31 Kouksi44

  • Members
  • 89 posts
  • LocationMunich, Germany

Posted 11 September 2016 - 09:10 PM

 Adn, on 11 September 2016 - 06:46 PM, said:

I plan to develop an Apt-like package management for CC (https://en.wikipedia..._Packaging_Tool). My goal is to create an application for easy installation of popular CC/Lua scripts without the need to do research for the pastebins. I'm currently collecting useful and popular scripts and libraries and your idea of this OOP library is very nice. That's why I plan to add it as "libclassic" for other scripts. It would be nice if you tell me your opinion about my idea and if you don't want to see classic in my repository.

While I would recommend you to use existing options like Packman, you`re definitely free to use classic in any way you want to.

#32 Kouksi44

  • Members
  • 89 posts
  • LocationMunich, Germany

Posted 12 September 2016 - 07:48 PM

Bug fix once again: Passing the self reference as an argument for another instance method could`ve led to some problems with private members being accessible from outside the object. Should be fixed now.

Pastebin updated !

#33 Adn

  • Members
  • 4 posts

Posted 14 September 2016 - 04:36 PM

 Lyqyd, on 11 September 2016 - 07:46 PM, said:

You might check out packman, it may be what you're looking to create.

Ah, I didn't know there was such a solution.

#34 Kouksi44

  • Members
  • 89 posts
  • LocationMunich, Germany

Posted 30 January 2017 - 10:30 PM

New update:

I rewrote this library from the ground up, so this update comes with quite a lot of changes.

Classic 2.0

First of all defining classes that extend other classes changed a little bit:

class "Shape" {
  color = private - colours.red;
}

class "Rectangle" : Shape {
  width = private - 0;
  height = private - 0;
}

Instead of using the extends keyword you just use a colon now.

Also, instead of using
function SomeClass:init() end
You now use the name of the class as the constructor name:
function SomeClass:SomeClass() end

Accessing values of a class is now possible by simply indexing it:

class "Test" {
  x = static - 234;
}

print(Test.x) -- prints 234

Another new feature is, that you can now define your class to to be at a specific classpath:
class "myProject.subFolder.TestClass" {
  ...
}

If you want to create an instance of such a class you either have to import that namespace or specific class:
using "myProject.subFolder.TestClass"
or
using "myProject.subFolder.*" -- imports all classes in this namespace

Or you use the full path to the class:
local obj = myProject.subFolder.TestClass()

Note: You first have to load a class from whatever file it is located in and after that make it actually usable by calling using.

You also cannot extend from classes that are not in the default global namespace and are not imported yet:
class "SomeClass" : myProject.subFolder.TestClass {} -- does not work

While in previous version of Classic, you could set the type of a member, this is no longer possible in this version. I really felt like it was a bit unnecessary and and too slow overall.
You can still make members : public, private, final and static

That's it for now ! Let me know if there are any bugs in this version !

Updated pastebin link:
pastebin get gjTfwQ8P

Edited by Kouksi44, 30 January 2017 - 10:48 PM.


#35 Mao Zedong

  • Members
  • 14 posts
  • LocationFunctional Programming Zealot

Posted 31 January 2017 - 05:18 PM

OOP is a terrible idea. We need more λ in ComputerCraft.

#36 Kouksi44

  • Members
  • 89 posts
  • LocationMunich, Germany

Posted 31 January 2017 - 08:27 PM

What exaclty makes oop such a terrible idea for you ? Lua does contain lambda calculus if that is what you mean...

#37 Nothy

  • Members
  • 249 posts
  • LocationMars

Posted 02 March 2017 - 09:09 AM

I need to check this out. I've read through the entire thread and this looks super promising.

#38 Kouksi44

  • Members
  • 89 posts
  • LocationMunich, Germany

Posted 11 March 2017 - 05:28 PM

New update:

Because of whatever reasons, I decided to rewrite this library once again... Classes and objects do no longer use any metatable magic to make inheritance possible. So theoratically the new version should perform a tiny bit better, even though it still won't be the fastest.

Apart from what changed under the hood, most things are still realtively the same, except for a few small changes and new features:

Creating and extending classes now works again how it used to before the last update:
 class "mypackage.blah.Test" extends "myotherpackage.bluh.ImportantClass" { ... } 

That way it is much easier to inherit classes that are not in the default namespace.

Also, method overriding does actually work how it should work now:
class "A" {

y = public - function()  ...  end;

z = public - function(self)  self.y()  end

}

class "B" extends "A" {

test = function(self)  self.z() end

y = public - function()  print("This is called by A.z") end
}

A method can only be overriden, if it is public and neither static nor final.
I also added the baseclass "CObject" that is the baseclass of all classes that don't already inherit from some class. CObject offers a view useful methods:
Spoiler

The last change is how you import classes from another file. That part is much easier now:
 using "mypackage.blah.TestClass" : from "TestClass.lua" 

If the class you want to import is already loaded, the function will simply put it in the default namespace, so that the package name can be omitted. In that case it's not necessary to call the :from() function.

Pastebin installer:
 pastebin run fq9fwb2S 

Edited by Kouksi44, 11 March 2017 - 05:29 PM.


#39 Kouksi44

  • Members
  • 89 posts
  • LocationMunich, Germany

Posted 12 April 2017 - 01:37 PM

Another update:
This one adds several new features that should be quite useful.
First of all method and field resolving should work a bit better now. Also most error messages will now print the correct class in which the method or field lookup failed.

Anonymous classes:
You can now easily define an anonymous class that extends some existing class pretty much the same way as you would do in Java :
class "SomeClass" {
  bar = public - function() end;
}

local anonymous = SomeClass() {
  bar = public - function()
	-- this overrides SomeClass.bar
  end;
}
Note that these anonymous classes are treated as objects of the class they are extending. Any new methods that are added in the anonymous class that do not exist in the superclass won't be visible

Generic classes :
You can now specify a type parameter in the head of the class definition like so:
class "SomeClass" : T {
  someValue = public - T - nil;
}
-- also works with inheritance

class "SomeClass" extends "SomeOtherClass" : T {
  ...
}

local obj = SomeClass[String](...)

Properties:
The property keyword should be pretty straightforward. Instead of directly setting and getting some value, the respective getter and setter methods are called when a property is accessed.
class "SomeClass" {
  x = public - property - String - "Hello World";
  getX = function(self) ... end;
  setX = function(self,value) end;
}

Some other changes:
- Attributes can be given certain types again, just specify the type like any other keyword: String, Table, boolean, thread, number.
- The type of an attribute can also be an existing class. Any object that is assigned to such an attribute is treated as an object of that class. That means, that the object must be castable to the given type.
- Calling getClasss() on an object will now return an instance of the built in Class class. That class offers some helper functions:
Spoiler

The installer is still the same:
pastebin run fq9fwb2S






1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users