unofficial mirror of guile-user@gnu.org 
 help / color / mirror / Atom feed
* GOOPS: calling next-method with different arguments.
@ 2013-04-19  7:18 Tobias Brandt
  2013-04-19  8:54 ` Panicz Maciej Godek
  0 siblings, 1 reply; 7+ messages in thread
From: Tobias Brandt @ 2013-04-19  7:18 UTC (permalink / raw)
  To: guile-user

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

I posed the following question regarding constructors in Lisp/CLOS on
stackoverflow:
http://stackoverflow.com/questions/16089809/whats-the-equivalent-of-constructors-in-clos

The accepted answer uses the ability of CLOS to modifiy the arguments
passed to CALL-NEXT-METHOD.

I know it's (currently) not possible to change the method arguments when
calling next-method in GOOPS. How would you solve that problem (if
possible)?

Cheers,
Tobias

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

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

* Re: GOOPS: calling next-method with different arguments.
  2013-04-19  7:18 GOOPS: calling next-method with different arguments Tobias Brandt
@ 2013-04-19  8:54 ` Panicz Maciej Godek
  2013-04-19 22:58   ` Panicz Maciej Godek
  0 siblings, 1 reply; 7+ messages in thread
From: Panicz Maciej Godek @ 2013-04-19  8:54 UTC (permalink / raw)
  To: Tobias Brandt; +Cc: guile-user@gnu.org

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

2013/4/19 Tobias Brandt <tob.brandt@gmail.com>

> I posed the following question regarding constructors in Lisp/CLOS on
> stackoverflow:
>
> http://stackoverflow.com/questions/16089809/whats-the-equivalent-of-constructors-in-clos
>
> The accepted answer uses the ability of CLOS to modifiy the arguments
> passed to CALL-NEXT-METHOD.
>
> I know it's (currently) not possible to change the method arguments when
> calling next-method in GOOPS. How would you solve that problem (if
> possible)?
>
>
GOOPS offers a procedure `generic-function-methods`, which does what it
says, i.e. returns all the methods of a generic function.
It can be called on `initialize` generic as well, returning the list of all
defined initialize methods.
The we need to get those methods that apply to the superclass of our class.
We get those classes using `class-direct-supers`, and we can find out
whether a method applies to a certain class using `method-specializers`
procedure.
The `method-specializers` takes a method and returns a list (possibly
improper) of classes that correspond to the types of a considered method's
arguments.
Therefore, we can take a list of all initializers, such that their first
specializer belongs to one of our supers.
We can then use `apply-method` with our current object and whatever
argument list we like, and this is actually what you asked for.

I think that this should work, although I haven't tried that myself. You
can try that and let us know if it works.

Best regards,
M.

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

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

* Re: GOOPS: calling next-method with different arguments.
  2013-04-19  8:54 ` Panicz Maciej Godek
@ 2013-04-19 22:58   ` Panicz Maciej Godek
  2013-04-21 16:05     ` Tobias Brandt
  0 siblings, 1 reply; 7+ messages in thread
From: Panicz Maciej Godek @ 2013-04-19 22:58 UTC (permalink / raw)
  To: Tobias Brandt; +Cc: guile-user@gnu.org

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

Hey,
I've been trying to make some deeper inquiry. It turns out that the case is
not as simple as I thought.
I came up with the following function
(use-modules (oop goops) (ice-9 match) (srfi srfi-1))

