2

I am implementing a datastructure and want the user to be able to use any type as key as long as he provides a suitable key type wrapping it. I have a trait for this key type. The idea is to have implicit conversions from base to key type and the other way round to (virtually) just use the base type. The trait looks like this:

trait Key[T] extends Ordered[Key[T]] {
  def toBase : T

  // Further stuff needed for datastructure...
}
object Key {
  implicit def key2base[T](k : Key[T]) : T = k.toBase
}

Call site code could look like this:

def foo[K <% Key[K]]( bar : Seq[K] ) = bar.sorted(0)

Plan is that values of type K should be implicitly converted to Key[K] which is ordered or the ordering on Key[K] should be implcitly used, respectively, so everything should work out. Of course, there is no way to implement implicit base2key in the trait itself. Or is there, maybe using implicitly passed class manifests? I could not find any references considering this.

Is it possible to somehow assert statically that any type extending Key[T] will come with an implicit conversion T => Key[T]? The companion object can not have abstract methods, sadly.

Assume this works out, is the entire enterprise feasible or will the stated use case need multiple chained implicit conversions, anyway? (Chaining does not happen, as I have read.)

Addendum: With above definition, I can sort a sequence of Node(key : K, ...) (under K <% Key[K]) by using sortWith(_.key <= _.key), but not using sortBy(_.key). So, obviously, the conversion from K to Key[K] happens implicitly, even though it is never declared anywhere by me, but there is no Ordering on Key[K] available implicitly. What is going on here?

Raphael
  • 9,779
  • 5
  • 63
  • 94
  • Why do you use Ordered, not Ordering? – Landei Jan 28 '11 at 14:37
  • Because a type *is* ordered but *has* an Ordering. Would it make a difference? How can I implement a generic/abstract function yielding an Ordering? – Raphael Jan 28 '11 at 14:44
  • I thought maybe passing the ordering as implicit parameter of `foo` would work, but of course this conflicts with the view bound. – Raphael Jan 28 '11 at 14:54

3 Answers3

1

you ask "Is it possible to somehow assert statically that any type extending Key[T] will come with an implicit conversion T => Key[T]? The companion object can not have abstract methods, sadly."

but your example is a static assertion : when you require a view from T to Key[T], you assert at compile time that you may only call foo for types which can be lifted to keys. Or am i misunderstanding something?

regarding the addendum: you say you are surprised that "the conversion from K to Key[K] happens implicitly, even though it is never declared anywhere by me". thing is, you did declare it: T <% Key[T] (I am using T here instead of K, you seem to be mixing up the notion of base *T*ype and *K*ey here?). This is identical to

def foo[T](bar : Seq[T])(implicit view: T => Key[T]) = bar.sortWith(_ <= _)(0)

thus, when you do sortWith(_ <= _) you coerce T into Key[T] (for which <= is defined as per trait Ordered[Key[T]]).

taking your Node example, why not do

case class Node[K](key: K)
def test[K](bar: Seq[Node[K]])(implicit ord: Ordering[K]) = bar.sortBy(_.key)(0)

hope that helps...

0__
  • 66,707
  • 21
  • 171
  • 266
  • 1) Yes, I assert `K <% Key[K]`, but I never actually implemeted this conversion. I assume that the implementation (`StringKey`) I have provides this implicitly by its constructor. 2) I used `T` and `Key` consistently, but that might not be obvious here. I use `T` in the context of `Key` were it is only a type, and `K` in context of its use where it is the key type. I will edit this above for clarity. 3) As for your suggestion, this just moves the responsibility for providing the Ordering upwards, right? That might actually work since somewhere a concrete type must be available. Thanks! – Raphael Jan 31 '11 at 16:10
  • I am still curious why this is needed, though, since I already know the keys are `Ordered`. Weird. – Raphael Jan 31 '11 at 16:11
  • Well, your solution loses the implicit conversion from `K` to `Key[K]` in `test` which I employ heavily (for "Further stuff" I did not elaborate on). Can you combine both? – Raphael Jan 31 '11 at 16:57
  • Turns out that `def foo[K](bar : Seq[K])(implicit ord: Ordering[K], base2key : K => Key[K])` works fine. At the calling site of `foo`, a concrete implementation of base2key has to be visible; the ordering is apparently inferred or implicitly available. Very strange, that. If you adapt your solution accordingly, I will accept it. – Raphael Jan 31 '11 at 17:06
  • See my reference answer for a new problem: how do we ensure that the ordering defined via `Key` is used, not some other one that might exist on `K`? – Raphael Jan 31 '11 at 17:46
0

Here is possible solution to your problem (hope I understood your requirements correctly):

// Key stuff

trait Keyable[T] {
  def toKey(t: T): Key[T]
}

