> Andrew wrote:
> > for example, how would you create a single method that
> > took a list of either boys or girls and arranged it so
> > that everyone in the list was sharing a room, without
> > duplicating the code for boys and girls separately?
>
James Wrote:
> It does make me think that there must be a cleaner way to
> express the constraint than with an implicit. But I'm
> stymied about what it might be. The more direct approach
> "def shareList[T <: Skier {type SuitableRoommate = T}]"
> got me errors regarding cyclic references to type T.
>
I think part of the difficulty here is that this is a contrived example dreamed up by Bertrand Meyers over a decade ago. There's no real problem we're trying to solve, and in such cases, it's hard to know what would make a good solution.
However, I get the sense that Andrew's basic desire is to be able to call share on a variable of type Skier. I think the trouble is that if the goal is to have the compiler to statically determine that you're only passing in a suitable roommate each time you call share, and all you know about the object you're calling share on (at compile time) is that it is a Skier, then the compiler doesn't have enough information to compile the code. It just isn't defined at the Skier level of abstraction what type is suitable to be passed to share.
As a result, any time you call share, you will need to call it on either a Boy reference or a Girl reference, or a reference of some other subtype of Skier that has a concrete SuitableRoommate.
I think James Iry's solution is very nice. It is probably obscure to anyone not familiar with Scala, but the key that he uses an implicit function named f to ensure that shareList can only be called when passing Skier subtypes whose SuiteableRoommate type is the same type as the Skier subtype itself.
James, you don't need to actually call f, because by the time you get into the shareList method, T is already known to fulfill the constraint. So this works too (the difference from your code is instead of f(x) share y, it is just x share y):
// use an implicit to prove that the suitable roommate matches
def shareList[T <: Skier](list : List[T])(implicit f : T => Skier {type SuitableRoommate = T} ) = {
val length = list.length
(list take length/2) zip (list drop length/2) foreach {case (x, y) => x share y}
}
// these two compile just fine
shareList(List(new Boy, new Boy, new Boy, new Boy))
shareList(List(new Girl, new Girl, new Girl, new Girl))
For those not familiar with Scala's implicits, an implicit parameter is a parameter that's passed in implicitly without you having to type the code at the call site. You can also do it explicitly, and that would look like this:
shareList(List(new Boy, new Boy, new Boy, new Boy))((boy: Boy) => boy)
shareList(List(new Girl, new Girl, new Girl, new Girl))((girl: Girl) => girl)
Because the parameter is declared implicit in the shareList signature, clients don't need to specify it explicitly. But all it is is a function that takes a Boy and returns a Boy, or a Girl and returns a Girl, respectively, both of which adhere to the constraint James put on the implicit function. That constraint is that the input to the function be a T (which is already defined to be a Skier with the [T <: Skier] at the beginning of the shareList signature), and the output be a Skier whose SuiteableRoommate type is also T. That only works for Boy and Girl. You couldn't pass to shareList a List of Cat or Dog types, which I showed earlier, that had each other as SuitableRoommates.
The implicit function that gets picked up is the identity function, which Scala imports into every Scala source file. This is an implicit function from any type to itself. All this function does is convert a Boy to a Boy, or a Girl to a Girl, which does nothing in this case, except reduce the types that can be passed to shareList. In my version of the shareList method, I don't even ever invoke the function.
One last tweak that could be used to make the shareList function a bit more concise is to use a view bounds. This is just a shorthand for the implicit that James wrote out explicitly. That would look like this:
def shareList[T <% Skier {type SuitableRoommate = T}](list : List[T]) = {
val length = list.length
(list take length/2) zip (list drop length/2) foreach {case (x, y) => x share y}
}
// these two compile just fine
shareList(List(new Boy, new Boy, new Boy, new Boy))
shareList(List(new Girl, new Girl, new Girl, new Girl))
So there it is. I would probably use this code, though I'm not sure I would have thought of it. Not without a lot of trial and error, but maybe from now on I'll just email James Iry and let him have at it first.
If you want a more understandable description of implicits and view bounds, you may want to check out the Feel of Scala video on Parleys:
http://tinyurl.com/dcfm4c