8

Could a class be a first-class object? If yes, how would the syntax for dynamically creating new classes look like?


To narrow the question - how would you give this functionality while keeping language consistent?

For example how you create reference for new type. Do you make reference first-class object too and then use something like this:

Reference<newType> r = new Reference<newType>();
r.set(value);

Well this could get messy so you may just force user to use Object type references for dynamically created classes, but then you loose type-checking.

I think creating concise syntax for this is interesting problem which solving could lead to better language design, maybe language which is metalanguage for itself (I wonder if this is possible).

4
  • 3
    You're going to have to do a dramatically better job of phrasing this if you want to save it from certain doom.
    – user29776
    Aug 17, 2011 at 19:16
  • 2
    @mrpyo: Asking how the syntax would look makes no sense, because syntax is by definition language-specific. If you are designing a language, I would suggest you to get the semantics right and only then start worrying about syntax.
    – isekaijin
    Aug 17, 2011 at 19:40
  • 1
    @kekekela: Classes as first class objects is a well defined term. Don't need to be much clearer than that. Aug 17, 2011 at 21:27
  • An interesting point to consider here - the original asker appeared to be thinking in terms of statically typed languages - his example syntax is clear a static language with manifest types - but all of the answers given so far relate to dynamic languages. What facilities are available for this kind of operation in static languages?
    – Jules
    Feb 7, 2016 at 12:02

8 Answers 8

7

Ruby has this capability, along with a (perhaps too-) detailed API for modifying classes on the fly.

You can get a taste of this by looking through the documentation for the Ruby core classes Class and Module (Class is a subclass of Module, as they share some code for namespace management), and most Ruby books will have at least some coverage of this.

8

Different programming languages are designed according to different philosophies and with different kinds of users in mind. Some languages treat classes (or, more generally, types) as first class objects; other languages do not.

Languages that treat types as first-class objects can be divided in two groups:

  1. Those that treat types as immutable objects at runtime (i.e., you can query, but not modify the valid operations on a type, and the types of the parameters and return values of these operations)
  2. Those that treat types as regular objects at runtime (i.e., you can dynamically define and modify types and operations at runtime).

The latter are particularly powerful, although these capabilities usually come at a performance cost, because a runtime must take care of the types and operations you have dynamically defined.


Regarding how the implementation would look: Are you asking this as a language user or as a language designer?

To a language user, the only thing that matters is what I said in the previous part: a runtime must take care of the types and operations you have defined.

To a language designer, well, you have to design such runtime. Oh, and implement it, if you want your language to be used in the real world.


I do not see the point of the question regarding how the syntax would be. Syntax is, by definition, language-specific. If you are a language designer, I would suggest you to get the semantics right before worrying about syntax.

6

Sure, nothing simpler than having a class be a first class object:

Object subclass: #MyObject
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'My-Category'

(Smalltalk.)

1
  • 1
    Smalltalk was the first thing that came to my mind when I heard the question. May 10, 2012 at 6:09
4

Eli Bendersky recently posted Python Metaclasses, which discusses that very question. In it, he starts with

In Python, everything is an object. And that includes classes. In fact, classes in Python are first-class objects – they can be created at runtime, passed as parameters and returned from functions, and assigned to variables.

He goes on to show some working Python examples of creating a new Metaclass, which can be used to create new classes. Highly worth reading.

2
  • Is the Metaclass class object (i.e., the object whose identity is that of the Metaclass class) modifiable itself?
    – isekaijin
    Oct 4, 2011 at 17:10
  • If you mean the Metaclass that you constructed, yes - it's an object like any other. (At least you can modify it, by default - it is also possible to create an object/class that doesn't let you add new attributes). If you mean the underlying base class, which is type, then no, you can't modify the type object (as far as I can tell).
    – Cyclops
    Oct 4, 2011 at 19:17
3

Certainly.

A specific example: In a prototype-based approach, a class is effectively the same thing as an instance of a class, and it can just be cloned with new fields added or overwritten to make new instances.

A simple prototype-based example in Clojure:

(def human
  {:species-name "Human"
   :leg-count 2})

(def john-smith
  (merge human
    {:first-name "John"
     :last-name "Smith"
     :profession "Brewer"}))

(def john-silver
  (merge human
    {:first-name "John"
     :last-name "Silver"
     :leg-count 1
     :profession "Pirate"}))

(:leg-count human) ; class value
=> 2

(:leg-count john-smith) ; inherited value
=> 2

(:leg-count john-silver) ; overridden value
=> 1
3

