From: Pascal Costanza on 4 Aug 2010 15:15 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 4 Aug 2010 16:38 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 4 Aug 2010 16:50 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 4 Aug 2010 18:47 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 4 Aug 2010 18:55
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 |