unofficial mirror of guile-user@gnu.org 
 help / color / mirror / Atom feed
* GOOPS constructors
@ 2014-07-22 12:18 Marko Rauhamaa
  2014-07-22 12:22 ` Tobias Brandt
  0 siblings, 1 reply; 8+ messages in thread
From: Marko Rauhamaa @ 2014-07-22 12:18 UTC (permalink / raw)
  To: guile-user


Consider this simple program:

========================================================================
(use-modules
 (oop goops)
 (ice-9 optargs))

(define-class <rectangle> ()
  (width #:accessor width #:init-keyword #:width)
  (height #:accessor height #:init-keyword #:height))

(define-method (area (@ <rectangle>))
  (* (height @) (width @)))

(define-class <square> (<rectangle>)
  (side #:accessor side #:init-keyword #:side))

(define-method (initialize (@ <square>) args)
  (let-keywords
   args #f ((side #f))
   (next-method @ (list #:width side #:height side))))

(format #t "~S\n" (area (make <square> #:side 3)))
(format #t "~S\n" (side (make <square> #:side 3)))
========================================================================

The program outputs:

========================================================================
9
ERROR: Unbound slot in object #<<square> b76cfec0>
========================================================================

I understand that by overriding <square>'s initialize method I'm losing
the magic of the default initializer. How could I have the cake and eat
it, too?


Marko



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: GOOPS constructors
  2014-07-22 12:18 GOOPS constructors Marko Rauhamaa
@ 2014-07-22 12:22 ` Tobias Brandt
  2014-07-22 13:03   ` Marko Rauhamaa
  0 siblings, 1 reply; 8+ messages in thread
From: Tobias Brandt @ 2014-07-22 12:22 UTC (permalink / raw)
  To: Marko Rauhamaa; +Cc: guile-user@gnu.org

[-- Attachment #1: Type: text/plain, Size: 1500 bytes --]

Couldn't just define <square> like this:

(define-class <square> (<rectangle>))

then define a side method:

(define-method (side (@ <square>)) (slot-ref @ 'height))

This way, you're not storing the same information twice.


On 22 July 2014 14:18, Marko Rauhamaa <marko@pacujo.net> wrote:

>
> Consider this simple program:
>
> ========================================================================
> (use-modules
>  (oop goops)
>  (ice-9 optargs))
>
> (define-class <rectangle> ()
>   (width #:accessor width #:init-keyword #:width)
>   (height #:accessor height #:init-keyword #:height))
>
> (define-method (area (@ <rectangle>))
>   (* (height @) (width @)))
>
> (define-class <square> (<rectangle>)
>   (side #:accessor side #:init-keyword #:side))
>
> (define-method (initialize (@ <square>) args)
>   (let-keywords
>    args #f ((side #f))
>    (next-method @ (list #:width side #:height side))))
>
> (format #t "~S\n" (area (make <square> #:side 3)))
> (format #t "~S\n" (side (make <square> #:side 3)))
> ========================================================================
>
> The program outputs:
>
> ========================================================================
> 9
> ERROR: Unbound slot in object #<<square> b76cfec0>
> ========================================================================
>
> I understand that by overriding <square>'s initialize method I'm losing
> the magic of the default initializer. How could I have the cake and eat
> it, too?
>
>
> Marko
>
>

[-- Attachment #2: Type: text/html, Size: 2185 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: GOOPS constructors
  2014-07-22 12:22 ` Tobias Brandt