trait Key[T] extends Ordered[Key[T]] {
  def toBase() : T
}

object Key {
  implicit def key2base[T](k : Key[T]) : T = k.toBase
  implicit def base2key[T : Keyable](k : T) : Key[T] = implicitly[Keyable[T]].toKey(k)
}

// more concrete stuff - namely A

class A(val i: Int) {
  override def toString = "A {" + i + "}"
}

object A {
  implicit val aKeyable = new Keyable[A] {
    def toKey(t: A) = new Key[A] {
      def toBase() = t
      def compare(that: Key[A]) = t.i compare that.i
    }
  }
}

// testing

def foo[K : Keyable](bar: Seq[K]) = bar.sortBy(implicitly[Keyable[K]].toKey)

val list = List(new A(5), new A(1), new A(3))

println(foo(list)) // prints: List(A {1}, A {3}, A {5})

In this case Key[T] is wrapper for type T and Keyable[T] is type calss that allows to convert type T to Key[T]. I also showed how base2key can look like.

tenshi
  • 26,268
  • 8
  • 76
  • 90
  • This might work (did not try it), but it limits the amount of possible key types (i.e. base types do not implement Keyable or the conversions!). You might be able to pull some more implicit strings, but it will then be even more convoluted than what I have already. – Raphael Jan 31 '11 at 16:05
  • @Raphael: You can place all implicit `Keyable` vals for basic types in `object Keyable` and they always would be available. – tenshi Jan 31 '11 at 16:18
  • Boy, that would be a hack. I am looking for a solution that is more idomatic for Scala. But thanks for your effots, anyway. – Raphael Jan 31 '11 at 17:44
  • @Raphael: Hmm... I'm not sure why you considering this as a hack (can you please explain?). `Ordering` itself uses the same approach for the base types (like Int, Boolean, etc...): https://github.com/blair/scala/blob/ebffdbec14262ab01664f6b51f7721b4bcbf936e/src/library/scala/math/Ordering.scala#L123 – tenshi Jan 31 '11 at 17:59
  • If I wanted to allow *any* type as key, I had to list all types there, which is impossible. Therefore, I have to restrict myself to likely types (I already know!). This qualifies as hack for me. Not saying it might not be a valid approach, I just don't like its flavor. (Note that "we" code one abstraction level higher than the Scala library, i.e. on it.) – Raphael Jan 31 '11 at 18:36
  • @Raphael: Ok, seems that I failed to fully understand requirements. I tried to follow them very closely and come and with this solution. Even for me it looks a little bit overcomplicated (with both `Keyable` and `Key`) – tenshi Jan 31 '11 at 18:46
0

In this answer, I will keep the currently best version for reference. Using this answer to more focused question; will be obsolete with 2.9 according to this one.

The Key trait remains unchanged; I add a specific function for illustration:

trait Key[T] extends Ordered[Key[T]] {
  def toBase : T
  def foo(i : Int) : Key[T]
}
object Key {
  implicit def key2base[T](k : Key[T]) : T = k.toBase
  implicit def ordering[T <% Key[T]] = new Ordering[T]{
    def compare(x: T, y: T) = x compare y
  }
}

The following works as expected (if import Key._ is done):

def min[K <% Key[K]](l : Seq[K]) : K = l.sorted.head

Let us assume we have a simple class Node[K](val key : K). Again, things work as expected:

def min[K <% Key[K]](l : Seq[Node[K]]) : Node[K] = l.sortBy(_.key).head

For another example, assume this code using only the Key[T] interface:

def test[K <% Key[K]](bar : Seq[K]) = 
  bar.map(_.foo(3)).sorted

Note that this compiles since map yields a Seq[Key[K]] directly; no conversion needed for sorting. Now, if we have a proper implementation of Key, say

class StringKey(val key : String) extends Key[String] {
  def foo(i : Int) =  StringKey(this.key + "_" + i)
  def toBase = key
  override def compare(other : Key[String]) = -1*this.key.compare(other.toBase)
}
object StringKey {
  def apply(key : String) = new StringKey(key)
  def unapply(key : String) = Some(key)
  implicit def string2key(s : String) = StringKey(s)
}

the following should work:

import StringKey.string2key
import Key.key2base
val bla : Seq[String] = test(Seq("b", "c", "a")).map(_.capitalize)
println(bla)
// expected output: Seq(C_3, B_3, A_3)

But actually, the conversion from StringKey to String is not found:

error: value capitalize is not a member of this.Key[java.lang.String]

This is strange; there is a conversion from Key[String] to String, if declared with a generic type parameter.

Community
  • 1
  • 1
Raphael
  • 9,779
  • 5
  • 63
  • 94