Re: Keyword Arguments
Kevin Edwards <[email protected]> Sat, 16 Jun 2012 16:33:35 -0500
| Newsgroups | gmane.comp.lang.io |
|---|---|
| Message-ID | <[email protected]> |
On 6/15/2012 5:59 PM, Steve Dekorte wrote: > On 2012-06-15 Fri, at 02:02 AM, Kevin Edwards wrote: >> As a simple and contrived example, consider a Rectangle object. >> Do we package that as 2 Points (x1, y1) and (x2, y2)? Or as 2 >> Ranges (x1, x2) and (y1, y2)? Or flat (x1, x2, y1, y2)? In OO, >> if we implement the logic for one format (like intersect, union, >> expand, contract, etc.), then we either have to convert to/from >> the other formats (more data moves), or we'll have to (manually) >> duplicate the core logic for each format we use (which can impact >> development time and locality due to size). > > Exactly, we can ask the other object to describe itself in a way > we understand. While FP would require large switch statements > scattered around all the functions of the system to handle every > type - all of which would break upon the introduction of a new type. I think FP would use some sort of dispatch mechanism like pattern matching rather than static switching, but perhaps more interesting is that in FP, functions are viewed as always applying to only a single argument, which is very similar to your ideal view. In fact, your ideal view looks like keyword args when compared to FP's positional currying. Whereas you would avoid positional binding, FP avoids name (keyword) binding. I think both are useful forms of expression and they are technically equivalent. > Example: > > aPerson setAge(thePersonsAge) > aPerson print > aPerson canRetire > > vs. > > aPerson print(thePersonsAge) > aPerson canRetire(thePersonsAge) > > > etc > > It's clear that the age is being stored in the wrong place and > that passing it in constantly is a bad design choice. > To me this suggests that anything that encourages developers to > pass more data also encourages them to make bad design decisions. I agree with you entirely here, I'm just saying that keyword argument syntax could do the exact same thing as your first example. e.g.: aPerson = Person(age = thePersonsAge) aPerson print aPerson canRetire Note that the same age setter would be used as in your example. You wouldn't need to implement a monolithic constructor to handle that -- it would be modular, as in your example. To be clear, I'm not advocating the traditional implementation of keyword args. Instead, I'm suggesting that it would roughly correspond to `Person clone do( age = thePersonsAge ) eval` because that is basically what an eager method call does -- it clones the definition, binds the arguments, and returns the result. The syntax is just a natural expression of the relationships involved. >> You also mentioned validation elsewhere. e.g. setA can perform >> validation. `a=1` could perform validation, too, couldn't it? >> More generally, I like the idea of custom setters and setSlot >> using the same syntax and control path (the same as any dict for >> that matter). > > Well, you can get this by using ::= to create the slot and then > calling the setter method (which is considered good coding > practice) instead of using = . Yeah, the user should not need to be aware of whether a setter is overridden, and you achieve that by always creating and using setX even if it just calls the raw setter for x. So, you pay the penalty of a custom setter right off the bat just in case you want to override it in the future. But if you move to using Slots, then you can transparently intercept any behavior of a Slot (get, set, update, delete, etc.) using a single control path, and setX would no longer exist in the same namespace as x (similarly for updateX, deleteX, etc. if you ever need to override those behaviors). > Also, you might look at how self unified assignment with methods. > Maybe there is something there. Could you elaborate? I thought Self did roughly the same thing as Io, but calls it `x:` rather than `setX`. If `x(1)` becomes Io's setter syntax, then you'll probably end up using extra messages to accomplish traditional application. For example: F clone x(1) y(2) eval Here we are using "clone" to initiate the application and using "eval" to indicate the end of it and force a simplification. That whole chain is what `F(x=1, y=2)" traditionally means. And, as I mentioned previously, we could add a little syntax to prevent the eager eval if we want. >> i think part of the answer to performance is a more flexible >> dispatcher which can take the `at` and `put` together, so that an >> explicit Slot object doesn't actually have to be created if it >> doesn't already exist. But this may bear some similarity to >> multiple dispatch, so I'm interested in learning your thoughts >> about that anti-pattern. :-) > > Multiple dispatch makes the deep assumption that a notion of > external type exists. But what notion of type can deal with > something like a proxy which is, in effect, all possible types? Proxies have a distinct type that is defined at a meta-level relative to the proxied type. For example, a perfectly transparent Proxy will respond to all messages identically to how the proxied type would respond, and an external ProxyViewer object would be used to look inside the Proxy and control the Proxy's underlying behavior. We use proxies specifically for this special meta-behavior which defines the Proxy type distinctly from other types. If you don't go to the meta-level to define Proxy, and instead try to merge that meta-behavior into the base behavior, you end up with an imperfect Proxy. > Or from another angle: any system that depends on a notion of type > that is anything more than asking an object what it choses to do > at the moment violates encapsulation in ways that have serious > consequences for what the system can and can't do. That is a fascinating and profound angle. :-) You're right that the notion of type imposes constraints on what an object can and can't do, but that can actually be very useful because it explicitly tells us the bounds within which the object can change while maintaining a coherent system. Granted, a lot of type systems unnecessarily over-constrain objects or prohibit changing constraints at run-time, but that does not mean it has to be that way. For example, we can instrument an `if(<cond>, <then>)` call and change its implementation in all sorts of flexible ways, but fundamentally it must always evaluate <then> based upon the condition. If we break that constraint, then we break the larger system relative to the intent of the programmer. That constraint defines the type of `if`. So, exposing the essential constraints on objects and messages actually frees the system to do a whole lot more, like safely reason about code in order to perform intent-preserving substitutions, cut out dead branches, or dispatch to specialized solutions from multiple messages, as in your `aDict at(key) put(value)` example. Phew! Sorry for the long e-mail, but I'm hoping it is constructive and worth reading. :-) Kevin