(define (parent-methods method class)
  (let* ((supers (class-direct-supers class))
         (super? (lambda(c)(find (lambda(s)(eq? c s)) supers))))
    (filter-map (match-lambda((method (? super? super) args ...) method)
(else #f))
(map (lambda(m)(cons m (method-specializers m)))
                     (generic-function-methods method)))))

it works more or less as expected, at least for initializers (because it is
defined in such way that it only looks at the method's first argument) --
it does return a list of parents' initializers.

The problem appears if a parent's initializer contains (next-method)
invocation. Otherwise (if there's no call to (next-method)) the method
contains a procedure in its 'procedure' slot, which can be accessed using
`method-procedure`, and then called or applied in a regular manner.
However, if there is a call to (next-method) in a macro, the
`method-procedure` called on such method returns #f. I think the idea is
that methods are meant to be called from the context of `apply-generic` and
next-method is a continuation, but I haven't figured out how it works
exactly.
Anyway, the thing isn't as simple as I thought and perhaps someone else
could try another approach.

Sorry for causing confusion!
M.

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

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

* Re: GOOPS: calling next-method with different arguments.
  2013-04-19 22:58   ` Panicz Maciej Godek
@ 2013-04-21 16:05     ` Tobias Brandt
  2013-04-21 17:17       ` Tobias Brandt
  0 siblings, 1 reply; 7+ messages in thread
From: Tobias Brandt @ 2013-04-21 16:05 UTC (permalink / raw)
  To: Panicz Maciej Godek; +Cc: guile-user@gnu.org

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

Hi,

thanks for your input. I tried to avoid the whole next-method issue
entirely and defined a method for make on bar's metaclass instead.

(use-modules (oop goops))

(define-class <foo> () (s #:init-keyword #:s))
(define-class <bar-class> (<class>))
(define-class <bar> (<foo>) #:metaclass <bar-class>)

(define-method (make (self <bar-class>) (i <integer>))
    (make self #:s (number->string i)))

Now we can do:

(slot-ref (make <bar> 1) 's)
=> "1"

Old constructor still works (it has to, because it's called in the method
we just defined):

(slot-ref (make <bar> #:s "baz") 's)
=> "baz"


However, I have no idea what the performance implications of creating a new
metaclass are. IIRC, in Smalltalk every class has its own metaclass
automatically and that doesn't seem to cause any problems.


Regards,
Tobias



On 20 April 2013 00:58, Panicz Maciej Godek <godek.maciek@gmail.com> wrote:

> Hey,
> I've been trying to make some deeper inquiry. It turns out that the case
> is not as simple as I thought.
> I came up with the following function
> (use-modules (oop goops) (ice-9 match) (srfi srfi-1))
>
> (define (parent-methods method class)
>   (let* ((supers (class-direct-supers class))
>          (super? (lambda(c)(find (lambda(s)(eq? c s)) supers))))
>     (filter-map (match-lambda((method (? super? super) args ...) method)
> (else #f))
>  (map (lambda(m)(cons m (method-specializers m)))
>                      (generic-function-methods method)))))
>
> it works more or less as expected, at least for initializers (because it
> is defined in such way that it only looks at the method's first argument)
> -- it does return a list of parents' initializers.
>
> The problem appears if a parent's initializer contains (next-method)
> invocation. Otherwise (if there's no call to (next-method)) the method
> contains a procedure in its 'procedure' slot, which can be accessed using
> `method-procedure`, and then called or applied in a regular manner.
> However, if there is a call to (next-method) in a macro, the
> `method-procedure` called on such method returns #f. I think the idea is
> that methods are meant to be called from the context of `apply-generic` and
> next-method is a continuation, but I haven't figured out how it works
> exactly.
> Anyway, the thing isn't as simple as I thought and perhaps someone else
> could try another approach.
>
> Sorry for causing confusion!
> M.
>

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

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

* Re: GOOPS: calling next-method with different arguments.
  2013-04-21 16:05     ` Tobias Brandt
@ 2013-04-21 17:17       ` Tobias Brandt
  2013-04-22  4:39         ` Daniel Hartwig
  0 siblings, 1 reply; 7+ messages in thread
From: Tobias Brandt @ 2013-04-21 17:17 UTC (permalink / raw)
  To: Panicz Maciej Godek; +Cc: guile-user@gnu.org

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

I just noticed something: next-method *already* supports calling it with
different arguments. It's just not documented.

(define-class <foo> ())
(define-class <bar> (<foo>))

(define-method (f (self <foo>) (x <number>))
    (format #t "foo: ~a\n" x))

(define-method (f (self <bar>) (x <number>))
    (next-method self (1+ x))  ;; change arguments
    (format #t "bar: ~a\n" x))

(f (make <bar>) 1)

=>
foo: 2
bar: 1




On 21 April 2013 18:05, Tobias Brandt <tob.brandt@gmail.com> wrote:

> Hi,
>
> thanks for your input. I tried to avoid the whole next-method issue
> entirely and defined a method for make on bar's metaclass instead.
>
> (use-modules (oop goops))
>
> (define-class <foo> () (s #:init-keyword #:s))
> (define-class <bar-class> (<class>))
> (define-class <bar> (<foo>) #:metaclass <bar-class>)
>
> (define-method (make (self <bar-class>) (i <integer>))
>     (make self #:s (number->string i)))
>
> Now we can do:
>
> (slot-ref (make <bar> 1) 's)
> => "1"
>
> Old constructor still works (it has to, because it's called in the method
> we just defined):
>
> (slot-ref (make <bar> #:s "baz") 's)
> => "baz"
>
>
> However, I have no idea what the performance implications of creating a
> new metaclass are. IIRC, in Smalltalk every class has its own metaclass
> automatically and that doesn't seem to cause any problems.
>
>
> Regards,
> Tobias
>
>
>
> On 20 April 2013 00:58, Panicz Maciej Godek <godek.maciek@gmail.com>wrote:
>
>> Hey,
>> I've been trying to make some deeper inquiry. It turns out that the case
>> is not as simple as I thought.
>> I came up with the following function
>> (use-modules (oop goops) (ice-9 match) (srfi srfi-1))
>>
>> (define (parent-methods method class)
>>   (let* ((supers (class-direct-supers class))
>>          (super? (lambda(c)(find (lambda(s)(eq? c s)) supers))))
>>     (filter-map (match-lambda((method (? super? super) args ...) method)
>> (else #f))
>>  (map (lambda(m)(cons m (method-specializers m)))
>>                      (generic-function-methods method)))))
>>
>> it works more or less as expected, at least for initializers (because it
>> is defined in such way that it only looks at the method's first argument)
>> -- it does return a list of parents' initializers.
>>
>> The problem appears if a parent's initializer contains (next-method)
>> invocation. Otherwise (if there's no call to (next-method)) the method
>> contains a procedure in its 'procedure' slot, which can be accessed using
>> `method-procedure`, and then called or applied in a regular manner.
>> However, if there is a call to (next-method) in a macro, the
>> `method-procedure` called on such method returns #f. I think the idea is
>> that methods are meant to be called from the context of `apply-generic` and
>> next-method is a continuation, but I haven't figured out how it works
>> exactly.
>> Anyway, the thing isn't as simple as I thought and perhaps someone else
>> could try another approach.
>>
>> Sorry for causing confusion!
>> M.
>>
>
>

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

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

* Re: GOOPS: calling next-method with different arguments.
  2013-04-21 17:17       ` Tobias Brandt
@ 2013-04-22  4:39         ` Daniel Hartwig
  2013-04-22  6:42           ` Tobias Brandt
  0 siblings, 1 reply; 7+ messages in thread
From: Daniel Hartwig @ 2013-04-22  4:39 UTC (permalink / raw)
  To: Tobias Brandt; +Cc: guile-user@gnu.org

On 22 April 2013 01:17, Tobias Brandt <tob.brandt@gmail.com> wrote:
> I just noticed something: next-method *already* supports calling it with
> different arguments. It's just not documented.
>

When changing the _type_ of an argument this will perhaps not have the
desired result.  You have to consider the specific situation quite
careful.

The list of applicable methods is already computed before
‘next-method’ is invoked, and is based on the argument types in the
original call.  If you change any of these types the applicable
methods are not recomputed, and neither should they be.  The entire
applicable list and call tree will potentially be quite different.  So
this will only work when the _already computed_ next method is
compatible with the new argument types, but even then, the method you
arrive at may be less specialized than is desired (i.e. dispatching on
<object> rather than <string>).

In your actual use case involving object construction, you can
specialize ‘initialize’ (see later) and ‘next-method’ will work fine
because ‘initialize’ is not dispatching on argument types other than
the class.  The next method is the next superclass' intializer in most
situations, which is what you want.

Generally however, and in cases like the example you gave for ‘f’, it
will not work to change argument types with ‘next-method’.  Suppose
you want a method specialized on the base class and a string:

(define-method (g (foo <foo>) (x <string>))
  (format #t "foo: ~a\n" x))

and another method specialized on the derived class and a number:

(define-method (g (bar <bar>) (x <number>))
  (next-method self (number->string x))
  (format #t "bar: ~a\n" x))

When you make the original call, the list of applicable methods is:

(compute-applicable-methods g (list (make <bar>) 1))
=> (#<<method> (<bar> <number>) 99840a0>)

Note that the method dispatching on <foo> is not in that list, indeed
there is only a single applicable method.  Hence, the call to
‘next-method’ within the second method will throw an error:

(g (make <bar>) 1)
ERROR: In procedure scm-error:
ERROR: No next method when calling #<<generic> g (2)>
with arguments (#<<bar> 95d2138> "1")

In a situation like this, just call the desired generic directly:

(define-method (g (bar <bar>) (x <number>))
  (g bar (number->string x))
  (format #t "bar: ~a\n" x))

which will be dispatched correctly considering the new type.  This is
effectively what you have done with ‘make’ in your previous post.  The
key point to take away is that when the argument type has been
significant in dispatching the call, it is probably not correct to use
‘next-method’.

> On 21 April 2013 18:05, Tobias Brandt <tob.brandt@gmail.com> wrote:
>>
>> Hi,
>>
>> thanks for your input. I tried to avoid the whole next-method issue
>> entirely and defined a method for make on bar's metaclass instead.
>>
>> (use-modules (oop goops))
>>
>> (define-class <foo> () (s #:init-keyword #:s))
>> (define-class <bar-class> (<class>))
>> (define-class <bar> (<foo>) #:metaclass <bar-class>)
>>
>> (define-method (make (self <bar-class>) (i <integer>))
>>     (make self #:s (number->string i)))
>>

I would actually call that argument ‘class’ rather than ‘self’.

Specializing ‘make’ like this is perhaps inconvenient for classes
derived from <bar>, or when there are more slots to initialize with
keywords.  It does not take any rest argument, and the integer
argument omits a keyword that is otherwise typical of ‘make’ and
‘initialize’ methods.  Instead, you can specialize ‘initialize’ and
avoid using a custom metaclass and atypical ‘make’:

;; Using a unique keyword #:i for <bar>:
(use-modules (ice-9 optargs))
(define-method (initialize (bar <bar>) initargs)
  (let-keywords initargs #t
      (i)
    (cond (i (next-method bar (append `(#:s ,(number->string i))
                                      initargs)))
          (else (next-method)))))


I have to wonder, what is the purpose of your real <bar> anyway?  Is
it just a convenience so you can construct the base class using a
number instead of a string?  If so, why not just use:

(define-method (make-bar (i <integer>))
  (make <foo> #:s (number->string i)))

>> However, I have no idea what the performance implications of creating a
>> new metaclass are. IIRC, in Smalltalk every class has its own metaclass
>> automatically and that doesn't seem to cause any problems.
>>

Likewise in GOOPS, all objects use a metaclass (usually <class> in
typical cases).



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

* Re: GOOPS: calling next-method with different arguments.
  2013-04-22  4:39         ` Daniel Hartwig
@ 2013-04-22  6:42           ` Tobias Brandt
  0 siblings, 0 replies; 7+ messages in thread
From: Tobias Brandt @ 2013-04-22  6:42 UTC (permalink / raw)
  To: Daniel Hartwig; +Cc: guile-user@gnu.org

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

> When changing the _type_ of an argument this will perhaps not have the
> desired result.  You have to consider the specific situation quite
> careful.

I'm aware of that. The CL hyperspec comes with the same warning.
I would probably only use it for initialize, where the next method
is basically the super class constructor.

> I have to wonder, what is the purpose of your real <bar> anyway?  Is
> it just a convenience so you can construct the base class using a
> number instead of a string?

There is no real <bar>, it was just a stupid example. I was hoping to do
some
OOP in Scheme in the future and thought about how things translate from
Java/C#.
I know, it's not always a could idea to just literally translate things
from one
language to another.

Cheers,
Tobias


On 22 April 2013 06:39, Daniel Hartwig <mandyke@gmail.com> wrote:

> On 22 April 2013 01:17, Tobias Brandt <tob.brandt@gmail.com> wrote:
> > I just noticed something: next-method *already* supports calling it with
> > different arguments. It's just not documented.
> >
>
> When changing the _type_ of an argument this will perhaps not have the
> desired result.  You have to consider the specific situation quite
> careful.
>
> The list of applicable methods is already computed before
> ‘next-method’ is invoked, and is based on the argument types in the
> original call.  If you change any of these types the applicable
> methods are not recomputed, and neither should they be.  The entire
> applicable list and call tree will potentially be quite different.  So
> this will only work when the _already computed_ next method is
> compatible with the new argument types, but even then, the method you
> arrive at may be less specialized than is desired (i.e. dispatching on
> <object> rather than <string>).
>
> In your actual use case involving object construction, you can
> specialize ‘initialize’ (see later) and ‘next-method’ will work fine
> because ‘initialize’ is not dispatching on argument types other than
> the class.  The next method is the next superclass' intializer in most
> situations, which is what you want.
>
> Generally however, and in cases like the example you gave for ‘f’, it
> will not work to change argument types with ‘next-method’.  Suppose
> you want a method specialized on the base class and a string:
>
> (define-method (g (foo <foo>) (x <string>))
>   (format #t "foo: ~a\n" x))
>
> and another method specialized on the derived class and a number:
>
> (define-method (g (bar <bar>) (x <number>))
>   (next-method self (number->string x))
>   (format #t "bar: ~a\n" x))
>
> When you make the original call, the list of applicable methods is:
>
> (compute-applicable-methods g (list (make <bar>) 1))
> => (#<<method> (<bar> <number>) 99840a0>)
>
> Note that the method dispatching on <foo> is not in that list, indeed
> there is only a single applicable method.  Hence, the call to
> ‘next-method’ within the second method will throw an error:
>
> (g (make <bar>) 1)
> ERROR: In procedure scm-error:
> ERROR: No next method when calling #<<generic> g (2)>
> with arguments (#<<bar> 95d2138> "1")
>
> In a situation like this, just call the desired generic directly:
>
> (define-method (g (bar <bar>) (x <number>))
>   (g bar (number->string x))
>   (format #t "bar: ~a\n" x))
>
> which will be dispatched correctly considering the new type.  This is
> effectively what you have done with ‘make’ in your previous post.  The
> key point to take away is that when the argument type has been
> significant in dispatching the call, it is probably not correct to use
> ‘next-method’.
>
> > On 21 April 2013 18:05, Tobias Brandt <tob.brandt@gmail.com> wrote:
> >>
> >> Hi,
> >>
> >> thanks for your input. I tried to avoid the whole next-method issue
> >> entirely and defined a method for make on bar's metaclass instead.
> >>
> >> (use-modules (oop goops))
> >>
> >> (define-class <foo> () (s #:init-keyword #:s))
> >> (define-class <bar-class> (<class>))
> >> (define-class <bar> (<foo>) #:metaclass <bar-class>)
> >>
> >> (define-method (make (self <bar-class>) (i <integer>))
> >>     (make self #:s (number->string i)))
> >>
>
> I would actually call that argument ‘class’ rather than ‘self’.
>
> Specializing ‘make’ like this is perhaps inconvenient for classes
> derived from <bar>, or when there are more slots to initialize with
> keywords.  It does not take any rest argument, and the integer
> argument omits a keyword that is otherwise typical of ‘make’ and
> ‘initialize’ methods.  Instead, you can specialize ‘initialize’ and
> avoid using a custom metaclass and atypical ‘make’:
>
> ;; Using a unique keyword #:i for <bar>:
> (use-modules (ice-9 optargs))
> (define-method (initialize (bar <bar>) initargs)
>   (let-keywords initargs #t
>       (i)
>     (cond (i (next-method bar (append `(#:s ,(number->string i))
>                                       initargs)))
>           (else (next-method)))))
>
>
> I have to wonder, what is the purpose of your real <bar> anyway?  Is
> it just a convenience so you can construct the base class using a
> number instead of a string?  If so, why not just use:
>
> (define-method (make-bar (i <integer>))
>   (make <foo> #:s (number->string i)))
>
> >> However, I have no idea what the performance implications of creating a
> >> new metaclass are. IIRC, in Smalltalk every class has its own metaclass
> >> automatically and that doesn't seem to cause any problems.
> >>
>
> Likewise in GOOPS, all objects use a metaclass (usually <class> in
> typical cases).
>

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

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

end of thread, other threads:[~2013-04-22  6:42 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-04-19  7:18 GOOPS: calling next-method with different arguments Tobias Brandt
2013-04-19  8:54 ` Panicz Maciej Godek
2013-04-19 22:58   ` Panicz Maciej Godek
2013-04-21 16:05     ` Tobias Brandt
2013-04-21 17:17       ` Tobias Brandt
2013-04-22  4:39         ` Daniel Hartwig
2013-04-22  6:42           ` Tobias Brandt

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).