From: Pascal Costanza on
On 05/08/2010 00:55, RG wrote:
> 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.

In my 10 lines of code I have four definitions referring to a single
list container. So are you saying that in 10000 lines of code, I would
have 4000 definitions referring to that single list container? I'm
wondering what kind of code that would be... ;)


Pascal

--
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: RG on
In article <8budnhFdg0U1(a)mid.individual.net>,
Pascal Costanza <pc(a)p-cos.net> wrote:

> On 05/08/2010 00:55, RG wrote:
> > 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.
>
> In my 10 lines of code I have four definitions referring to a single
> list container. So are you saying that in 10000 lines of code, I would
> have 4000 definitions referring to that single list container? I'm
> wondering what kind of code that would be... ;)

Why do they all have to refer to that one list container? Why could you
not have 1000 different kinds of containers, each of which requires the
same 10-line boilerplate, and all of which might have to be changed
under the right circumstances? I'm working with a system right now that
pretty much fits that description. (It's a financial system.)

rg
From: Pascal Costanza on
On 05/08/2010 02:36, RG wrote:
> In article<8budnhFdg0U1(a)mid.individual.net>,
> Pascal Costanza<pc(a)p-cos.net> wrote:
>
>> On 05/08/2010 00:55, RG wrote:
>>> 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.
>>
>> In my 10 lines of code I have four definitions referring to a single
>> list container. So are you saying that in 10000 lines of code, I would
>> have 4000 definitions referring to that single list container? I'm
>> wondering what kind of code that would be... ;)
>
> Why do they all have to refer to that one list container? Why could you
> not have 1000 different kinds of containers, each of which requires the
> same 10-line boilerplate, and all of which might have to be changed
> under the right circumstances? I'm working with a system right now that
> pretty much fits that description. (It's a financial system.)

Why would the all require the same change at the same time?

If they all require the same boilerplate, why didn't you write a macro
to generate it? Then you would have greatly reduced the workload again...


Pascal

--
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 <m2y6cmglk8.fsf(a)nex.nex>, bjorn(a)runa.se (Bj�rn Lindberg)
> wrote:

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

Yes, but each such abstraction will be different, because it will be
tailored to fit the semantics of the program, rather than abstracted in
the direction of generic collections.

There are two reasons why Alessio's critique is not applicable. The
first one is that whereas numbers as a consequence of mathematical
definitions support the same set of operations and behave the same under
these operations, collections do not. A generic collections framework
will by necessity have a lowest common denominator API, hiding relevant
details. You have to give something up to use it, which you do not give
up in the case of the numbers. Just as an example, for lists a FOREACH
operator quite naturally will process the elements in order, whereas for
a tree you would have to choose which traversal order should be the
canonical one. Other orders would be unavailable without breaking the
abstraction.

The second reason why Alessio's argument misses its mark is that almost
always functions using the arithmetic operators will _at_run-time_
accept different number types. This is the common case. In contrast,
although a generic collections framework can be used for this purpose
too, the topic of discussion here is how to be able to change an
underlying implementation in a later version of the code without
excessive refactoring. At run-time in our example, the collection type
will be the same. This does not make Alessio's critique invalid as a
counter argument in some other discussion, but makes it not applicable
in this case.

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

Programming is very dependent on discipline, indeed. This case is hardly
unique. And transparent structures have the advantage of simplifying
debugging. That aside, this is not a problem with the approach
itself. Nothing prevents you from having add-function returning nil, the
name of the function added or something else entirely if you do not
trust yourself or your colleagues. And even then, the generic approach
suffers the same dilemma. SUBSEQ for instance, being generic over lists
and vectors, will for some invocations return a list, which one of those
undisciplined programmers surely would take the CDR of.


Bj�rn Lindberg
From: =?iso-8859-1?Q?Bj=F6rn?= Lindberg on
RG <rNOSPAMon(a)flownet.com> writes:

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

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

No, Pascal just showed how he encapsulated the choice of using lists
such that even when the code base grows to 10,000 lines, changing it to
use vectors still only involves a few lines. Your 40% would come from
littering the code with pushnews and removes. The difference in approach
is creating an application specific abstraction -- in this case
register/deregister -- versus littering the code with generic REF
accesses. (Although they are not mutually exclusive, of course.)


Bj�rn Lindberg