API

jacius edited this page Dec 9, 2012 · 12 revisions

This reference guide describes (almost) all the procedures, macros, and standard methods provided by Protolk. Procedures and macros that are only useful to advanced users are described in Advanced Customization.

Core

make-pob

Procedure: (make-pob KEYWORD: ARG ...)

Create and return a new pob. Note: If you are deriving from another pob (the "base pob"), you should usually send that pob the derive message (described below) instead of calling make-pob. Using derive gives the base pob an opportunity to perform custom initialization of the new pob.

The following keyword arguments are accepted:

  • base: The new pob's base, or #f to have no base. Default: #f.
  • props: A list of prop names and values to be contained by the new pob. Default: empty list.
  • methods: A list of method names and procedures to be contained by the new pob. Default: empty list.
  • prop-resolver: A procedure defining how the pob looks up props. Default: std-prop-resolver.
  • method-resolver: A procedure defining how the pob looks up methods. Default: std-method-resolver.

See Advanced Customization for more details about prop and method resolvers.

Example:

(define point
  (make-pob
    base: stdpob
    props: (list (list 'x 0.0)
                 (list 'y 0.0))
    methods: (list (list 'x (prop-reader 'x))
                   (list 'y (prop-reader 'y)))
    prop-resolver: std-prop-resolver
    method-resolver: std-method-resolver))

send

Procedure: (send POB 'MESSAGE-NAME ARG ...)

Send the pob a message, to ask it to invoke the matching method. The ARGs (after the message name) are passed through to the method. (Of course, if the method doesn't require any args, you can omit them when you use send.) send returns whatever value the invoked method returns.

The protolk-syntax-send-brackets provides syntax sugar for send. See Getting Started for more information about using syntax sugar.

Example:

;; Without syntax sugar:
(if (send point 'has-ancestor? stdpob)
    (display "stdpob is an ancestor of point.\n"))

;; With syntax sugar:
(if [point has-ancestor? stdpob]
    (display "stdpob is an ancestor of point.\n"))

Defining Methods

prop-reader

Procedure: (prop-reader 'PROP-NAME)

Returns a method procedure which, when invoked by a pob, returns the value (possibly inherited) of that pob's prop with the given name. Returns #<unspecified> if the pob does not have a prop with that name.

prop-writer

Procedure: (prop-writer 'PROP-NAME)

Returns a method procedure which, when invoked by a pob, sets the value of the prop with the given name to a new value, creating the prop in the pob if necessary.

own-prop

Procedure: (own-prop 'PROP-NAME)

When called within a method context, returns the value (possibly inherited) of the prop with the given name in the pob invoking the method. Returns #<unspecified> if the pob does not have a prop with that name. If own-prop is called outside of a method context, it signals an exception.

The protolk-syntax-own-prop-at provides syntax sugar for own-prop. See Getting Started for more information about using syntax sugar.

own-prop is compatible with the SRFI 17: Generalized set!. Calling (set! (own-prop 'x) 3), or (set! @x 3) if you are using syntax sugar, is equivalent to calling (set-own-prop! 'x 3)

Example:

;; Without syntax sugar:
(define-method point (move! #!key (x 0) (y 0))
  (set! (own-prop 'x) (+ x (own-prop 'x)))
  (set! (own-prop 'y) (+ y (own-prop 'y))))

;; With syntax sugar:
(define-method point (move! #!key (x 0) (y 0))
  (set! @x (+ x @x))
  (set! @y (+ y @y)))

set-own-prop!

Procedure: (set-own-prop! 'PROP-NAME VALUE)

When called within a method context, sets the value of the prop with the given name in the pob invoking the method, creating the prop in the pob if necessary. If set-own-prop! is called outside of a method context, it signals an exception.

define-method

Macro: (define-method POB (METHOD-NAME ARG ...) BODY ...)

Defines a method in the pob, replacing any existing method with the same name in that pob. Within the body of the method:

  • the variable self refers to the receiving pob
  • the own-prop and set-own-prop! procedures access props in that pob
  • the super, apply-super, etc. macros can be used to invoke the next inherited method (if any) with the same name

define-private-method

Macro: (define-private-method POB (METHOD-NAME ARG ...) BODY ...)

Like define-method, except the method is private, i.e. it can only be invoked by the pob sending itself a message from within another method. If the method is invoked from outside the pob, it signals an exception.

make-method

Macro: (make-method (METHOD-NAME ARG ...) BODY ...)

Like define-method, except it returns the resulting method procedure instead of setting it in a pob.

make-private-method

Macro: (make-private-method (METHOD-NAME ARG ...) BODY ...)

Like make-method, except the method is private.

Super Methods

super?

Procedure: (super?)

When called within a method context, returns #t if there is a super method, or #f if there is not. You should use this procedure to test whether it is OK to use super / super* / apply-super.

super

Macro: (super ARG ...)

Invokes the next super method, passing it the given arguments. Signals an exception if there is no next super method.

Example:

(define over-eager-point (send point 'derive))

(define-method over-eager-point (move! #!key (x 0) (y 0))
  (super x: (* x 2) y: (* y 2)))

super*

Macro: (super* )

Like super, but invokes the next super method with the same arguments that were passed to the current method. Signals an exception if there is no next super method.

Example:

(define verbose-point (send point 'derive))

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

  (super*))

apply-super

Macro: (apply-super ARG ... ARGS-LIST)

Like super, but invokes the next super method with with the arguments from ARGS ... ARGS-LIST in the same way as the standard Scheme procedure, apply.

Example:

(define analytical-point (send verbose-point 'derive))

(define-method analytical-point (move! #!rest args)
  (display "My 'move! method was called with these args: ")
  (display args)
  (newline)

  (apply-super args))

Internal Methods

There are a few "internal methods" that all pobs should have: _display, _receive, and (usually) _method_missing. Protolk provides default implementations for all these methods. stdpob already contains the default implementations, so if you inherit from stdpob, you only need to define these methods in your pobs if wish to customize the pob's behavior (see Advanced Customization). If a pob does not have one of these methods, Protolk uses the default implementation.

These methods are not intended to be invoked by sending the pob messages. Rather, they are used behind the scenes by Protolk itself. (The _method-missing method is a slight exception, because the _receive method invokes it when no matching method is found, and you can do the same if you reimplement _receive.)

_display / std-_display

Procedure: (std-_display POB #!optional PORT)

The _display method is used by Protolk to display the pob as text, for example with print, display, or write, including when the pob is printed out on the REPL. The PORT argument is the output port to write to (default: (current-output-port)).

std-_display is the default implementation of _display. In Protolk 0.5, std-_display simply prints the string "#<pob>", but in the future it will probably be more informative and useful.

_receive / std-_receive

Procedure: (std-_receive POB 'MESSAGE-NAME ARG ...)

The _receive method is used by the pob to handle messages sent to it. The arguments passed to _receive are the same as the arguments that were passed to send when the message was sent.

std-_receive is the default implementation of _receive. It looks for a method that matches the given message name. If it finds one, it invokes it with the given ARGs, and returns the value that the method returns. If it finds no matching method, it invokes its own _method-missing method, or std-_method-missing if it has no _method-missing method.

_method-missing / std-_method-missing

Procedure: (std-_method-missing POB 'MESSAGE-NAME ARG ...)

The _method-missing method is called by std-_receive when it cannot find a matching method. The arguments passed to _method-missing are the same as the arguments that were passed to _receive (and thus the same as the arguments that were passed to send).

std-_method-missing is the default implementation of _method-missing. It signals an exception to indicate that no matching method was found.

You can define _method-missing in your own pobs to perform some other action, such as invoking a different method than requested, or dynamically defining the requested method.

stdpob

stdpob is the "standard pob" which comes pre-defined in Protolk. stdpob and the stdpob-______ procedures are defined in the protolk-stdpob module.

derive

Method: [POB derive KEYWORD: ARG ...]
Procedure: (stdpob-derive POB KEYWORD: ARG ...)

Create a new pob derived from the receiving pob, i.e. the new pob's base is the receiving pob. In stdpob-derive, this is (mostly) equivalent to (make-pob base: POB KEYWORD: ARG ...). But, derive is preferred over make-pob in general, because it gives the base pob an opportunity to perform custom initialization of the new pob, in case the base pob has defined a custom derive method.

ancestors

Method: [POB ancestors]
Procedure: (stdpob-ancestors POB)

Returns a list of all the pob's ancestors, i.e. its base, its base's base, and so on up the inheritance chain. The list is constructed by recursively sending the ancestors message to the pob's base and consing the results together.

has-ancestor?

Method: [POB has-ancestor? OTHER-POB]
Procedure: (stdpob-has-ancestor? POB OTHER-POB)

Returns #t if OTHER-POB is an ancestor of POB, or #f if it is not. The result is determined by recursively sending the has-ancestor? message to the pob's base.

responds-to?

Method: [POB responds-to? 'MESSAGE-NAME]
Procedure: (stdpob-responds-to? POB 'MESSAGE-NAME)

Returns #t if POB is able to receive a message with the given name, or #f if it is not able. In stdpob-responds-to?, the receiving pob simply checks whether it contains or inherits a method with the same name as the message.

Coming in 0.6

These are procedures, macros, and methods that will be added in Protolk 0.6.

pob?

Procedure: (pob? VALUE)

Returns #t if VALUE is a pob, or #f if it is some other type of value. (In Protolk 0.5, this procedure is available in the Primitive API, but due to an oversight it was not included in the normal API.)

is-receiver?

Procedure: (is-receiver? POB)

Returns #t if the given pob is the active receiver (i.e. the pob who is performing an action because it received a message).

define-prop-readers

Procedure: (define-prop-readers POB '((PROP-NAME READER-NAME) ...))

Define prop reader methods on a pob for the given props. If you specify just PROP-NAME instead of (PROP-NAME READER-NAME), the reader method name will be the same as the prop name.

Example:

;; Using default reader method names:
(define-prop-readers point '(x y))

;; Using custom reader method names:
(define-prop-readers point '((x ex) (y why)))

(equal? [point x] [point ex])   ; #t
(equal? [point y] [point why])  ; #t

define-prop-writers

Procedure: (define-prop-writers POB '((PROP-NAME WRITER-NAME) ...))

Define prop writer methods on a pob for the given props. If you specify just PROP-NAME instead of (PROP-NAME WRITER-NAME), the writer method name will be 'set-PROP-NAME!.

Example:

;; Using default writer method names:
(define-prop-writers point '(x y))
[point set-x! 4]
[point set-y! 6]

;; Using custom writer method names:
(define-prop-writers point '((x x:) (y y=)))
[point x: 4]
[point y= 6]

define-prop-accessors

Procedure: (define-prop-accessors POB '((PROP-NAME READER-NAME WRITER-NAME) ...)

Define prop reader and writer methods on a pob for the given props. If you specify just PROP-NAME instead of (PROP-NAME READER-NAME WRITER-NAME), the reader method name will be the same as the prop name, and the writer method name will be 'set-PROP-NAME!. If you specify If you specify just (PROP-NAME READER-NAME) (omitting the writer method name), the writer method name will be 'set-READER-NAME!.

Example:

;; Using default reader and writer method names:
(define-prop-accessors point '(x y))
[point set-x! 2]
[point set-y! 3]
(equal? [point x] 2)   ; #t
(equal? [point y] 3)   ; #t

;; Using custom reader method names, default writer method names:
(define-prop-accessors point '((x ex) (y why)))
[point set-ex! 6]
[point set-why! 7]
(equal? [point ex] 6)   ; #t
(equal? [point why] 7)  ; #t

;; Using custom reader and writer method names:
(define-prop-accessors point '((x xu xu:) (y yu yu=)))
[point xu: 4]
[point yu= 5]
(equal? [point xu] 4)   ; #t
(equal? [point yu] 5)   ; #t

set-method!

Procedure: (set-method! POB 'METHOD-NAME PROCEDURE)

Replace a method in the pob. Like define-method, but you can use an already existing procedure.

unset-own-prop!

Procedure: (unset-own-prop! 'PROP-NAME)

Remove the prop from the pob, so it inherits from its base again.

set-base!

Procedure: (set-base! POB NEW-BASE)

Set the pob's base to another pob (or #f). If setting the pob's base would cause cyclic ancestry, the procedure signals an exception, without setting the pob's base.