@ 2014-07-22 13:03   ` Marko Rauhamaa
  2014-07-22 13:09     ` Tobias Brandt
  2014-07-23 15:02     ` Barry Fishman
  0 siblings, 2 replies; 8+ messages in thread
From: Marko Rauhamaa @ 2014-07-22 13:03 UTC (permalink / raw)
  To: Tobias Brandt; +Cc: guile-user@gnu.org

Tobias Brandt <tob.brandt@googlemail.com>:

> Couldn't just define <square> like this:
>
> (define-class <square> (<rectangle>))
>
> then define a side method:
>
> (define-method (side (@ <square>)) (slot-ref @ 'height))
>
> This way, you're not storing the same information twice.

Well, I was trying to find a simple toy example to demonstrate the need,
but maybe I'll need to find a more compelling one.

How about:

========================================================================
(define-class <error> ()
  (msg #:getter msg #:init-keyword #:msg))

(define-class <file-error> (<error>)
  (path #:getter path #:init-keyword #:path)
  (code #:getter code #:init-keyword #:code)
  (fs #:getter fs #:init-keyword #:fs))

(define-method (initialize (@ <file-error>) args)
  (let-keywords
   args #f ((path #f) (code #f) (fs #f))
   (next-method @ (list #:msg (format #f "File error in ~S (~S)" path code)))))
========================================================================

How do I now write the getters for path, code and fs?


Marko



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: GOOPS constructors
  2014-07-22 13:03   ` Marko Rauhamaa
@ 2014-07-22 13:09     ` Tobias Brandt
  2014-07-22 13:15       ` Marko Rauhamaa
  2014-07-23 15:02     ` Barry Fishman
  1 sibling, 1 reply; 8+ messages in thread
From: Tobias Brandt @ 2014-07-22 13:09 UTC (permalink / raw)
  To: Marko Rauhamaa; +Cc: guile-user@gnu.org

[-- Attachment #1: Type: text/plain, Size: 1590 bytes --]

I don't have access to guile currently, so I can't try this. But I think
you can just pass path, code, and fs to next-method.
They should eventually end up in the default initialize.

(define-method (initialize (@ <file-error>) args)
  (let-keywords
   args #f ((path #f) (code #f) (fs #f))
   (next-method @ (list #:msg (format #f "File error in ~S (~S)" path code)
#:path path #:code code #:fs fs))))


On 22 July 2014 15:03, Marko Rauhamaa <marko@pacujo.net> wrote:

> Tobias Brandt <tob.brandt@googlemail.com>:
>
> > Couldn't just define <square> like this:
> >
> > (define-class <square> (<rectangle>))
> >
> > then define a side method:
> >
> > (define-method (side (@ <square>)) (slot-ref @ 'height))
> >
> > This way, you're not storing the same information twice.
>
> Well, I was trying to find a simple toy example to demonstrate the need,
> but maybe I'll need to find a more compelling one.
>
> How about:
>
> ========================================================================
> (define-class <error> ()
>   (msg #:getter msg #:init-keyword #:msg))
>
> (define-class <file-error> (<error>)
>   (path #:getter path #:init-keyword #:path)
>   (code #:getter code #:init-keyword #:code)
>   (fs #:getter fs #:init-keyword #:fs))
>
> (define-method (initialize (@ <file-error>) args)
>   (let-keywords
>    args #f ((path #f) (code #f) (fs #f))
>    (next-method @ (list #:msg (format #f "File error in ~S (~S)" path
> code)))))
> ========================================================================
>
> How do I now write the getters for path, code and fs?
>
>
> Marko
>

[-- Attachment #2: Type: text/html, Size: 2309 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: GOOPS constructors
  2014-07-22 13:09     ` Tobias Brandt
