interface CanQuack
{
void quack();
}
interface CanWalk
{
void walk();
}
<T extends CanQuack & CanWalk> void doDucklikeThings(T t)
{
t.quack();
t.walk();
}
You can pass anything to this method that implements CanWalk and CanQuack.
Of course, this is optimally flexible when you have one method per interface, but that's not a problem.
I can easily see the dependencies of doDucklikeThings, whereas if I had a larger Duck interface, e.g.:
interface Duck
{
void quack();
void walk();
void eatAFish();
}
I wouldn't be able to tell by looking at the signature of a method that used Duck, whether it would call eatAFish.
This works well for reducing coupling, and would probably be better than
this Proxy-based attempt.
There is no runtime cost, thanks to type erasure. It's rare that someone thanks Sun for type erasure, it seems, but I have got to grips with generics fairly well, thanks to Angelika Langer's
excellent generics FAQ, and I find it very useful.
I have applied this technique to a lot of the 20kloc program that I work on,
IPSim (a network simulator), but I am still in this process.
I hope you find this useful, or give me some damn good reasons why it isn't!
13 comments:
Nice that some ideas I have are not so weird that nobody else can relate to them.
See last part of my post Use minimal Interfaces. It would be nice to compose types for return values, parameters and anonymous classes, too. This would make it easier to create something that walks and quacks:
return new CanWalk, CanQuack() {...};
Anyway, a very creative use of generics!
The technique here seems handy, but it's not duck typing. Duck typing is about just happening to use the right names - not explicitly declaring interfaces. Or in other words, duck typing requires reflection (such as in the proxy example linked to).
But still, this technique here seems interesting, especially with the info that you've used it in practice.
I looked at the definition of duck typing, by the author of Ruby, and I don't think it actually needs to be dynamic.
What I did was duck typing, but the capabilities are statically checked. Nothing in that definition says that they capabilities must be only dynamically checked.
I use interfaces to express capabilities, which seems to be ideal.
To be honest, even if you can pull out another definition that proves my generic use not to be duck typing, it doesn't matter, as you can do roughly the same things with it that you can with 'real' duck typing.
What I did was duck typing...
Sorry, that isn't what folk mean when they say duck typing.
Here's a class with the capability to doDucklikeThings
class A {
void quack(){ ... }
void walk(){ ... }
}
Instances of class A will never get the opportunity to doDucklikeThings because the class doesn't implement the interface needed to satisfy the compiler type check.
Class A has all the capabilities needed to doDucklikeThings - it quacks, it walks ...
This isn't even a static type checking / dynamic type checking issue - in the functional language Clean we might do something like this (haven't checked this code)
doDucklikeThings :: a -> a | quack a & walk a
doDucklikeThings ducky
# quack(ducky)
# walk(ducky)
= ducky
Any type that defined quack and walk could be passed to doDucklikeThings and would type check at compile time.
Here's an example of Clean overloaded functions
Start = doDucklikeThings(0)
+ doDucklikeThings(0.0)
+ doDucklikeThings('z')
// + doDucklikeThings("ZZZ")
class walk a :: a -> Int
class quack a :: a -> Int
instance walk Int where walk a = 1
instance quack Int where quack a = 1
instance walk Real where walk a = 2
instance quack Real where quack a = 2
instance walk Char where walk a = 3
instance quack Char where quack a = 3
//instance walk {#Char} where walk a = 4
doDucklikeThings a = quack(a) + walk(a)
Here's one of the infered types
doDucklikeThings :: a -> Int | walk a & quack a
and if we uncomment those two lines we get this error
Overloading error [duck.icl,4,Start]: "quack" no instance available of type {#Char}
(replace [] brackets below with <>. [] were used to get around html limitations)
[T extends CanQuack & CanWalk] void doDucklikeThings(T t)
{
t.quack();
t.walk();
}
The code above did not compile so I modified it into:
class Duck[T extends CanQuack & CanWalk] {
void doDucklikeThings(T t) {
t.quack();
t.walk();
}
}
That works. Is that what you were trying to demonstrate?
I didn't realise that comment moderation was enabled here! Damn! I'll turn it off now.
Isaac Gouy said:
"Sorry, that isn't what folk mean when they say duck typing."
In Ruby, etc., capabilities of an object are generally expressed by what methods they have. In Java, the same is often true, but another way we can do it in Java is by implementing interfaces.
In fact, if you make interfaces that only have one method, then you are making interfaces that logically ARE a method. I have my own List type, and it implements an interface called AddAll (among others), which just has one method, addAll. I'm equating interfaces and methods.
That's what I proposed here - build up your Duck object by implementing interfaces that represent the individual capabilities that it has . It's not duck typing as in Ruby, but I think it's close.
You can make a method that accepts anything that implements CanQuack and CanWalk - it's a bit of a stretch, I agree.
A possible advantage of this implementation of duck typing over others is that you can't get it wrong as easily - if you implement the interface[s] then you should know the contracts that that interface represents - rather than just going on method name.
It's not duck typing as in Ruby, but I think it's close.
It's not duck typing, full-stop.
It's not close because it requires explicit, detailed, static definition of interfaces and interface use in advance. None of that is required by duck typing - duck typing is notional not concrete.
Ricky wrote I don't know Clean, just a little Haskell, but it looks to me like your definition of Duck in Clean is similar to class Duck implements CanWalk,CanQuack
The difference is that it doesn't require explicit, detailed, static definition of interface use in advance.
We don't need to hard-code implements CanWalk,CanQuack - type inference will figure out what other functions must exist for the definition doDucklikeThings a = quack(a) + walk(a) to be valid.
I looked at the definition of duck typing, by the author of Ruby, and I don't think it actually needs to be dynamic.
Your probebly right
What I did was duck typing, but the capabilities are statically checked. Nothing in that definition says that they capabilities must be only dynamically checked.
Depending on how you interper dynamically, your probebly right too.
I use interfaces to express capabilities, which seems to be ideal.
yeah.
Isaac Gouy said...
It's not duck typing, full-stop.
and he's right too.
(as you would still end up writing adapers, next to other practical problems.)
and its because of this:
Duck typing is about just happening to use the right names - not explicitly declaring interfaces.
however the compiler can savely check that those methods do exist at compile time for 99% of the cases.
so if we introduce 'like' or perhaps better 'reflects' and enhance the compiler we'd be pritty much there. an annotation to override the odd case where you can't predict it...
But then again not everone likes ducktyping and, even sucjesting to, introduce another keyword might get you schot so..
you can always fill in an RFC I suppose.
greets,
Michael B
Ducks eat fish?
does not solve all my problems.
I need Quack to return an unknown type
basically:
T Quack(T t){
do_something_with(t);
return t;
}
T can be, for instance, Integer, String, etc, as long as it returns the same type it takes in.
<T> T quack(T t) { doSomethingWith(t); return t; }
Post a Comment