Pyli/Object-Oriented Programming

From Jonathan Gardner's Tech Wiki
Jump to: navigation, search

Native Types

There are several native types.

  • object
  • text
  • bytes
  • vector
  • dict
  • integer
  • decimal
  • float
  • file
  • ... etc ...

Then there are the objects. All objects are an 'object' native type.

Special Attributes

  • obj.parent (r/w): The parent is where you look for attributes if you can't find it here. This is roughly equivalent to a parent class or the class of an instance.
  • obj.type (ro): The native type of the object. This is a string of text, not an object. NOTE: I really want to get rid of this, but I think it is important.
  • (obj.get-attr attr): Returns the value of the attribute.
  • (obj.set-attr attr value): Sets the attribute's value.
  • (obj.delete-attr' attr): Removes the attribute, treating it as if it were not set.
  • --call--: This is a method. no matter what. That is, whatever is set here has to accept 'self' as the first parameter. It is invoked if the object is invoked as an operator.
  • --get-attr--: This is a method, no matter what. The first argument is always 'self'. The second argument is the attribute that is being looked up. This is always invoked FIRST.

Getting an Attribute

We first need to distinguish some things.

  • Simple lookup is where you see if the attribute is there or not, without doing anything special.
  • Full lookup is to lookup the '--get-attr--' method, and then call it.


The default get-attr that comes with Object does the following.

  • Checks to see if obj.attrs has an element named attr.
    • If so, then we get the get attribute of the

Prototype Tree

Object
  Text
    [All text instances]
  Vector
    [All vector instances]
  [... instances]

Introduction

The Pyli system is powerful and easy to master. There are relatively few details to get familiar with.

Examples

It's best to start with some examples.

# Anonymous class
(class
  &(method &set-a &(a)
    &(set! &self.a a))
  &(method &get-a &()
    &(self.a)))

Type

The concept of type is somewhat equivalent to class in other systems. Every instance--that is, anything that a variable can refer to--is an instance. Every instance has a type.

Types tend to be capitalized when they are named. This is merely a convention, and generally applies only to the global level. See Pyli/Built-in Types for all the built-in types.

  • (type value): returns the instance representing the type of the value.

Constructor

A constructor is simply a function. It invokes, if necessary, the (make-instance type) function, which returns a fresh new object of that type. (Kind of like perl's bless...)

The constructor may set some initial attributes.

You can have many different constructors for a type.

Attributes & Methods

Attributes and methods are looked up with the attr function.

(attr a &b) # Lookup the 'b' attribute of the object named with 'a'.

There is a shorthands:

(a . b)  # -> (attr a &b) Note the quoting.

Looking up an attribute works in the following way, by default:

  1. See if the name is part of the dictionary of the object (for user-defined objects) or one of the recognized attributes (for built-in objects). If so, return it. Do not bind functions.
  2. See if the name is part of the type as in #1. If so, return it. Bind functions.
  3. See if the name is part of the parent types using MRO.

You can override lookups with special attributes. If a value has a --get--, --set--, or --delete-- functions, then those will be called, just like Python.


Methods

Methods are attached to the types of the objects, but only loosely. Many methods may share the same name. Each may have different parameter lists.

The methods must specify what kinds of types they expect.

To find the method that is actually invoked. all the applicable methods are found. The most specific one is invoked. How do you determine which one is most specific? It matches the most types most specifically, and the ones that match the most closely are on the left.


METHOD A

When invoking a function:

  • First, look at the function. Is it a name or an expression?
    • If a name, it may be a method invocation. Look at the first argument, evaluate it, and see if it has an attribute with the same name. If so, execute that with all of the parameters, including the first.
    • Otherwise, it is not a method.

NOTE: The above is NO GOOD. You can't specify the method programmatically except with eval.

METHOD B

  • Explicitly declare some objects to be methods.
  • Write a method instance for particular classes.
  • When a method is invoked, it looks for the best match, but only at the first argument.
  • Why stop at first argument? => CLOS?

METHOD C

  • All functions are really methods.
  • When you define a function, you are defining the generic method that will work as a fallback.
  • When you define a method, you need to specify the objects that this will apply to. These override the fallback or inferior methods.
  • Why limit to the first parameter? => CLOS?

METHOD D

NOTE: I like this a lot!

Special syntax to invoke methods. Something like:

((el obj &method-name) parameters)

Problem: How to pass in self? Should we assume it, or require it explicitly? Should it bind as it is retrieved, like Python?

Shortcut: If we are going to use el for everything, let's invent a special syntax-- '.', ':' or something.

a . b => (el a &b)
a . b . c => (el (el a &b) &c)
(some fn) . (some fn) => (el (some fn) &(some fn)) # Inappropriate

Alternatively (not quoting the name):

a{b} => (el a b)
a{b}{c}
(some fn){(some fn)} => (el (some fn) (some fn))

Or: (I don't like this.)

b of a => (el a b)
a's b


Metaclasses

Metaclasses seem like a useful concept.


Multiple Inheritance

Heck, just do everything the same way Python does. They have a reasonable model. Of course, 'super' needs some work.

Internal

Internal structure:

- Python classes represent types, but are not those types. - types are objects in the pyli universe. The 'type_ attribute points to it. - the 'el' method handles all attribute access through 'el'.


Special Attributes

--parent--

The parent or prototype that this object is extended from. This can be modified.

Special Methods

These methods all are treated as methods without being true methods.

(--call-- obj params...)

Called when the object is treated as an operator. For instance, in the expression:

(foo 1 2 3)

The method --call-- is invoked as:

(foo.--call-- foo 1 2 3)

Note that foo.--call-- shouldn't be a real method, just a function.

Note that if foo.--call-- is another object with a --call-- object defined, then it will be invoked with yet another additional parameter.

(obj Object [
   [&--call-- (obj Object [
     [&--call-- (fn &(inner-instance outer-instance)
       &...)]])]])

--get-attr--

This is the first call when an attribute access is made.

foo.bar

is translated to

(foo.--get-attr-- foo &bar)

Note that looking up --get-attr-- always goes directly to the object and isn't handled by --get-attr--, for obvious reasons.

Object.--get-attr--, the default attribute getter, does the following:

* Checks to see if the attribute is present.
* If so, then checks to see if the value has a --get-- attribute.
** If there is a --get--, call (value.--get-- value obj attr-name). Return the value returned.
** If there is not a --get--, then return the value itself.
* If the attribute isn't present, call (obj.--get-missing-attr-- obj attr-name)


--get--

Invoked by the default --get-attr-- when an object is retrieved through attribute access. The result of this function is what is returned as being retrieved.

This takes three arguments: obj owner attr-name. Obj is the attribute of owner that is being looked up. Owner is the owner of obj. attr-name is the name of the attribute being looked up.

Methods use this to return a bound method upon attribute access.

= --get-missing-attr--

Called when an attribute is missing. Takes the object instance and the attribute name, much like --get-attr--.

--set-attr--

Like Python __setattr__. Invoked from (set! foo.bar 5)

--set--

= --set-missing-attr--

--del-attr--

Like Python __delattr__. Invoked from (delete! foo.bar)

--del--

= --del-missing-attr--

Container methods

Containers are objects that contain other objects, such as Vectors and Dicts.

--get-el--

Like Python __getitem__. Invoked from (el foo &bar)

--set-el--

Like Python __setitem__. Invoked from (set! (el foo &bar) 5)

--del-el--

Like Python __delitem__. Invoked from (delete! (el foo &bar))

--length--

Invoked from (length foo).

Int Methods

--int--

Returns the int value of this.

--float-- =

Text Methods

--text--

--code--

Bytes Methods

--encode-- encoding-method

--decode-- encoding-method

Pyli Objects

These are objects defined within Pyli itself.

Slot

A slot is either an attribute or a method or something else. Since objects behave most like environments, think of a slot as a variable name.

You can specify a default slot value or handler. This will be activated when a name lookup fails.

Attributes

Attributes are just a value for a slot. No special binding occurs, even if the attribute is a function of some sort.

Methods

Methods are a special value for a slot. What happens is when the value is accessed, a bound method is returned. This will call the function with the first argument of the object and the rest of whatever else is passed in.

Special Slots

  • parent: The object that this is derived from. Object has no parent, so this is False. Note that you can derive from False, although those values will not be considered False.
  • has-parent: Whether there is a parent. Signals whether to pay attention to the parent attribute.
  • slots: Returns a dict (or something dict-like) of slot-value mappings.
  • get-slot: The get-slot slot is a slot that is called before any lookup. Signal "missing-slot" error if missing.

The default get-slot will do the following:

if the slot is in slots:
  get the value from &slots for slot
  if the value has an &on-get attribute:
    call that and return the result
  else:
    return the value

Every lookup, therefore, would require at least two additional lookups: one to slot, the other to on-get for the value. We can, of course, optimize this by caching the results and skipping an actual lookup.

Note that 'slots' returns the entire universe of values, including the parent values. However, setting won't overwrite the parent's value, but create a local override. Either that or we have to recursively go up the chain of parents.

We can do something like what Io does where accesses are cached and kept in an easy-to-use index.

To bypass the on-get functionality, just get the slot from the slots attribute yourself.

  • set-slot: The set-slot slot is a slot that is called before any set. Signal "missing-slot" error if missing.

The default set-slot will only change values in this object. It will not affect parent objects, instead overriding what is already there.

Of course, the set behavior can be modified with the on-set attribute.

  • delete-slot: The delete-slot is called before any delete. Signal "missing-slot" error if missing.

You can override this behavior with on-delete of the value.

Default is to remove it from the local environment. This means masking it so that it appears deleted but leaving the parent alone.

  • get-missing-slot: The get-missing-slot is called when a lookup fails due to a missing slot. This (and the following two) are higher-level than get-slot, et al.
  • set-missing-slot: The set-missing-slot is called when a set fails due to missing slot.
  • delete-missing-slot: The set-missing-slot is called when a set fails due to missing slot.
  • get: Called by the default get-slot handler when the object is retrieved. If this attribute is missing, then the object itself will be returned. Otherwise, this will be called by the object.
  • set: Idem but for set.
  • delete: Idem but for delete.

true?

By default, this is True, except for False which it is False. Note that this should return either True or False. if tests for (a) whether the item is False or (b) whether the item is True. If neither is true, then the value for item.true? is tested, ad infinitum.