@ 2014-07-22 13:15       ` Marko Rauhamaa
  0 siblings, 0 replies; 8+ messages in thread
From: Marko Rauhamaa @ 2014-07-22 13:15 UTC (permalink / raw)
  To: Tobias Brandt; +Cc: guile-user@gnu.org

Tobias Brandt <tob.brandt@googlemail.com>:

> I don't have access to guile currently, so I can't try this. But I
> think you can just pass path, code, and fs to next-method. They should
> eventually end up in the default initialize.

Hey, that was it! Thank you.


Marko



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: GOOPS constructors
  2014-07-22 13:03   ` Marko Rauhamaa
  2014-07-22 13:09     ` Tobias Brandt
@ 2014-07-23 15:02     ` Barry Fishman
  2014-07-23 17:42       ` Marko Rauhamaa
  1 sibling, 1 reply; 8+ messages in thread
From: Barry Fishman @ 2014-07-23 15:02 UTC (permalink / raw)
  To: guile-user


On 2014-07-22 16:03:43 +0300, Marko Rauhamaa wrote:
> Tobias Brandt <tob.brandt@googlemail.com>:
> Well, I was trying to find a simple toy example to demonstrate the need,
> but maybe I'll need to find a more compelling one.
>
> How about:
>
> ========================================================================
> (define-class <error> ()
>   (msg #:getter msg #:init-keyword #:msg))
>
> (define-class <file-error> (<error>)
>   (path #:getter path #:init-keyword #:path)
>   (code #:getter code #:init-keyword #:code)
>   (fs #:getter fs #:init-keyword #:fs))
>
> (define-method (initialize (@ <file-error>) args)
>   (let-keywords
>    args #f ((path #f) (code #f) (fs #f))
>    (next-method @ (list #:msg (format #f "File error in ~S (~S)" path code)))))
> ========================================================================
>
> How do I now write the getters for path, code and fs?

If you need to redefine "initialize" to do what you want, this is a
clear indication that you might using the wrong abstraction.

You are trying to force methods to be a part of the class definition.
They don't belong there.

Although this is done in other object oriented languages, this it not a
part of CLOS/GOOPS.  In GOOPS, methods and data are distinct.  Classes
form a hierarchy of the types of objects.  Methods provide an common
interface for a collection of object types.  Languages like Java
recognize the distinction, but still force everything (including
independent functions) into classes.

You can build your <shape> class tree to include <rectangle>, <square>,
etc.  You can add methods for area, circumference, etc.  You can
make your code a package for others to share, or buy.

If I then come along and use your package in a application.  I might want
to draw out the shapes.  My draw methods might be GTK specific.

Where does my code belong?  You may not want to update your package to
include my GTK specific draw functions.  In other languages, if I have
your source, I can patch your package to include my own draw <shape>
virtual method, I will then have to maintain it and track all your bug
fixes and changes. I might build a visitor pattern, if you made that
possible, but this does require me to track all your future class
structure changes.

However in GOOPS, I can build my own draw methods for your derived
classes along with any other independent packages that I use.  I could
restrict my methods to use only the access methods you provide for each
of the specific classes I support, and avoid internal class information.
I don't need to touch your code.

GOOPS will auto-generate simple assessors for you, but anything more
complex really should not be pushed into the class definition.  After
having to use gross mechanisms (like visitor patterns or even #ifdefs)
in languages like C++, I find this as a significant improvement.

If two classes have no common fields, like <rectangle> and <square>,
they just share a common interface, they are not derived from each
other.  They only need a common interface class (like <shape>), if this
is required for class inheritance (like being used in higher level
method signatures).

There is no need to juggle a set of Interface only base classes, and the
associated derived classes for a package, each time an application needs
a new interface method.  Interface classes are just needed for pure
class inheritance.

Application specific methods can be added without having to modify
the class definition in an external package.

--8<---------------cut here---------------start------------->8---
(define-class <error> () ) ; No common class fields

(define-class <file-error> (<error>)
  (path #:getter path #:init-keyword #:path)
  (code #:getter code #:init-keyword #:code)
  (fs   #:getter fs   #:init-keyword #:fs))

(define-method (get-message (err <file-error>))
  (format #f "File error in ~S (~S)" (path err) (code err)))
--8<---------------cut here---------------end--------------->8---

You don't get any "missing method" until run-time, but Scheme is a
dynamic language, so this isn't generally true anyway.  You gain the
ability to make error messages unique in each of several applications
that share common error throwing code.

(define-method (my-message (err <file-error>))
  (format #f "ERROR: Code ~S in ~S" (code err) (path err)))

(define-method (my-message (err <error>))
  (format #f "ERROR: Unknown error type ~S" err))

--
Barry Fishman




^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: GOOPS constructors
  2014-07-23 15:02     ` Barry Fishman
@ 2014-07-23 17:42       ` Marko Rauhamaa
       [not found]         ` <m3lhri21kk.fsf@barry_fishman.acm.org>
  0 siblings, 1 reply; 8+ messages in thread
From: Marko Rauhamaa @ 2014-07-23 17:42 UTC (permalink / raw)
  To: Barry Fishman; +Cc: guile-user

Barry Fishman <barry_fishman@acm.org>:

