Changeset 40244 in project


Ignore:
Timestamp:
06/30/21 20:04:23 (4 weeks ago)
Author:
Mario Domenech Goulart
Message:

records-and-oop: update to CHICKEN 5, add some markup

File:
1 edited

Legend:

Unmodified
Added
Removed
  • wiki/records-and-oop

    r32518 r40244  
    1414creates automatically ten procedures
    1515
    16 * a constructor, make-rect
    17 * a predicate, rect?
    18 
    19 for each slot, x, y, w, h
    20 
    21 * an accessor rect-x, rect-y, rect-w, rect-h
    22 * and a mutator rect-x-set!, rect-y-set!, rect-w-set!, rect-h-set!
     16* a constructor, {{make-rect}}
     17* a predicate, {{rect?}}
     18
     19for each slot, {{x}}, {{y}}, {{w}}, {{h}}
     20
     21* an accessor {{rect-x}}, {{rect-y}}, {{rect-w}}, {{rect-h}}
     22* and a mutator {{rect-x-set!}}, {{rect-y-set!}}, {{rect-w-set!}}, {{rect-h-set!}}
    2323
    2424This is convenient, but since these procedures are never defined
    2525explicitly, you might have problems understanding code, where their use
    26 is far away from the call to define-record. Moreover, you are missing
     26is far away from the call to {{define-record}}. Moreover, you are missing
    2727the flexibility to choose your own names for these procedures, and --
    2828which is more important -- you can't make individual slots read-only.
     
    3030=== define-record-type
    3131
    32 All these problems are avoided using srfi-9's define-record-type, also
    33 supplied by Chicken in the special-forms unit. The following call will
    34 do exactly the same as the define-record call above, but now all names
     32All these problems are avoided using srfi-9's {{define-record-type}}, also
     33supplied by CHICKEN in the {{chicken.base}} module. The following call will
     34do exactly the same as the {{define-record}} call above, but now all names
    3535are explicit, hence can be chosen by yourself and the modifiers can be
    3636omitted at all, thus providing read-only slots.
     
    4848
    4949You can also save some procedure identifiers by replacing e.g.
    50 rect-x-set! with (setter rect-x). Then you can mutate the x slot in the
    51 srfi-17 way by (set! (rect-x rt) new-slot).
     50{{rect-x-set!}} with {{(setter rect-x)}}. Then you can mutate the {{x}} slot in the
     51srfi-17 way by {{(set! (rect-x rt) new-slot)}}.
    5252
    5353=== define-record-printer
    5454
    55 Both define-record and define-record-type can be used in combination
    56 with define-record-printer from the special-forms unit, thus allowing
    57 records to be printed in a readable form. Without it, a rect instance
    58 is simply printed #<rect>, which is not very descriptive. But having
     55Both {{define-record}} and {{define-record-type}} can be used in combination
     56with {{define-record-printer}} from the {{chicken.base}} module, thus allowing
     57records to be printed in a readable form. Without it, a {{rect}} instance
     58is simply printed {{#<rect>}}, which is not very descriptive. But having
    5959written
    6060
     
    6565</enscript>
    6666
    67 (make-rect 0 0 1 2) will print #,(rect 0 0 1 2) instead of #<rect>.
     67{{(make-rect 0 0 1 2)}} will print {{#,(rect 0 0 1 2)}} instead of {{#<rect>}}.
    6868
    6969=== define-reader-ctor
    7070
    71 Now you can turn things around and read a rect instance written in the
     71Now you can turn things around and read a {{rect}} instance written in the
    7272form printed above via
    7373
    74 (define rt '#,(rect 0 0 1 2))
    75 
    76 provided you have used srfi-10's define-reader-ctor, supplied by
    77 Chicken's library unit via
    78 
    79 (define-reader-ctor 'rect make-rect)
     74  (define rt '#,(rect 0 0 1 2))
     75
     76provided you have used srfi-10's {{define-reader-ctor}}, supplied by
     77CHICKEN's {{chicken.read-syntax}} module via
     78
     79  (define-reader-ctor 'rect make-rect)
    8080
    8181=== The records egg
    8282
    83 Up to now, you have used a syntactic interface to rects. There is a
    84 procedural one as well, provided by the records egg.  Now I can reduce
     83Up to now, you have used a syntactic interface to {{rects}}. There is a
     84procedural one as well, provided by the [[/eggref/5/records egg|records]].  Now I can reduce
    8585the number of identifiers by packaging accessors and modifiers with
    86 srfi-17's getter-with-setter, so that set! can work on accessor
    87 expressions.  But note that define-record-printer is used a bit
     86srfi-17's {{getter-with-setter}}, so that {{set!}} can work on accessor
     87expressions.  But note that {{define-record-printer}} is used a bit
    8888different then above, because you must use the name of the record-type,
    8989not the record-type-descriptor.
     
    107107</enscript>
    108108
    109 Now with (define rt (Rect 0 0 1 2)), rt will print #,(rect 0 0 1 2),
    110 (rect-x rt) will print 0 and after (set! (rect-x rt) 10) it will print
    111 10.
     109Now with {{(define rt (Rect 0 0 1 2))}}, {{rt}} will print {{#,(rect 0 0 1 2)}},
     110{{(rect-x rt)}} will print {{0}} and after {{(set! (rect-x rt) 10)}} it will print
     111{{10}}.
    112112
    113113Henceforth, record-type descriptors will be written all uppercase, and
     
    116116=== srfi-99
    117117
    118 Chicken's implementation of srfi-99, written by Thomas Chust, combines
     118CHICKENS's implementation of srfi-99, written by Thomas Chust, combines
    119119both interfaces, the syntactic and the procedural one, so that they can
    120120be used interchangeably, and it provides its own version of
     
    125125srfi-99-records-inspection and srfi-99-records-syntactic, the former
    126126being an extension to the srfi-99 document. As that document postulates,
    127 Chicken's srfi-99 records not only implement type extension in the form
     127CHICKENS's srfi-99 records not only implement type extension in the form
    128128of single inheritance, but also all optional extensions, the srfi-document
    129129mentions: opaque, sealed and non-generative records.
     
    156156referenced in a vector instead of a list and they are tagged as mutable.
    157157Alternatively, you could have tagged them with immutable. Short forms of,
    158 e.g. (mutable x) or (immutable y) are accepted as well, namely (x) or y
     158e.g. {{(mutable x)}} or {{(immutable y)}} are accepted as well, namely {{(x)}} or {{y}}
    159159respectively, but I strongly recommend, not to use them: It's all to
    160 easy to write x when you mean (x) and later wonder, why a setter doesn't
     160easy to write {{x}} when you mean {{(x)}} and later wonder, why a setter doesn't
    161161work, although you have explicitly supplied a correct one.
    162 Another change is, that explicit mutators via getter-with-setter are
     162Another change is, that explicit mutators via {{getter-with-setter}} are
    163163missing; they are automatically provided by the srfi-99 library, so
    164 something like (set!  (rect-x rt) 10) would work as expected, provided
     164something like {{(set!  (rect-x rt) 10)}} would work as expected, provided
    165165the field x is tagged as mutable.
    166166The name-changes of srfi-99 with the rtd abbreviations, untypical for
     
    179179Note, that the slot vector is empty. There are no new slots. But the
    180180constructor and the predicate need to be corrected to reflect the
    181 spezialization. A raw rtd-constructor of a child rtd accepts the
     181spezialization. A raw {{rtd-constructor}} of a child rtd accepts the
    182182parent's and the child's slots in this order as arguments.
    183183
    184184Now, every square is a rectangle, but not every rectangle a square!
    185185Note, that albeit the rtd SQUARE doesn't provide any accessors, they are
    186 inherited from rtd RECT. But the names rect-x etc. are a bit foreign to
     186inherited from rtd RECT. But the names {{rect-x}} etc. are a bit foreign to
    187187a square. That's where dynamic binding and object orientation comes into
    188188the play ...
     
    191191
    192192The above is fine, but there is a serious drawback: The accessors are
    193 statically bound. So we had to prefix the slot-names with the rtd-name
     193statically bound. So we had to prefix the slot-names with the {{rtd-name}}
    194194to avoid name clashes, forcing the user of these records to remember in
    195195which hierarchy-level the accessors were defined; a real problem, if the
    196196hierarchy is deep.  We would prefer to have them dynamically bound and
    197 given them names like x, y, w and h, the actual routine being looked up
     197given them names like {{x}}, {{y}}, {{w}} and {{h}}, the actual routine being looked up
    198198by the system.  That's exactly what record-properties provide.
    199199
    200200First we have to define, for each method to be implemented, in
    201201particular for the slots, record-properties, e.g.
    202 (define-record-property x)
    203 
    204 This defines  procedures of at least one argument, which will eventually
     202{{(define-record-property x)}}/
     203
     204This defines procedures of at least one argument, which will eventually
    205205be applied to records, which have corresponding procedures bound to its
    206206record-type-descriptor. Before this happens, these properties will
    207 allways return the default value #f independently of its argument; try
    208 this by issuing (x #t) or (x #t #t #t). If you want another value to be
     207allways return the default value {{#f}} independently of its argument; try
     208this by issuing {{(x #t)}} or {{(x #t #t #t)}}. If you want another value to be
    209209returned, you must supply it as a second argument to
    210 define-record-property. This second argument is returned via Chicken's
     210{{define-record-property}}. This second argument is returned via CHICKEN's
    211211constantly procedure.  But you should be aware, that symbols as second
    212 argument to record-properties are treated specially: inside rtd's they
     212argument to {{record-properties}} are treated specially: inside rtd's they
    213213refer to accessors of equally named fields.
    214214
    215 To bind properties to record-type-descriptors, you'll use the #:property
    216 clause of make-rtd. Note, that after their implementation in the rtd,
     215To bind properties to record-type-descriptors, you'll use the {{#:property}}
     216clause of {{make-rtd}}. Note, that after their implementation in the rtd,
    217217the properties will be more or less dynamically bound versions of the
    218 statically bound accessors like rect-x and the corresponding mutators.
     218statically bound accessors like {{rect-x}} and the corresponding mutators.
    219219The accessors are simply referenced by their names as a symbol, while
    220220the mutators must be curried.
     
    278278</enscript>
    279279
    280 Now x, x! and friends are dynamically bound. They are called like (x rt)
    281 and ((x! rt) a). If there where other records with properties x and x!
     280Now {{x}}, {{x!}} and friends are dynamically bound. They are called like {{(x rt)}}
     281and {{((x! rt) a)}}. If there where other records with properties {{x}} and {{x!}}
    282282bound to its type, always the right accessor or mutator would be chosen.
    283283To see this, let's enhance the example, so that it's more realistic. In
     
    290290they should always be in a valid state. In other words, we would like to
    291291have an invariant property, which checks the validity of an object's
    292 state, automatic documentation and properties move!, scale! and area.
     292state, automatic documentation and properties {{move!}}, {{scale!}} and {{area}}.
    293293
    294294Since invariant and automatic documentation should be available
    295295everywhere, we package corresponding properties in an abstract base
    296 type, OBJECT, to be overridden in any of its childs. Abstract types
     296type, OBJECT, to be overridden in any of its children. Abstract types
    297297don't have constructors.
    298298
     
    344344(module rects (RECT Rect rect? x x! y y! w w! h h! move! scale! area)
    345345  (import scheme objects
    346           (only chicken error define-reader-ctor)
    347           (only extras fprintf)
     346          (only (chicken base) error define-reader-ctor)
     347          (only (chicken format) fprintf)
    348348          (only srfi-99 define-record-printer define-record-property
    349349                make-rtd rtd-constructor rtd-predicate))
     
    461461  (import scheme
    462462          rects
    463           (only chicken error define-reader-ctor)
    464           (only extras fprintf)
     463          (only (chicken base) error define-reader-ctor)
     464          (only (chicken format) fprintf)
    465465          (only srfi-99 define-record-printer define-record-property
    466466                make-rtd rtd-constructor rtd-predicate))
     
    515515
    516516Interesting is, how invariant and property-names are overridden in the
    517 subtpye SQUARE. To access the parent versions, simply add the supertype
    518 RECT as a second argument to the property.
    519 Another interesting point is the constructor Square. You can't simply
    520 call (Rect x y l l). This would create a RECT, not a SQUARE. But the
     517subtpye {{SQUARE}}. To access the parent versions, simply add the supertype
     518{{RECT}} as a second argument to the property.
     519Another interesting point is the constructor {{Square}}. You can't simply
     520call {{(Rect x y l l)}}. This would create a {{RECT}}, not a {{SQUARE}}. But the
    521521latter has four slots, not three. So you must call
    522 ((rtd-constructor SQUARE) x y l l) and check the invariant. Note in
     522{{((rtd-constructor SQUARE) x y l l)}} and check the invariant. Note in
    523523passing that child constructors always expect the parent slots before the
    524524child slots. The latter don't exist in the present case.
    525 Note also, that property l is simply equivalent to property w, but l!
    526 isn't equivalent to w!.
    527 And last, but not least, properties move!, scale! and area needn't be
    528 redefined in SQUARE, the parent versions do the job.
    529 
    530 Now you can test your objects as well as some inpectors, issuing e.g.
     525Note also, that property {{l}} is simply equivalent to property {{w}}, but {{l!}}
     526isn't equivalent to {{w!}}.
     527And last, but not least, properties {{move!}}, {{scale!}} and area needn't be
     528redefined in {{SQUARE}}, the parent versions do the job.
     529
     530Now you can test your objects as well as some inspectors, issuing e.g.
    531531
    532532<enscript highlight=scheme>
     
    589589In this pattern, instead of calling the property move! as above,
    590590
    591 ((move! sq) 10 20),
    592 
    593 a move! message is send to sq's only property, its handler, as follows
    594 
    595 ((handle sq) (move! 10 20))
     591{{((move! sq) 10 20)}},
     592
     593a {{move!}} message is send to {{sq}}'s only property, its handler, as follows
     594
     595{{((handle sq) (move! 10 20))}}
    596596
    597597Whereas in the method-properties pattern, encapsulation is achieved by
     
    603603In essence, variant records supply two macros,
    604604
    605 * define-variant-type
    606 * variant-case
     605* {{define-variant-type}}
     606* {{variant-case}}
    607607
    608608the former defining a series of constructors, the latter discriminating
     
    614614(module objects (OBJECT handle OBJECT-MESSAGE object-message? invariant messages)
    615615  (import scheme
    616           (only chicken error)
    617           (only data-structures constantly)
     616          (only (chicken base) constantly error)
    618617          (only srfi-99
    619618                define-record-property
     
    656655property then inspects -- using variant-case -- the message constructors
    657656in sequence and invokes the code of the first matching one, after having
    658 checked, that msg is indeed of tye OBJECT-MESSAGE, invoking the else
     657checked, that {{msg}} is indeed of type {{OBJECT-MESSAGE}}, invoking the else
    659658clause otherwise. Here, the else clause does nothing on purpose: Message
    660659handling can be used for broadcasting. Hence, if a message is not
     
    666665procedures.
    667666
    668 Here's the new version of the rects module:
     667Here's the new version of the {{rects}} module:
    669668
    670669<enscript highlight=scheme>
     
    672671               h h! move! scale! area)
    673672  (import scheme objects
    674           (only chicken error void define-reader-ctor)
    675           (only data-structures constantly)
    676           (only extras fprintf)
     673          (only (chicken base) constantly error void define-reader-ctor)
     674          (only (chicken format) fprintf)
    677675          (only srfi-99 define-record-printer define-record-property
    678676                define-variant-type variant-case
     
    770768
    771769The interesting thing here is, that of course, the handler can be
    772 defined outside of the record RECT and only be referenced inside.
    773 The variant-record RECT-MESSAGE does only define the new messages,
    774 whereas RECT's handler accepts messages of OBJECT-MESSAGE as well,
     770defined outside of the record {{RECT}} and only be referenced inside.
     771The variant-record {{RECT-MESSAGE}} does only define the new messages,
     772whereas {{RECT}}'s handler accepts messages of {{OBJECT-MESSAGE}} as well,
    775773they are imported with rects.
    776774
     
    780778(module squares (SQUARE SQUARE-MESSAGE Square square? square-message? l l!)
    781779  (import scheme rects ;objects
    782           (only chicken error define-reader-ctor)
    783           (only data-structures constantly)
    784           (only extras fprintf)
     780          (only (chicken base) constantly error define-reader-ctor)
     781          (only (chicken format) fprintf)
    785782          (only srfi-99 define-record-printer define-record-property
    786783                define-variant-type variant-case
     
    853850</enscript>
    854851
    855 No error occurs! Instead , only sq has changed, but rt is untouched:
    856 RECT can't handle the (l! 5) message and hence ignores it.  This is
    857 exactly the behaviour you want when broadcasing a message. There is no
     852No error occurs! Instead, only {{sq}} has changed, but {{rt}} is untouched:
     853{{RECT}} can't handle the {{(l! 5)}} message and hence ignores it.  This is
     854exactly the behaviour you want when broadcasting a message. There is no
    858855easy way to do the same with methods.
    859856
    860857==== The datatype egg
    861858
    862 Above, messages are defined with define-variant-type and processed with
     859Above, messages are defined with {{define-variant-type}} and processed with
    863860variant-case from the srfi-99 library. That's not the only possibility.
    864 You can use define-datatype and cases from the datatype egg instead, an
     861You can use {{define-datatype}} and cases from the datatype egg instead, an
    865862implementation of the equally named routines from the classic
    866863Friedman, Wand, Haynes, Essentials of programming languages.
     
    868865terminology, but simply note the differences in syntax and semantics.
    869866
    870 Whereas the constructors in define-variant-type are written as normal
    871 Scheme procedures, e.g. (move! dx dy), define-datatype constructors
     867Whereas the constructors in {{define-variant-type}} are written as normal
     868Scheme procedures, e.g. {{(move! dx dy)}}, define-datatype constructors
    872869specify argument type predicates, or, more general, preconditions:
    873 (move! (dx number?) (dy number?)). In other words, type tests are done
     870{{(move! (dx number?) (dy number?))}}. In other words, type tests are done
    874871automatically. This saves a lot of work. Moreover, by redefining a
    875872constructor in a module, the preconditions can be intensified by
     
    879876The cases construct of the datatype egg differs from variant-case above
    880877insofar, as they are not written as procedure calls. Instead of
    881 ((move! dx dy) ...) one writes (move! (dx dy) ...). This underlines the
     878{{((move! dx dy) ...)}} one writes {{(move! (dx dy) ...)}}. This underlines the
    882879fact, that the variants play different roles. They serve not only as
    883880constructors, but as discriminators and accessors as well ...
     
    908905
    909906[[/users/juergen-lorenz|Juergen Lorenz]]
    910 
Note: See TracChangeset for help on using the changeset viewer.