【问题标题】:Adding a validity check dependent on a typeclass (Optional implicits)添加依赖于类型类的有效性检查(可选隐式)
【发布时间】:2010-12-06 08:49:54
【问题描述】:

在 Scala 中,我们可以使用 隐式类型类 有条件地将方法添加到依赖于该类型参数的参数化类型。例如Iterator.sum:

def sum[B >: A](implicit num: Numeric[B]): B = foldLeft(num.zero)(num.plus)

必须有一个 Numeric 类型类的实例才能调用此方法:

scala> List(1, 2, 3).sum
res0: Int = 6

scala> List("a", "b").sum
<console>:6: error: could not find implicit value for parameter num: Numeric[java.lang.String]
       List("a", "b").sum
                  ^

到目前为止,一切都很好。假设我想要一些集合类型,My2Col:

class My2Col[A](a1 : A, a2 : A)

但我想强制要求,如果这是使用A : Numeric那么 a2 &gt; a1。但是,完全有效它可以使用不是数字的A

My2Col("a", "b") //OK
My2Col("b", "a") //OK
My2Col(1, 2)     //OK
My2Col(2, 1)     //THROW IllegalArgumentException

有没有人知道我可以如何做到这一点?

PS。如果有人对更好的问题标题有任何建议,我会全力以赴

【问题讨论】:

    标签: scala typeclass


    【解决方案1】:
    class My2Col[A](a1 : A, a2 : A)(implicit num: Numeric[A] = null){
      for{check <- Option(num); if(check.gteq(a1, a2))}
         throw new IllegalArgumentException
    }
    

    【讨论】:

    • @retronym 在 scala 邮件列表中提出了同样的建议
    • @oxbow_lakes 哇!很高兴知道:)
    • 也许令人惊讶的是,您不需要分号或条件句周围的括号:for (check &lt;- Option(num) if check.gteq(a1,a2)) ...。不错的解决方案!
    【解决方案2】:

    我将通过创建 2 个表示需求的隐式来实现这一点,一个更通用(例如,IntNumeric[T] 以外的所有类型),另一个更具体(Int 或 @ 987654325@)。

    然后,按照隐式解析的规则,我会将更具体的放在Requirement 类型的伴生对象中,将更通用的放在伴生对象的基类中。这样我可以确保编译器首先尝试应用更具体的。

    当然,缺点是如果用户要显式提供隐式参数,则可以绕过此机制而不进行检查。

    类似这样(其中Int 是具有特定规则的类型,Foo 是集合类):

      package ex                                                                                                          
    
      trait Requirement[T] {                                                                                            
        def check(a1: T, a2: T): Unit                                                                                   
      }                                                                                                                 
    
      trait BaseReq {                                                                                                   
        implicit def genericReq[T] = new Requirement[T] {                                                               
          def check(a1: T, a2: T) {println("generic")}                                                                  
        }                                                                                                               
      }                                                                                                                 
    
      object Requirement extends BaseReq {                                                                              
        implicit object IntReq extends Requirement[Int] {                                                               
          def check(a1: Int, a2: Int) = {                                                                               
            println("int")                                                                                              
            if (a2 <= a1) throw new IllegalArgumentException                                                            
          }                                                                                                             
        }                                                                                                               
      }                                                                                                                 
    
      class Foo[T](a1: T, a2: T)(implicit req: Requirement[T]) {                                                        
        req.check(a1, a2)                                                                                               
    
        // whatever `foo` does follows                                                                                  
      }                                                                                                            
    
      object Main {                                                                                                     
        def main(args: Array[String]) {                                                                                 
          new Foo(1, 2)                                                                                                 
          new Foo("S1", "S2")                                                                                           
          new Foo(2, 1)                                                                                                 
        }                                                                                                               
      }
    

    【讨论】:

    • 嗯,我不确定是否可以访问Requirement 对象以调用其方法..
    • @axel22 如果您使用上下文绑定(即Foo[T : Requirement]),您可以使用implicitly[Requirement[T]] 来引用关联的Requirement[T]。就个人而言,我更喜欢按照您最初的方式编写它,并且仅在不需要类型类的实例时才使用上下文边界,因为它是 DRYer。
    • 显然,您也可以通过生成的编译器参数(evidence$1)的名称来引用类型类的实例,因为使用上下文绑定只是添加您自己的隐式参数的糖。不过,这可能不是一个好主意,因为这个名字将来可能会改变。似乎是一个错误,您甚至可以在方法/构造函数主体中使用该名称....
    • @Aaron:是的,我看到了——甚至更好。也许您可以提议将其作为对标准库的增强。
    猜你喜欢
    • 2014-11-17
    • 2012-12-29
    • 1970-01-01
    • 1970-01-01
    • 2017-09-16
    • 1970-01-01
    • 1970-01-01
    • 2015-05-26
    • 2021-04-02
    相关资源
    最近更新 更多