> If you need to redefine "initialize" to do what you want, this is a
> clear indication that you might using the wrong abstraction.

If I have an IS-A relation, that is often described as class hierarchy.
The general question here is, is IS-A-WHOSE a valid basis for class
derivation?

For example,

   A thunk IS-A function WHOSE argument list is empty.

   A square IS-A rectangle WHOSE width equals its height.

   Green light IS electro-magnetic radiation WHOSE wavelength is 520 nm.

   A Texan IS-AN American WHOSE permanent address is in Texas.

In many programming languages, such restrictions are routinely expressed
in object constructors:

    class GreenLight(Radiation):
         def __init__(self):
              Radiation.__init__(self, wavelength=520e-9)


Marko



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: GOOPS constructors
       [not found]         ` <m3lhri21kk.fsf@barry_fishman.acm.org>
@ 2014-07-25 10:26           ` Marko Rauhamaa
  0 siblings, 0 replies; 8+ messages in thread
From: Marko Rauhamaa @ 2014-07-25 10:26 UTC (permalink / raw)
  To: Barry Fishman; +Cc: guile-user

Barry Fishman <barry_fishman@acm.org>:

> I am probably not the best person to go into this. I am more familiar
> with CLOS.
>
> As Tobias Brandt previous described, you can do what you want with
> overriding the initialization method.  I just find the resulting
> code harder to read, and conceptually a bit fuzzy.

I'm actually making a realization that GOOPS is probably not what I
want. The answer is much closer:

  <URL: https://www.gnu.org/software/guile/manual/html_node/OO-C
  losure.html#OO-Closure>

I have written a tiny (= 61 lines at the moment) object system based on
that idea. Namely, I don't need classes, I only need objects and
convenient dispatching.

Python is notorious for ducktyping: an object is judged by its personal
behavior and not by its membership in a class. However, even Python is
keen on the "class" keyword, and it assigns each object a type or class.
For the most part, Python's classes are equivalent to (constructor)
functions. So why not take that principle further and renounce the
existence of classes altogether?

In practice, Python's classes are very good. The only downside is that
when you need to construct one-off objects with special methods, you
must first create an one-off inner class and then construct your object.
It would be much cleaner and Scheme-like to just construct your one-off
object with a lambda constructor that sets the methods without bothering
to name a class.

This becomes a practical issue in network programming, where you deal
with state machines. You could of course organize your state machine
matrix in functions that dispatch on states:

   (define (received-eof)
      (case state
       ((#:IDLE) ...)
       ((#:READY) ...)
       ((#:SHUTTING-DOWN) ...)
       (else ...)))

   (set! state #:IDLE)

(which Python couldn't do, BTW). However, I find it elegant to employ
the state pattern:

   (define (<IDLE>)
     (define (received-eof)
        ...)
     (make-object #f #f received-eof)))

   (set! state (<IDLE>))

where "make-object" is from my tiny object system.

What's specially appealing with this kind of object system (as opposed
to GOOPS or Python) is that slot handling is more natural, IMO. For
example:

   (define (<point> .x .y)
     (define (x) .x)
     (define (y) .y)
     (define (reset! x y) (set! .x x) (set! .y y))
     (define (clear!) (reset! 0 0))
     (make-object #f #f x y reset! clear!))

Now,

   (let ((p (<point> 3 4)))
     (-> p x))
   =>
   3

(I'm still looking for the most elegant syntax for method dispatching. I
don't particularly like the looks of (p 'x). Maybe (p #:x).)


Marko



^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2014-07-25 10:26 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-07-22 12:18 GOOPS constructors Marko Rauhamaa
2014-07-22 12:22 ` Tobias Brandt
2014-07-22 13:03   ` Marko Rauhamaa
2014-07-22 13:09     ` Tobias Brandt
2014-07-22 13:15       ` Marko Rauhamaa
2014-07-23 15:02     ` Barry Fishman
2014-07-23 17:42       ` Marko Rauhamaa
     [not found]         ` <m3lhri21kk.fsf@barry_fishman.acm.org>
2014-07-25 10:26           ` Marko Rauhamaa

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).