Classes are objects in Objective-C, a feature which I think comes from Obj-C's Smalltalk roots. You generally don't create new "class objects" yourself in code; the compiler does that for you. That makes sense when you think about it: a class object is an object that happens to represent a class; if you instantiate it by sending it an +alloc message, you'd end up with a new object of the class represented by the class object, not another class object. That last bit really does make sense, I promise, but it's hard even for me to follow it, so here's some code to illustrate:

Class foo = [NSString class];        // foo points to the the object
                                     //    representing the NSString class
NSString *bar = [[foo alloc] init];  // bar points to an object of type NSString
int length = [bar length];           // length will be 0 because bar is empty

Note that both foo and bar are valid objects to which we can send messages.

It is actually possible to add class objects at runtime, but you do it by making calls to the Objective-C runtime directly.

3
  • 2
    +1 for knowing your roots! Aug 18, 2011 at 8:33
  • 1
    Your Class *foo should be Class foo instead. Class is a type that can hold pointers to class objects (similar to how id is a type that can hold pointers to objects). But Class is not a class and class objects are not "of type Class".
    – user102008
    Jun 1, 2012 at 0:07
  • @user102008 Thanks for the correction. Updated.
    – Caleb
    Jun 1, 2012 at 1:22
1

That's more or less the case in Ruby.

Object.const_set("Student", Class.new)

This is maybe the most simple way to demonstrate this feature. A class in Ruby is an instance of Class. With more code you can add member functions and variables.

Explained in more detail here

1

Have a look at CLOS, the Common Lisp Object System, described notably in Object-Oriented Programming in Common Lisp: A Programmer's Guide to CLOS in 1988. CLOS itself was influenced by existing object systems like Flavors and CommonLoops.

The following defines or redefines a class when called:

(defclass my-class (multiple parent classes)
  ((x :initarg :x)
   (y :initarg :y))) 

This is a macro, but the functional equivalent exist and is called ensure-class, which is easier to use when arguments are computed. The value returned by this expression, or by (find-class 'my-class), is a value of type standard-class. Classes have a metaclass, which is an object which describes how to represent classes and how to access slot's values. You can change CLOS to make it behave like another object system thanks to the meta-object protocol, described in The Art of the Metaobject protocol (a.k.a. AMOP). For example, you could provide a :metaclass argument which makes your objects serialize and deserialize from a database.

Once you have a class, you can instanciate it:

(make-instance 'my-class :x 10 :y 20)

Note that the class argument to make-instance can be given at runtime. All values in Common Lisp have a class:

(class-of 3)
=> #<built-in-class fixnum>

(class-of '(a b c))
=> #<built-in-class cons>

But not everything is necessarily an object.

CLOS allows you to define multimethods and hence generic functions are not tied to a single class. For example:

(defgeneric attack (attacker target)

  (:method ((w wizard) (v vampire))
    ;; Vampires do not glitter when exposed to light, they burn.
    (cast-spell w 'sunbath :on v))

  (:method ((w wizard) (d dragon))
    ;; Fall, you fool
    (cast-spell w 'paralysis :on d))

  ;; Dispatch on human here, a superclass of wizard

  (:method ((v vampire) (h human)) (bite v h))
  (:method ((d dragon) (h human)) (ignite d h))

  ;; Dragon duel
  (:method ((d1 dragon) (d2 dragon) (bite d1 d2))

  ;; Default case
  (:method (x y) (punch x y)))

You can add or remove methods at runtime and specify also other kind of methods, such as :after, :before or :around methods:

(defmethod attack :around (attacker target)
  (when (>= (roll-dice (attack-points attacker))
            (defense-points target))
    ;; attack is successful, proceed to actual attack
    (call-next-method)))

The above is executed around each attack and checks if the attacker is successful, based on chance and both objects's characteristics. The (call-next-method) expression is used to call the next less-specific :around method or the next most-specific primary method if no other :around method exists.

Objects can change class at runtime:

(defmethod bite ((v vampire) (h human))
  (take-hit h (- (bite-attack v) (armor h)))
  (change-class h (infected (class-of h))
                :target-class 'vampire
                :delay-turns 3))

Here, a vampire inflicts damage to a wizard which then changes into an infected-wizard class, thanks to (infected (class-of h)) which makes sure such a class exist by creating it if necessary (requires alexandria and closer-mop):

(defun infected (class)
  (ensure-class
   (symbolicate 'infected "-" (class-name class))
   :direct-superclasses (list 'infected class)))

The instance of human is updated with new slots: here, the wizard is supposed to be changed into a generic vampire in three turns.

how would you give this functionality while keeping language consistent?

Changing classes and methods at runtime might be problematic but the system is built around a robust protocol for dealing with runtime changes, such as calling the appropriate initialization methods when changing an object's class, or recomputing class precedence lists before generic functions are called.

... but then you loose type-checking

Type-checking is enforced at runtime.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.