From: Pascal Costanza on
On 04/08/2010 19:20, RG wrote:
> In article<9mpzkx2ibyb.fsf(a)runa.se>, bjorn(a)runa.se (Björn Lindberg)
> wrote:
>
>> RG<rNOSPAMon(a)flownet.com> writes:
>>
>>> In article<9mplj8mkewt.fsf(a)runa.se>, bjorn(a)runa.se (Björn Lindberg)
>>> wrote:
>>>
>>>> RG<rNOSPAMon(a)flownet.com> writes:
>>>>
>>>>> In article<9mpwrs7kipq.fsf(a)runa.se>, bjorn(a)runa.se (Björn Lindberg)
>>>>> wrote:
>>>>>
>>>>>>> It all boils down to some common sense. Just like we use + rather than
>>>>>>> fixnum+, bignum+, float+, double+, it's often convenient to use ELT
>>>>>>> rather than AREF or NTH.
>>>>>>
>>>>>> No, these are two completely different topics. We do not use generic
>>>>>> arithmetic operators to delay an implementation choice of which number
>>>>>> type to use.
>>>>>
>>>>> Huh?!? This seems to me to be the *only* reason to use generic
>>>>> arithmetic. What else is it good for?
>>>>
>>>> To write code that can accept differing types of numbers at run-time.
>>>
>>> And how is that not "delaying an implementation choice" (to run-time) on
>>> which number type to use?
>>
>> Let us be careful not to have this discussion stray from the topic, and
>> consider the context in which that comment was made. If you think that
>> Alessio's 'typed arithmetics' are similar in nature to what I was
>> outlining in the post he replied to, can you please explain that
>> further?
>
> The original context was snipped out. I presume you're referring to
> this:
>
>> If I may interject, I do see value in abstraction, but it will usually
>> take a different form than what you propose.
>>
>> Let's say that we in our program have objects of the kind foo, indexed
>> by bar, stored in collections bork. You do not want to prematurely
>> select the implementation of bork to be either hash tables, alists or
>> something else. You now write accessors like ADD-FOO, FIND-FOO, and the
>> like, essentially creating an ADT specific to the program. This ADT
>> encapsulates whatever implementation is chosen for bork, making change
>> later easy. The advantages of this approach are plenty: The accessors
>> can be written to fit the semantics of foo, bar and bork, rather than
>> shoe-horning the behaviour of those into some generic collections
>> framework. It makes the code much more descriptive of what it is doing
>> than if it is sprinkled with "generic" REF calls, which from a
>> documentation perspective is even worse than having it filled with calls
>> to GETHASH.
>
> Alessio's typed arithmetic operators seem to me to be exactly analogous
> to your typed collections operators. ADD-FOO (presumably) only adds
> objects of type FOO. In fact, just change FOO to FIXNUM and you've got
> Alessio's example nearly verbatim.

I think Björn actually meant something like register-observer and
deregister-observer. [1] Something like this:

