【问题标题】:Implicit Conversions on Generic Trait泛型特征的隐式转换
【发布时间】:2011-01-28 14:11:07
【问题描述】:

我正在实现一个数据结构,并希望用户能够使用任何类型作为键,只要他提供了一个合适的键类型来包装它。我有这种键类型的特征。这个想法是从基类型到键类型进行隐式转换,反之则(实际上)只使用基类型。特征如下所示:

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
}

调用站点代码可能如下所示:

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

计划是 K 类型的值应隐式转换为已排序的 Key[K] 或应分别隐式使用 Key[K] 上的排序,因此一切都应该解决。当然,没有办法在特征本身中实现隐式base2key。或者是否存在,也许使用隐式传递的类清单?考虑到这一点,我找不到任何参考资料。

是否有可能以某种方式静态断言扩展Key[T] 的任何类型都将带有隐式转换T =&gt; Key[T]?可悲的是,伴生对象不能有抽象方法。

假设这可行,整个企业是否可行,或者所述用例是否需要多个链式隐式转换? (正如我所读的那样,链接不会发生。)

附录:有了上面的定义,我可以使用sortWith(_.key &lt;= _.key)Node(key : K, ...)(在K &lt;% Key[K]下)的序列进行排序,但不能使用sortBy(_.key)。所以,很明显,从KKey[K] 的转换是隐式发生的,即使我从未在任何地方声明它,但Key[K] 上没有Ordering 隐式可用。这是怎么回事?

【问题讨论】:

  • 你为什么使用 Ordered,而不是 Ordering?
  • 因为一个类型有序但一个有序。它会有所作为吗?如何实现产生 Ordering 的通用/抽象函数?
  • 我认为也许将排序作为foo 的隐式参数传递会起作用,但这当然与视图绑定冲突。

标签: scala generics implicit traits


【解决方案1】:

您问“是否有可能以某种方式静态断言任何扩展 Key[T] 的类型都将带有隐式转换 T =&gt; Key[T]?可悲的是,伴随对象不能有抽象方法。”

但是您的示例是静态断言:当您需要从TKey[T] 的视图时,您在编译时断言您只能调用foo 来获取可以提升为键的类型。还是我误会了什么?

关于附录:您说您很惊讶“从KKey[K] 的转换是隐式发生的,即使我从未在任何地方声明过”。问题是,您确实声明了它:T &lt;% Key[T](我在这里使用 T 而不是 K,您似乎混淆了 base *T*ype 和 *K*在这里?)。这等同于

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

因此,当您执行sortWith(_ &lt;= _) 时,您会将T 强制转换为Key[T](其中&lt;= 是根据特征Ordered[Key[T]] 定义的)。

以你的 Node 为例,为什么不这样做

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

希望对您有所帮助...

【讨论】:

  • 1) 是的,我断言K &lt;% Key[K],但我从未真正实现过这种转换。我假设我的实现 (StringKey) 由其构造函数隐式提供。 2) 我一直使用TKey,但这在这里可能并不明显。我在Key 的上下文中使用T,它只是一个类型,而K 在它的使用上下文中,它是键类型。为了清楚起见,我将在上面进行编辑。 3)至于您的建议,这只是将提供订购的责任向上移动,对吗?这实际上可能有效,因为必须在某个地方提供具体类型。谢谢!
  • 我仍然很好奇为什么需要这样做,因为我已经知道密钥是Ordered。很奇怪。
  • 好吧,您的解决方案在我大量使用的test 中丢失了从KKey[K] 的隐式转换(对于“其他内容”,我没有详细说明)。你能把两者结合起来吗?
  • 原来def foo[K](bar : Seq[K])(implicit ord: Ordering[K], base2key : K =&gt; Key[K]) 工作正常。在foo 的调用点,base2key 的具体实现必须是可见的;排序显然是推断的或隐含的。很奇怪,那个。如果您相应地调整您的解决方案,我会接受它。
  • 查看我的参考答案以了解一个新问题:我们如何确保使用通过Key 定义的排序,而不是K 上可能存在的其他排序?
【解决方案2】:

以下是您问题的可能解决方案(希望我正确理解了您的要求):

// 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})

在这种情况下,Key[T]T 类型的包装器,Keyable[T] 是允许将类型 T 转换为 Key[T] 的类型 cals。我还展示了base2key 的外观。

【讨论】:

  • 这可能有效(没有尝试过),但它限制了可能的键类型的数量(即基本类型不实现 Keyable 或转换!)。您也许可以提取一些更隐含的字符串,但它会比我已经拥有的更加复杂。
  • @Raphael:您可以将基本类型的所有隐式Keyable vals 放在object Keyable 中,它们始终可用。
  • 男孩,这将是一个黑客。我正在寻找一种更适合 Scala 的解决方案。但无论如何,感谢您的努力。
  • @Raphael:嗯...我不知道你为什么认为这是一种黑客行为(你能解释一下吗?)。 Ordering 本身对基本类型(如 Int、Boolean 等)使用相同的方法:github.com/blair/scala/blob/…
  • 如果我想允许 any 类型作为键,我必须在那里列出所有类型,这是不可能的。因此,我必须将自己限制为可能的类型(我已经知道了!)。这对我来说是骇客。并不是说这可能不是一种有效的方法,我只是不喜欢它的味道。 (请注意,“我们”编码比 Scala 库高一个抽象级别,即在它上面。)
【解决方案3】:

在这个答案中,我将保留当前最好的版本以供参考。使用this answer 更集中的问题;根据this one,2.9 将被淘汰。

Key 特征保持不变;我添加了一个特定的功能来说明:

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
  }
}

以下工作按预期工作(如果import Key._ 完成):

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

假设我们有一个简单的class Node[K](val key : K)。同样,一切都按预期进行:

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

再举一个例子,假设这段代码只使用Key[T] 接口:

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

请注意,这是因为 map 直接生成 Seq[Key[K]] 后编译的;排序不需要转换。现在,如果我们有一个正确的Key 实现,比如说

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)
}

以下应该有效:

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)

但实际上,没有找到从StringKeyString 的转换:

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

这很奇怪;如果使用泛型类型参数声明,会发生从Key[String]String 的转换。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-12
    • 1970-01-01
    • 2017-09-10
    • 1970-01-01
    • 1970-01-01
    • 2018-12-08
    相关资源
    最近更新 更多