Suppose I'm writing a piece of banking software. I've defined my class
ACCOUNT (i.e. a bank account) and now I'm implementing the WITHDRAW
method. One way to write it is like this:
(defmethod withdraw ((account account) amount)
(if (>= (balance account) amount)
(decf (balance account) amount)
(error "Insufficient funds.")))
I.e. specialize on the parameter that I'm using to dispatch on. That
is, I'm planning to write other methods on other classes of account.
But I don't bother specializing the second parameter because I don't
intend to dispatch on it; it's always going to be a number.
On the other hand it had better in fact *be* a number given that I'm
passing it to >= and DECF. So maybe I should write this:
(defmethod withdraw ((account account) (amount number))
(if (>= (balance account) amount)
(decf (balance account) amount)
(error "Insufficient funds.")))
Anyone have any stylistic preference for one or the other? (I guess
the practical difference is just trading a type-error in for a
no-applicable-method error if withdraw is ever called with a
non-numeric second argument.)
-Peter
--
Peter Seibel ·····@javamonkey.com
Lisp is the red pill. -- John Fraser, comp.lang.lisp
Peter Seibel wrote:
>
> On the other hand it had better in fact *be* a number given that I'm
> passing it to >= and DECF. So maybe I should write this:
>
> (defmethod withdraw ((account account) (amount number))
> (if (>= (balance account) amount)
> (decf (balance account) amount)
> (error "Insufficient funds.")))
>
I would not even specialize on account. withdraw sounds pretty
accountish. Well, actually there would not be a withdraw method, there
would be:
(defmethod tx-commit ((tx withdrawal) account...
No, hang on, the transaction knows the account as well as the amount, so:
(defmethod tx-commit ((tx withdrawal))
(if (or (>= (balance (account tx)) (amount tx))
(overdraft-cool-p (account tx)))
(decf ...)
(if (kid-glove-p (customer (account tx)))
(tx-kinder-gentler-enqueue tx)
(error "INF"))))
ie, let special handling arise from other GFs reached in the branches of
the implementation of any given GF.
Sorry for the digression. This is just more fun than doing scroller
widgets (for the third time in my life).
kt
--
Home? http://tilton-technology.com
Cells? http://www.common-lisp.net/project/cells/
Cello? http://www.common-lisp.net/project/cello/
Why Lisp? http://alu.cliki.net/RtL%20Highlight%20Film
Your Project Here! http://alu.cliki.net/Industry%20Application
Peter Seibel <·····@javamonkey.com> writes:
> On the other hand it had better in fact *be* a number given that I'm
> passing it to >= and DECF. So maybe I should write this:
>
> (defmethod withdraw ((account account) (amount number))
> (if (>= (balance account) amount)
> (decf (balance account) amount)
> (error "Insufficient funds.")))
>
> Anyone have any stylistic preference for one or the other? (I guess
> the practical difference is just trading a type-error in for a
> no-applicable-method error if withdraw is ever called with a
> non-numeric second argument.)
What happens if I try to withdraw #(0.0 100.0) USD ?
What happens if I try to withdraw 100/3 EUR ?
What happens if I try to withdraw PI UKL ?
You don't withdraw numbers, you withdraw money!
--
__Pascal_Bourguignon__ http://www.informatimago.com/
There is no worse tyranny than to force a man to pay for what he doesn't
want merely because you think it would be good for him.--Robert Heinlein
http://www.theadvocates.org/
Pascal Bourguignon <····@thalassa.informatimago.com> writes:
> Peter Seibel <·····@javamonkey.com> writes:
> > On the other hand it had better in fact *be* a number given that I'm
> > passing it to >= and DECF. So maybe I should write this:
> >
> > (defmethod withdraw ((account account) (amount number))
> > (if (>= (balance account) amount)
> > (decf (balance account) amount)
> > (error "Insufficient funds.")))
> >
> > Anyone have any stylistic preference for one or the other? (I guess
> > the practical difference is just trading a type-error in for a
> > no-applicable-method error if withdraw is ever called with a
> > non-numeric second argument.)
>
> What happens if I try to withdraw #(0.0 100.0) USD ?
I mean:
What happens if I try to withdraw #C(0.0 100.0) USD ?
> What happens if I try to withdraw 100/3 EUR ?
> What happens if I try to withdraw PI UKL ?
>
> You don't withdraw numbers, you withdraw money!
--
__Pascal_Bourguignon__ http://www.informatimago.com/
There is no worse tyranny than to force a man to pay for what he doesn't
want merely because you think it would be good for him.--Robert Heinlein
http://www.theadvocates.org/
Pascal Bourguignon <····@thalassa.informatimago.com> writes:
> Peter Seibel <·····@javamonkey.com> writes:
>> On the other hand it had better in fact *be* a number given that I'm
>> passing it to >= and DECF. So maybe I should write this:
>>
>> (defmethod withdraw ((account account) (amount number))
>> (if (>= (balance account) amount)
>> (decf (balance account) amount)
>> (error "Insufficient funds.")))
>>
>> Anyone have any stylistic preference for one or the other? (I guess
>> the practical difference is just trading a type-error in for a
>> no-applicable-method error if withdraw is ever called with a
>> non-numeric second argument.)
>
> What happens if I try to withdraw #(0.0 100.0) USD ?
> What happens if I try to withdraw 100/3 EUR ?
> What happens if I try to withdraw PI UKL ?
>
> You don't withdraw numbers, you withdraw money!
Yeah, yeah. You are, of course, correct. However I'm not *really*
writing a banking app. But I could reframe my question assuming an
appropriate MONEY class with associated functions as:
(defmethod withdraw ((account account) amount)
(if (money>= (balance account) amount)
(cash-decrement (balance account) amount)
(error "Insufficient funds.")))
vs
(defmethod withdraw ((account account) (amount money))
(if (money>= (balance account) amount)
(cash-decrement (balance account) amount)
(error "Insufficient funds.")))
-Peter
--
Peter Seibel ·····@javamonkey.com
Lisp is the red pill. -- John Fraser, comp.lang.lisp
Peter Seibel <·····@javamonkey.com> writes:
> Yeah, yeah. You are, of course, correct. However I'm not *really*
> writing a banking app. But I could reframe my question assuming an
> appropriate MONEY class with associated functions as:
>
> (defmethod withdraw ((account account) amount)
> (if (money>= (balance account) amount)
> (cash-decrement (balance account) amount)
> (error "Insufficient funds.")))
>
> vs
>
> (defmethod withdraw ((account account) (amount money))
> (if (money>= (balance account) amount)
> (cash-decrement (balance account) amount)
> (error "Insufficient funds.")))
I'd write it the first way, then later I'd probably switch to:
(defmethod withdraw ((account account) (amount us-dollars)) ...)
(defmethod withdraw (account (amount money))
(withdraw account (convert-to-dollars amount)))
when I realized I better support for AMOUNT.
[ Trolls: of course this is not how a banking app would work, try to
concentrate on the point of the question. ]
--
/|_ .-----------------------.
,' .\ / | No to Imperialist war |
,--' _,' | Wage class war! |
/ / `-----------------------'
( -. |
| ) |
(`-. '--.)
`. )----'
Thomas F. Burdick wrote:
> Peter Seibel <·····@javamonkey.com> writes:
>
>
>>Yeah, yeah. You are, of course, correct. However I'm not *really*
>>writing a banking app. But I could reframe my question assuming an
>>appropriate MONEY class with associated functions as:
>>
>> (defmethod withdraw ((account account) amount)
>> (if (money>= (balance account) amount)
>> (cash-decrement (balance account) amount)
>> (error "Insufficient funds.")))
>>
>>vs
>>
>> (defmethod withdraw ((account account) (amount money))
>> (if (money>= (balance account) amount)
>> (cash-decrement (balance account) amount)
>> (error "Insufficient funds.")))
>
>
> I'd write it the first way, then later I'd probably switch to:
>
> (defmethod withdraw ((account account) (amount us-dollars)) ...)
>
> (defmethod withdraw (account (amount money))
> (withdraw account (convert-to-dollars amount)))
>
> when I realized I better support for AMOUNT.
>
> [ Trolls: of course this is not how a banking app would work, try to
> concentrate on the point of the question. ]
Troll here. Well, I /did/ say I would not use narrowed specializers to
validate input. But I always wanted to be a troll, so...
I think technical questions (especially when a language has a kazillion
ways to do everything, and especially where only a "preferred approach"
out of competing workables is sought) only make sense in real
situations. So it is fair to quibble with the example provided.
kt
Kenny Tilton <·······@nyc.rr.com> writes:
> Thomas F. Burdick wrote:
>
> > [ Trolls: of course this is not how a banking app would work, try to
> > concentrate on the point of the question. ]
>
> Troll here. Well, I /did/ say I would not use narrowed specializers to
> validate input. But I always wanted to be a troll, so...
Sorry, to get troll points, you'd have to explain why my pseudo-code
would result in a bank erasing or creating money. Something about how
the banking industry works.
> I think technical questions (especially when a language has a kazillion
> ways to do everything, and especially where only a "preferred approach"
> out of competing workables is sought) only make sense in real
> situations. So it is fair to quibble with the example provided.
I do agree with you here, although succinct real world examples are
tough. This afternoon, Peter said, "pretend it's a bank in a MUD."
Video-game banking ... that works for me :-)
However you split the methods up, once you go back and refactor, I
always try to make it so that the type errors are recoverable very
close to where the error occured.
--
/|_ .-----------------------.
,' .\ / | No to Imperialist war |
,--' _,' | Wage class war! |
/ / `-----------------------'
( -. |
| ) |
(`-. '--.)
`. )----'
Peter Seibel <·····@javamonkey.com> writes:
> > You don't withdraw numbers, you withdraw money!
>
> Yeah, yeah. You are, of course, correct. However I'm not *really*
> writing a banking app. But I could reframe my question assuming an
> appropriate MONEY class with associated functions as:
>
> (defmethod withdraw ((account account) amount)
> (if (money>= (balance account) amount)
> (cash-decrement (balance account) amount)
> (error "Insufficient funds.")))
>
> vs
>
> (defmethod withdraw ((account account) (amount money))
> (if (money>= (balance account) amount)
> (cash-decrement (balance account) amount)
> (error "Insufficient funds.")))
But now we've progressed.
There may be several kind of "money". Not only devises that are all of
the same kind of fiat money. From some account you can withdraw gold,
or shares.
You could have:
(defclass value ())
(defclass gold (value)
((mass :type integer :documentation "unit mg")))
(defclass money (value)
((facial-value :type integer)
(devise :type devise)))
Then you would definitely want to do:
(defmethod withdraw ((account account) (amount money)) ...)
(defmethod withdraw ((account account) (amount gold)) ...)
(But actually, as it has been noted, since there are several kind of
withdrawals (atm, cashier, etc), you want to reify it).
--
__Pascal_Bourguignon__ http://www.informatimago.com/
There is no worse tyranny than to force a man to pay for what he doesn't
want merely because you think it would be good for him.--Robert Heinlein
http://www.theadvocates.org/
Pascal Bourguignon <····@thalassa.informatimago.com> writes:
> Then you would definitely want to do:
>
> (defmethod withdraw ((account account) (amount money)) ...)
> (defmethod withdraw ((account account) (amount gold)) ...)
[You do get, don't you, that I don't actually care about how to write
a banking application? Just checking. Anyway, playing along:]
Would you definitely? It seems more likely that in order to prevent a
combinatorial explosion I'd want to define abstract operations on
MONEY that can be used to implement the semantics of WITHDRAW without
regard to what kind of money it is. That is, do the semantics of
WITHDRAW really vary depending on the type of currency involved in
ways that can't be captured by methods specialized only on the type of
currency, e.g. something like Thomas suggested:
(defmethod withdraw ((account account) amount)
(let ((converted-amount (convert (currency-type account) amount)))
(if (money>= (balance account) converted-amount)
(decf (balance account) converted-amount)
(error "Insufficient funds."))))
where CONVERT is then specialized on the various kinds of things with
monetary value (Okay, so I cheat and also use GF dispatching to
specify the type of currency. But it's not a direct function of the
account type):
(defmethod convert ((currency (eql 'us-dollars) (amount gold))) ...)
(defmethod convert ((currency (eql 'us-dollars) (amount pounds-sterling))) ...)
(defmethod convert ((currency (eql 'us-dollars) (amount pounds-of-flesh))) ...)
-Peter
--
Peter Seibel ·····@javamonkey.com
Lisp is the red pill. -- John Fraser, comp.lang.lisp
Peter Seibel <·····@javamonkey.com> writes:
> Pascal Bourguignon <····@thalassa.informatimago.com> writes:
>
> > Then you would definitely want to do:
> >
> > (defmethod withdraw ((account account) (amount money)) ...)
> > (defmethod withdraw ((account account) (amount gold)) ...)
>
> [You do get, don't you, that I don't actually care about how to write
> a banking application? Just checking. Anyway, playing along:]
>
> Would you definitely? It seems more likely that in order to prevent a
> combinatorial explosion I'd want to define abstract operations on
> MONEY that can be used to implement the semantics of WITHDRAW without
> regard to what kind of money it is.
Yes. But since those abstract operations would not be defined on cons,
on numbers or on T in all generality, you should better define the
method on your abstract monetary-value class:
(defmethod withdraw ((acount account) (amount monetary-value)) ...)
Alternatively, you may perhaps have a valuation function that would
give a monetary value for a cons or an array, then you could have:
(defmethod withdraw ((acount account) (amount t))
(let ((mv-amount (convert-to-monetary-value amount)))
...))
> That is, do the semantics of
> WITHDRAW really vary depending on the type of currency involved in
> ways that can't be captured by methods specialized only on the type of
> currency, e.g. something like Thomas suggested:
In the case of accounts and withdrawals, yes. The actual algorithm to
use for an withdrawal depends on the legislation of the account, the
type of withdrawal, the type of monetary value, etc. There are
different taxes, fees, and limits.
So, either you have an abstract argument and you can define the
algorithm generally, (but beware you don't end writing something like:
(defmethod withdraw ((acount account) (amount monetary-value))
(case (abstract-kind amount)
(kind-1 (do-something-1))
(kind-2 (do-something-2))
;; ...
(kind-n (do-something-n))))
), or you'd better define the method specifically for each kind of
amount or account.
--
__Pascal_Bourguignon__ http://www.informatimago.com/
There is no worse tyranny than to force a man to pay for what he doesn't
want merely because you think it would be good for him.--Robert Heinlein
http://www.theadvocates.org/
On 2004-03-26, Peter Seibel <·····@javamonkey.com> wrote:
|
| Anyone have any stylistic preference for one or the other? (I guess
| the practical difference is just trading a type-error in for a
| no-applicable-method error if withdraw is ever called with a
| non-numeric second argument.)
A third possibility would be to use CHECK-TYPE:
(defmethod withdraw ((account account) amount)
(check-type amount number)
(if (>= (balance account) amount)
(decf (balance amount) amount)
(error "Insufficient funds.")))
Now if you call WITHDRAW with a non-numeric AMOUNT, the debugger will
give you an opportunity to change it (via the STORE-VALUE restart).
--
Cliff Crawford *** ·····@cornell.edu
"The perfection of art is to conceal art." -- Quintilian
Peter Seibel wrote:
> Suppose I'm writing a piece of banking software. I've defined my class
> ACCOUNT (i.e. a bank account) and now I'm implementing the WITHDRAW
> method. One way to write it is like this:
>
> (defmethod withdraw ((account account) amount)
> (if (>= (balance account) amount)
> (decf (balance account) amount)
> (error "Insufficient funds.")))
>
> I.e. specialize on the parameter that I'm using to dispatch on. That
> is, I'm planning to write other methods on other classes of account.
> But I don't bother specializing the second parameter because I don't
> intend to dispatch on it; it's always going to be a number.
There ya go: "I don't intend to dispatch on it". I forgot Tilton's Law:
Don't use a screwdriver to drive a nail.
Parameter specialization exists for method dispatch. ie, for dividing up
huge, unmaintainable wadges of code into manageable chunks by object
type. So don't use it for software QA. Especially if the context is
educational; an example of multiple dispatch should be found which
honestly motivates the feature.
Consider what happens if you go ahead and validate using dispatch. The
error the developer sees is "no withdraw method for args (<account>
'banana). Great. The code now has a specialization on money which
confuses others into thinking the method is dispatched on that, and the
programmer sees only "no method for 'banana". A few instructions later
they would have gotten 'banana is not a number as their error, which is
just as good, so really nothing has been accomplished with the
misleading specialization.
The only place to put a better error than "banana is not a number" is in
the calling routine, where one can offer:
(assert (numberp wd-amt)()
"this ~a and that ~a produced nun-number ~a"
this that wd-amt))
It ain't banking, but one framework I did dispatched GUI rendering on
the output stream as well as the widget, so special requirements of
hardcopy rendering could be handled neatly. Something like that.
Maybe dispatch on account and transaction type.
kt
Kenny Tilton <·······@nyc.rr.com> writes:
> Peter Seibel wrote:
>> Suppose I'm writing a piece of banking software. I've defined my class
>> ACCOUNT (i.e. a bank account) and now I'm implementing the WITHDRAW
>> method. One way to write it is like this:
>> (defmethod withdraw ((account account) amount)
>> (if (>= (balance account) amount)
>> (decf (balance account) amount)
>> (error "Insufficient funds.")))
>> I.e. specialize on the parameter that I'm using to dispatch on. That
>> is, I'm planning to write other methods on other classes of account.
>> But I don't bother specializing the second parameter because I don't
>> intend to dispatch on it; it's always going to be a number.
>
> There ya go: "I don't intend to dispatch on it". I forgot Tilton's
> Law:
>
> Don't use a screwdriver to drive a nail.
>
> Parameter specialization exists for method dispatch. ie, for
> dividing up huge, unmaintainable wadges of code into manageable
> chunks by object type. So don't use it for software QA. Especially
> if the context is educational; an example of multiple dispatch
> should be found which honestly motivates the feature.
Yeah. I think that's where I was heading--method parameter
specialization should be considered purely a tool for dispatch.
Otherwise it's just like a DEFUN where the types are implicit (or
possibly checked with CHECK-TYPE) and it's up to the caller to know
how to use the function.
However I don't see the argument for the exttra specialization as
being about QA, since--as you point out--it only slightly changes the
timing and type of error that will be signaled. Rather it seems to me
to be about about expressing your intent: since the amount argument
actually *does* have to be a particular type, and we have this
opportunity to express that rather than leaving it implicit, there
might be some value in doing so. The programmer writing code to call
WITHDRAW can tell, either by (mop:generic-function-methods #'withdraw)
or 'grep (defmethod withdraw' or by a fancy IDE that in fact WITHDRAW
should be called with an ACCOUNT and a REAL without having to read the
docstring or look at the code of the method. Like I said, I'm not sure
I buy this argument but I don't think it's obviously crazy.
-Peter
--
Peter Seibel ·····@javamonkey.com
Lisp is the red pill. -- John Fraser, comp.lang.lisp
Peter Seibel <·····@javamonkey.com> writes:
> Suppose I'm writing a piece of banking software. I've defined my class
> ACCOUNT (i.e. a bank account) and now I'm implementing the WITHDRAW
> method. One way to write it is like this:
>
> (defmethod withdraw ((account account) amount)
> (if (>= (balance account) amount)
> (decf (balance account) amount)
> (error "Insufficient funds.")))
>
> I.e. specialize on the parameter that I'm using to dispatch on. That
> is, I'm planning to write other methods on other classes of account.
> But I don't bother specializing the second parameter because I don't
> intend to dispatch on it; it's always going to be a number.
>
> On the other hand it had better in fact *be* a number given that I'm
> passing it to >= and DECF. So maybe I should write this:
>
> (defmethod withdraw ((account account) (amount number))
> (if (>= (balance account) amount)
> (decf (balance account) amount)
> (error "Insufficient funds.")))
>
> Anyone have any stylistic preference for one or the other? (I guess
> the practical difference is just trading a type-error in for a
> no-applicable-method error if withdraw is ever called with a
> non-numeric second argument.)
Since >= and DECF by default ensure that their argument is a number,
you're doing the type-check twice in the second version.
And supposing that you have extended >= and DECF (and other math
operators) to handle strings in some weird manner. It would now make
sense to have a bank account based on strings, but the second version
would not permit it.
My *intuition* is that the first is better.
Joe Marshall <···@ccs.neu.edu> writes:
> Peter Seibel <·····@javamonkey.com> writes:
>
> > [...] One way to write it is like this: [...] I.e. specialize on
> > the parameter that I'm using to dispatch on. [...] On the other
> > hand it had better in fact *be* a number given that I'm passing it
> > to >= and DECF. So maybe I should write this: [dispatch on both
> > parameters].
> >
> > Anyone have any stylistic preference for one or the other? (I
> > guess the practical difference is just trading a type-error in for
> > a no-applicable-method error if withdraw is ever called with a
> > non-numeric second argument.)
One can also create a custom specialization for unknown type errors.
For example,
(defmethod withdraw ((account account) (amount t))
(error "~A is not a valid amount." amount))
This is more specific than either no-applicable-method or just raw
type-error, though arguably not much better than a raw type error. Of
course, if one started to use CERROR with different continuations for
the various not-quite-right types, or something along those lines,
perhaps this would start to be more useful than just raw type errors.
Of course, if one were not planning on doing all that work, it would
probably better to simply leave well enough alone.
> Since >= and DECF by default ensure that their argument is a number,
> you're doing the type-check twice in the second version.
Well, expect that there are valid numbers that are not valid monetary
units; e.g. US monetary units only allow one to go to 0.01 because the
cent is the smallest unit of US money while a Japaneese monetary unit
does not have naught but the Yen, IIRC. I believe that accounting
rules handle things such as whether or not one would round or truncate
a value, etc, though I'm not an accountant and so I don't really know.
As CL's rationals are not susceptible to the problems of floating
point precision/approximation, perhaps one could avoid the issue all
together; however, if there are standard accounting practices that
specify rules that differ from exact rational arithmatic, one might
want to use a monetary type inside of just a NUMBER.