(defclass observable-mixin ()
((observers :initform '())))

;; specializing on standard-class here is not standard-compliant
;; but good enough for illustration purposes
(defmethod (setf slot-value-using-class) :after
((class standard-class) (object observable-mixin) slot)
;; the following should pass more information
;; but you get the idea
(mapc #'notify (slot-value object 'observers)))

(defmethod register-observer ((object observable-mixin) observer)
(pushnew observer (slot-value object 'observers)))

(defmethod deregister-observer ((object observable-mixin) observer)
(setf (slot-value object 'observers)
(remove observer (slot-value object 'observers))))

Replacing the list with a vector here is downright trivial, because it's
nicely abstracted away behind the register/deregister protocol. Adding
an abstract collection API here doesn't buy you a lot in this and
similar cases.


Pascal

[1] Björn, please correct me if I'm wrong.

--
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
From: =?iso-8859-1?Q?Bj=F6rn?= Lindberg on
RG <rNOSPAMon(a)flownet.com> writes:

> In article <9mpzkx2ibyb.fsf(a)runa.se>, bjorn(a)runa.se (Bj�rn Lindberg)
> wrote:
>
>> RG <rNOSPAMon(a)flownet.com> writes:
>>
>> > In article <9mplj8mkewt.fsf(a)runa.se>, bjorn(a)runa.se (Bj�rn Lindberg)
>> > wrote:
>> >
>> >> RG <rNOSPAMon(a)flownet.com> writes:
>> >>
>> >> > In article <9mpwrs7kipq.fsf(a)runa.se>, bjorn(a)runa.se (Bj�rn Lindberg)
>> >> > wrote:
>> >> >
>> >> >> > It all boils down to some common sense. Just like we use + rather than
>> >> >> > fixnum+, bignum+, float+, double+, it's often convenient to use ELT
>> >> >> > rather than AREF or NTH.
>> >> >>
>> >> >> No, these are two completely different topics. We do not use generic
>> >> >> arithmetic operators to delay an implementation choice of which number
>> >> >> type to use.
>> >> >
>> >> > Huh?!? This seems to me to be the *only* reason to use generic
>> >> > arithmetic. What else is it good for?
>> >>
>> >> To write code that can accept differing types of numbers at run-time.
>> >
>> > And how is that not "delaying an implementation choice" (to run-time) on
>> > which number type to use?
>>
>> Let us be careful not to have this discussion stray from the topic, and
>> consider the context in which that comment was made. If you think that
>> Alessio's 'typed arithmetics' are similar in nature to what I was
>> outlining in the post he replied to, can you please explain that
>> further?
>
> The original context was snipped out.

By you.

> I presume you're referring to
> this:
>
>> If I may interject, I do see value in abstraction, but it will usually
>> take a different form than what you propose.
>>
>> Let's say that we in our program have objects of the kind foo, indexed
>> by bar, stored in collections bork. You do not want to prematurely
>> select the implementation of bork to be either hash tables, alists or
>> something else. You now write accessors like ADD-FOO, FIND-FOO, and the
>> like, essentially creating an ADT specific to the program. This ADT
>> encapsulates whatever implementation is chosen for bork, making change
>> later easy. The advantages of this approach are plenty: The accessors
>> can be written to fit the semantics of foo, bar and bork, rather than
>> shoe-horning the behaviour of those into some generic collections
>> framework. It makes the code much more descriptive of what it is doing
>> than if it is sprinkled with "generic" REF calls, which from a
>> documentation perspective is even worse than having it filled with calls
>> to GETHASH.
>
> Alessio's typed arithmetic operators seem to me to be exactly analogous
> to your typed collections operators. ADD-FOO (presumably) only adds
> objects of type FOO. In fact, just change FOO to FIXNUM and you've got
> Alessio's example nearly verbatim.

I will explain what I mean using a trivial example. Let us say that you
are writing an interpreter for a language. In the language, it is
possible to define functions, and later reference them in various, for
instance by calling them. In the interpreter, you initially decide to
store the association between the name of a function and the function
itself in an association list. To add to the list you are going to use
PUSH, and to look up a function by name ASSOC. Now, the concern raised
in this thread was that unless you have a generic collections framework
that turns PUSH and ASSOC into (setf (ref ...) ...) and (ref ...)
respectively, it will be very difficult to change the implementation
decision to use an association list to, say, a hash table, because you
will have PUSHs and ASSOCs all over the code, making refactoring
hard. My suggestion is to create an abstraction appropriate to the
problem:

(defun add-function (name function)
(push (cons name function) *functions*))

(defun find-function (name)
(or (cdr (assoc name *functions))
(error "Undefined function: ~A" name)))

Refactoring this interpreter to use a hash table for the functions will
require change in two places. Now, this is a trivial example, but the
approach scales. You do not need a generic collections framework to be
able to delay data structure implementation decisions without risking
large code refactorings.


Bj�rn Lindberg
From: =?iso-8859-1?Q?Bj=F6rn?= Lindberg on
Pascal Costanza <pc(a)p-cos.net> writes:

> I think Bj�rn actually meant something like register-observer and
> deregister-observer. [1] Something like this:

Not specifically in terms of register/deregister, but the concept of
application specific abstraction, yes, sure. This to me is building the
language to meet the problem you are solving.


Bj�rn Lindberg
From: RG on
In article <m2y6cmglk8.fsf(a)nex.nex>, bjorn(a)runa.se (Björn Lindberg)
wrote:

> RG <rNOSPAMon(a)flownet.com> writes:
>
> > In article <9mpzkx2ibyb.fsf(a)runa.se>, bjorn(a)runa.se (Björn Lindberg)
> > wrote:
> >
> >> RG <rNOSPAMon(a)flownet.com> writes:
> >>
> >> > In article <9mplj8mkewt.fsf(a)runa.se>, bjorn(a)runa.se (Björn Lindberg)
> >> > wrote:
> >> >
> >> >> RG <rNOSPAMon(a)flownet.com> writes:
> >> >>
> >> >> > In article <9mpwrs7kipq.fsf(a)runa.se>, bjorn(a)runa.se (Björn Lindberg)
> >> >> > wrote:
> >> >> >
> >> >> >> > It all boils down to some common sense. Just like we use + rather
> >> >> >> > than
> >> >> >> > fixnum+, bignum+, float+, double+, it's often convenient to use
> >> >> >> > ELT
> >> >> >> > rather than AREF or NTH.
> >> >> >>
> >> >> >> No, these are two completely different topics. We do not use generic
> >> >> >> arithmetic operators to delay an implementation choice of which
> >> >> >> number
> >> >> >> type to use.
> >> >> >
> >> >> > Huh?!? This seems to me to be the *only* reason to use generic
> >> >> > arithmetic. What else is it good for?
> >> >>
> >> >> To write code that can accept differing types of numbers at run-time.
> >> >
> >> > And how is that not "delaying an implementation choice" (to run-time) on
> >> > which number type to use?
> >>
> >> Let us be careful not to have this discussion stray from the topic, and
> >> consider the context in which that comment was made. If you think that
> >> Alessio's 'typed arithmetics' are similar in nature to what I was
> >> outlining in the post he replied to, can you please explain that
> >> further?
> >
> > The original context was snipped out.
>
> By you.

Yes. Because it didn't seem relevant. It still doesn't, even after
reading everything you just wrote. Alessio's critique still seems
spot-on to me, and I still don't see how your kind of abstraction is in
any salient way different from type-specific arithmetic.

> > I presume you're referring to
> > this:
> >
> >> If I may interject, I do see value in abstraction, but it will usually
> >> take a different form than what you propose.
> >>
> >> Let's say that we in our program have objects of the kind foo, indexed
> >> by bar, stored in collections bork. You do not want to prematurely
> >> select the implementation of bork to be either hash tables, alists or
> >> something else. You now write accessors like ADD-FOO, FIND-FOO, and the
> >> like, essentially creating an ADT specific to the program. This ADT
> >> encapsulates whatever implementation is chosen for bork, making change
> >> later easy. The advantages of this approach are plenty: The accessors
> >> can be written to fit the semantics of foo, bar and bork, rather than
> >> shoe-horning the behaviour of those into some generic collections
> >> framework. It makes the code much more descriptive of what it is doing
> >> than if it is sprinkled with "generic" REF calls, which from a
> >> documentation perspective is even worse than having it filled with calls
> >> to GETHASH.
> >
> > Alessio's typed arithmetic operators seem to me to be exactly analogous
> > to your typed collections operators. ADD-FOO (presumably) only adds
> > objects of type FOO. In fact, just change FOO to FIXNUM and you've got
> > Alessio's example nearly verbatim.
>
> I will explain what I mean using a trivial example. Let us say that you
> are writing an interpreter for a language. In the language, it is
> possible to define functions, and later reference them in various, for
> instance by calling them. In the interpreter, you initially decide to
> store the association between the name of a function and the function
> itself in an association list. To add to the list you are going to use
> PUSH, and to look up a function by name ASSOC. Now, the concern raised
> in this thread was that unless you have a generic collections framework
> that turns PUSH and ASSOC into (setf (ref ...) ...) and (ref ...)
> respectively, it will be very difficult to change the implementation
> decision to use an association list to, say, a hash table, because you
> will have PUSHs and ASSOCs all over the code, making refactoring
> hard.

I would put that slightly differently. It's not that you want a generic
framework that turns PUSH and ASSOC into REFs. What you want to do
(IMHO) is to write your code in terms of REF to begin with, and then
have that (potentially) translated into PUSH/ASSOC or whatever.

> My suggestion is to create an abstraction appropriate to the
> problem:
>
> (defun add-function (name function)
> (push (cons name function) *functions*))
>
> (defun find-function (name)
> (or (cdr (assoc name *functions))
> (error "Undefined function: ~A" name)))
>
> Refactoring this interpreter to use a hash table for the functions will
> require change in two places. Now, this is a trivial example, but the
> approach scales. You do not need a generic collections framework to be
> able to delay data structure implementation decisions without risking
> large code refactorings.

This is the abstraction approach advocated in SICP. It has two problems:

1) It's more work than REF. You have to build one of these every single
time you address a new problem. This is the essence of Alessio's
critique. If you want to add/find a FOO you need to write
ADD-FOO/FIND-FOO. If you want to ADD/FIND a BAZ you need to write
ADD-BAZ/FIND-BAZ. If you want to ADD/FIND/SUBTRACT/MULTIPLY a fixnum
you need to... [completing this sentence is left as an exercise]

2) Abstractions built this way are leaky. ADD-FUNCTION implemented your
way doesn't return an abstract opaque structure, it returns a cons cell.
So nothing prevents you from, say, taking the CDR of the result of
ADD-FUNCTION. If you do that you lose all the benefit of the
abstraction. So reaping the benefit of this kind of abstraction is
entirely dependent on programmer discipline.

REF, by way of contrast, can be implemented opaquely so that the
abstraction barrier cannot be accidentally breached. And it confers the
benefits of abstraction without requiring the programmer to write
endless boilerplate. And it doesn't prevent you from writing SICP-style
application-specific abstractions on top of REF if that's what floats
your canoe. And the interface is dead-simple: (REF COLLECTION INDEX).
REF seems to me to strictly dominate SICP-style abstraction in all
respects.

But of course all of this is tangential to the main point, which is that
Alessio's critique still seems spot-on to me.

rg
From: RG on
In article <8btsioFb3bU1(a)mid.individual.net>,
Pascal Costanza <pc(a)p-cos.net> wrote:

> On 04/08/2010 19:20, RG wrote:
> > In article<9mpzkx2ibyb.fsf(a)runa.se>, bjorn(a)runa.se (Björn Lindberg)
> > wrote:
> >
> >> RG<rNOSPAMon(a)flownet.com> writes:
> >>
> >>> In article<9mplj8mkewt.fsf(a)runa.se>, bjorn(a)runa.se (Björn Lindberg)
> >>> wrote:
> >>>
> >>>> RG<rNOSPAMon(a)flownet.com> writes:
> >>>>
> >>>>> In article<9mpwrs7kipq.fsf(a)runa.se>, bjorn(a)runa.se (Björn Lindberg)
> >>>>> wrote:
> >>>>>
> >>>>>>> It all boils down to some common sense. Just like we use + rather than
> >>>>>>> fixnum+, bignum+, float+, double+, it's often convenient to use ELT
> >>>>>>> rather than AREF or NTH.
> >>>>>>
> >>>>>> No, these are two completely different topics. We do not use generic
> >>>>>> arithmetic operators to delay an implementation choice of which number
> >>>>>> type to use.
> >>>>>
> >>>>> Huh?!? This seems to me to be the *only* reason to use generic
> >>>>> arithmetic. What else is it good for?
> >>>>
> >>>> To write code that can accept differing types of numbers at run-time.
> >>>
> >>> And how is that not "delaying an implementation choice" (to run-time) on
> >>> which number type to use?
> >>
> >> Let us be careful not to have this discussion stray from the topic, and
> >> consider the context in which that comment was made. If you think that
> >> Alessio's 'typed arithmetics' are similar in nature to what I was
> >> outlining in the post he replied to, can you please explain that
> >> further?
> >
> > The original context was snipped out. I presume you're referring to
> > this:
> >
> >> If I may interject, I do see value in abstraction, but it will usually
> >> take a different form than what you propose.
> >>
> >> Let's say that we in our program have objects of the kind foo, indexed
> >> by bar, stored in collections bork. You do not want to prematurely
> >> select the implementation of bork to be either hash tables, alists or
> >> something else. You now write accessors like ADD-FOO, FIND-FOO, and the
> >> like, essentially creating an ADT specific to the program. This ADT
> >> encapsulates whatever implementation is chosen for bork, making change
> >> later easy. The advantages of this approach are plenty: The accessors
> >> can be written to fit the semantics of foo, bar and bork, rather than
> >> shoe-horning the behaviour of those into some generic collections
> >> framework. It makes the code much more descriptive of what it is doing
> >> than if it is sprinkled with "generic" REF calls, which from a
> >> documentation perspective is even worse than having it filled with calls
> >> to GETHASH.
> >
> > Alessio's typed arithmetic operators seem to me to be exactly analogous
> > to your typed collections operators. ADD-FOO (presumably) only adds
> > objects of type FOO. In fact, just change FOO to FIXNUM and you've got
> > Alessio's example nearly verbatim.
>
> I think Björn actually meant something like register-observer and
> deregister-observer. [1] Something like this:
>
> (defclass observable-mixin ()
> ((observers :initform '())))
>
> ;; specializing on standard-class here is not standard-compliant
> ;; but good enough for illustration purposes
> (defmethod (setf slot-value-using-class) :after
> ((class standard-class) (object observable-mixin) slot)
> ;; the following should pass more information
> ;; but you get the idea
> (mapc #'notify (slot-value object 'observers)))
>
> (defmethod register-observer ((object observable-mixin) observer)
> (pushnew observer (slot-value object 'observers)))
>
> (defmethod deregister-observer ((object observable-mixin) observer)
> (setf (slot-value object 'observers)
> (remove observer (slot-value object 'observers))))
>
> Replacing the list with a vector here is downright trivial, because it's
> nicely abstracted away behind the register/deregister protocol.

No, replacing a list with a vector is trivial because you only have ten
lines of code. In fact, to replace lists with vectors you would need to
change four of your ten lines of code. If your code base were 10,000
LOC instead of 10, changing 40% of it to change an implementation would
feel like more of a chore.

If you had used a real abstraction you could have replaced lists with
vectors by changing zero lines of code. In a large code base that can
be a big win.

> Adding
> an abstract collection API here doesn't buy you a lot in this and
> similar cases.

Very few software engineering techniques pay big dividends on small code.

rg