Skip to content
jacius edited this page Nov 24, 2012 · 2 revisions

This guide gives a brief introduction to the basics of using Protolk.

Installing Protolk

You currently need Chicken Scheme to use Protolk. After you have installed Chicken, you can install the latest release of Protolk by running chicken-install protolk. Or, if you want the absolute latest revision of Protolk, you can clone the git repository then run the chicken-install command from within the repository clone directory.

Loading Protolk

To use Protolk in your code:

(require-extension protolk
                   protolk-stdpob
                   protolk-syntax-send-brackets
                   protolk-syntax-own-prop-at)

If you are using Protolk's syntax sugar and you want to compile your code, you will need to tell the compiler to load the syntax modules as compiler extensions:

csc -X protolk-syntax-send-brackets -X protolk-syntax-own-prop-at  your-code.scm

If you don't want to use syntax sugar, you should not give give those flags, and you should also omit the syntax modules from the require-extension expression shown above.

Creating a Pob (Protolk Object)

Pobs are usually created by deriving from another, already existing pob. Protolk comes with a standard pob, called stdpob, which you can use as the base for your own pobs.

(define point
  (send stdpob 'derive
        props: (list (list 'x 0) (list 'y 0))
        methods: (list (list 'x (prop-reader 'x))
                       (list 'y (prop-reader 'y)))))

This sends the message derive to the object stdpob, along with two keyword arguments, props and methods, which specify what props (properties) and methods the new pob should start with. stdpob reacts to this message by creating and returning a new pob. The new pob's base will be stdpob, meaning that the new pob inherits props and methods from stdpob, in addition to the props and methods that the pob contains within itself.

Sending Messages

The primary way to interact with pobs is to send them messages. We have already seen one example of this above, when we sent the message derive to stdpob.

When a pob receives a message, it looks to see if it either contains or inherits a method with the same name as the message. If it does, it invokes that method and passes it any extra arguments specified after the message name, such as the props and methods keyword arguments in the example above.

To send a message to a pob, use the send procedure:

(send TARGET-POB 'MESSAGE-NAME ARG1 ... ARGN)

If you are using the "protolk-syntax-send-brackets" extension, you can use square brackets "[ ]" to do exactly the same thing:

[TARGET-POB MESSAGE-NAME ARG1 ... ARGN]

Note that the message name is not quoted when using this syntax.

Defining Methods

The usual way to define methods is using the define-method macro:

(define-method point (move! #!key (x 0) (y 0))
  (set! (own-prop 'x)
        (+ (own-prop 'x) x))
  (set! (own-prop 'y)
        (+ (own-prop 'y) y))
  self)

The defines a new method on the pob point, with the method name move!, and taking two keyword arguments, x and y. The body of this method does three things:

  1. It adds the value of the x parameter to the value of the pob's x prop, then sets the pob's x prop to the result of that addition.
  2. Does the same thing for the y parameter and the pob's y prop.
  3. Returns the pob.

Note that even though this method was defined on the point pob, if another pob inherits from point, the method would apply to that other pob's props, and return the other prop. In other words, own-prop and self refer to the pob who received the message, which is not necessarily the pob that the method was defined on.

Because it's so common for a pob's methods to read and write that pob's props, Protolk provides some syntax sugar to make it more convenient. If you use the protolk-syntax-own-prop-at extension, you can write the example above like this:

(define-method point (move! #!key (x 0) (y 0))
  (set! @x (+ @x x))
  (set! @y (+ @y y))
  self)

Prop Inheritance

If a pob is derived from another pob (the "base"), the derived pob will inherit props and methods from the base. Inheritance works dynamically, whenever a pob needs to look up a prop or method in itself. The way it looks up the prop is fairly simple:

  1. If the pob itself contains a value for the prop it is looking for, it uses that value.
  2. Otherwise, if the pob has a base, it asks the base to look up the prop. The lookup recurses in the base.
  3. If the pob has no base and hasn't found the prop yet, it returns #<unspecified> (the value returned by (void).

Note that the process might recurse up many levels of base pobs, until it either finds the prop value or reaches a pob with no base. So, each pob inherits props from all its ancestors, with props in the closest ancestor taking precedence over props with the same name in more distance ancestors.

When a pob sets the value of one of its props, it simply sets the new value of the prop in itself. No lookup is performed, and no other pobs are directly affected. However, the new value is immediately available to any pobs that inherit from the pob who set its own value.

Method Inheritance

Methods are inherited in the same way as props, and the lookup process is very similar. The main difference is that if the lookup fails, the original pob invokes its own _method-missing method, which by default causes the pob to signal an error saying that the requested method was not found.

There is one other special feature with method inheritance: super methods. Inside a method body, you can use the super macro to invoke the next method with the same name in the inheritance chain.

(define verbose-point
  [point derive])

(define-method verbose-point (move! #!key (x 0) (y 0))
  (display "Moving by: ")
  (display (list x y))
  (newline)

  (super x: x y: y)

  (display "Now at location: ")
  (display (list @x @y))
  (newline))

This method would print a message, then invoke the move! method inherited from point, then print another message.

If you want to invoke the super method using all the same arguments that were passed to the current method, you can use the super* macro. So, you could write the above example like this:

(define-method verbose-point (move! #!key (x 0) (y 0))
  (display "Moving by: ")
  (display (list x y))
  (newline)

  (super*)

  (display "Now at location: ")
  (display (list @x @y))
  (newline))

If you want to invoke the super method using a list of args, you can use the apply-super macro, which is analogous to the standard Scheme procedure, apply.

If you try to invoke the super method when there is no super method, Protolk will signal an error. You can check whether there is a super method by using the super? procedure.