【问题标题】:Implicit parameters won't work on unapply. How to hide ubiquitous parameters from extractors?隐式参数不适用于 unapply。如何从提取器中隐藏无处不在的参数?
【发布时间】:2012-09-02 12:56:40
【问题描述】:

显然提取器对象中的 unapply/unapplySeq 不支持隐式参数。假设这里有一个有趣的参数 a,以及一个令人不安的无处不在的参数 b,在提取 c 时会很好地隐藏起来。

[编辑]:似乎我的 intellij/scala-plugin 安装中出现了问题,导致了这种情况。我无法解释。我的智能最近遇到了许多奇怪的问题。重新安装后,我无法再重现我的问题。确认 unapply/unapplySeq 确实允许隐式参数!感谢您的帮助。

这不起作用(**EDIT:是的,它起作用):**

trait A; trait C; trait B { def getC(a: A): C }

def unapply(a:A)(implicit b:B):Option[C] = Option(b.getC(a))

根据我对理想提取器应该是什么样的理解,其中意图对于 Java 人员来说也很直观,这个限制基本上禁止了依赖于附加参数的提取器对象。

您通常如何处理此限制?

到目前为止,我已经有了这四种可能的解决方案:

1)我想改进的最简单的解决方案:不要隐藏b,提供参数b和a,作为元组形式的unapply的普通参数:

object A1{ 
    def unapply(a:(A,B)):Option[C] = Option(a._2.getC(a._1)) }

在客户端代码中:

 val c1 = (a,b) match { case A1(c) => c1 }

我不喜欢它,因为这里有更多的噪音偏离将 a 解构为 c 很重要。此外,由于必须说服 Java 人员实际使用此 scala 代码,因此面临着一种额外的语法新颖性(元组大括号)。他们可能会受到反 scala 的攻击“这都是什么?...为什么不首先使用正常方法并检查是否?”。

2) 在封装对特定 B 的依赖的类中定义提取器,导入该实例的提取器。在导入站点对于 java 人来说有点不寻常,但是在模式匹配站点 b 被很好地隐藏了,并且直观地很明显会发生什么。我最喜欢的。我错过了一些劣势?

class BDependent(b:B){ 
   object A2{ 
    def unapply(a:A):Option[C] = Option(b.getC(a))
         } }

在客户端代码中的用法:

val bDeps = new BDependent(someB)
import bDeps.A2 
val a:A = ...
val c2 = a match { case A2(c) => c }
}

3) 在客户端代码范围内声明提取器对象。 b 是隐藏的,因为它可以在本地范围内使用“b”。妨碍代码重用,严重污染客户端代码(另外,在代码使用前必须说明)。

4) 具有函数 B => C 的 unapply return 选项。这允许导入和使用依赖于参数的提取器,而无需将 b 直接提供给提取器,而是在使用时提供结果。 Java 人可能对函数值的使用感到困惑,b 不是隐藏的:

 object A4{
  def unapply[A,C](a:A):Option[B => C] = Option((_:B).getC(a))
   }

然后在客户端代码中:

 val b:B = ...
 val soonAC: B => C = a match { case A4(x) => x }
 val d = soonAC(b).getD ...

补充说明:

  • 正如this answer 中所建议的,“视图边界”可能有助于让提取器处理隐式转换,但这对隐式参数 没有帮助。出于某种原因,我不喜欢使用隐式转换。
  • 查看了“上下文边界”,但它们似乎具有相同的限制,不是吗?

【问题讨论】:

    标签: scala pattern-matching implicit extractor


    【解决方案1】:

    您的第一行代码在什么意义上不起作用?对于提取器方法的隐式参数列表当然没有任意禁止。

    考虑以下设置(我使用普通的旧类而不是案例类来表明这里没有发生额外的魔法):

    class A(val i: Int)
    class C(val x: String)
    class B(pre: String) { def getC(a: A) = new C(pre + a.i.toString) }
    

    现在我们定义一个隐式 B 值并使用您的 unapply 方法创建一个提取器对象:

    implicit val b = new B("prefix: ")
    
    object D {
      def unapply(a: A)(implicit b: B): Option[C] = Option(b getC a)
    }
    

    我们可以这样使用:

    scala> val D(c) = new A(42)
    c: C = C@52394fb3
    
    scala> c.x
    res0: String = prefix: 42
    

    完全符合我们的预期。我不明白你为什么需要在这里找到解决方法。

    【讨论】:

    • 脚注:我知道这更像是一个评论而不是一个答案,但一个完整的工作示例不适合评论。如果它没有回答问题,我很乐意删除。
    • 嗯!那将是个好消息。但我很困惑......我已经与这个问题斗争了好几天。但是我的 intellij/scala 插件也有几个问题。试图在 REPL 中重现我的问题...
    【解决方案2】:

    您遇到的问题是隐式参数是编译时(静态)约束,而模式匹配是运行时(动态)方法。

    trait A; trait C; trait B { def getC(a: A): C }
    
    object Extractor {
      def unapply(a: A)(implicit b: B): Option[C] = Some(b.getC(a))
    }
    
    // compiles (implicit is statically provided)
    def withImplicit(a: A)(implicit b: B) : Option[C] = a match { 
      case Extractor(c) => Some(c)
      case _            => None
    }
    
    // does not compile
    def withoutImplicit(a: A) : Option[C] = a match { 
      case Extractor(c) => Some(c)
      case _            => None
    }
    

    所以这是一个概念问题,解决方案取决于您实际想要实现的目标。如果你想要一些类似于可选隐式的东西,你可以使用以下内容:

    sealed trait FallbackNone {
      implicit object None extends Optional[Nothing] {
        def toOption = scala.None
      }
    }
    object Optional extends FallbackNone {
      implicit def some[A](implicit a: A) = Some(a)
      final case class Some[A](a: A) extends Optional[A] { 
        def toOption = scala.Some(a)
      }
    }
    sealed trait Optional[+A] { def toOption: Option[A]}
    

    那么你有implicit b: B你将有implicit b: Optional[B]

    object Extractor {
       def unapply(a:A)(implicit b: Optional[B]):Option[C] = 
          b.toOption.map(_.getC(a))
    }
    
    def test(a: A)(implicit b: Optional[B]) : Option[C] = a match { 
       case Extractor(c) => Some(c)
       case _            => None
    }
    

    以下都编译:

    test(new A {}) // None
    
    {
      implicit object BImpl extends B { def getC(a: A) = new C {} }
      test(new A {}) // Some(...)
    }
    

    【讨论】:

    • 换句话说,它与提取器的概念没有任何关系,而是与隐式的静态性质有关。
    • 感谢您的回答。我不太明白你的例子/解释: withoutImplicit() 不会仅仅因为没有给出隐式而被编译。当然,我确实提供了一个,但我没有匹配或奇怪的 ClassCastExceptions,例如我的 main 不能被强制转换为 Nothing。也许这也与 Intellij 安装损坏有关。抱歉,到目前为止我还无法重现 REPL 中的错误...我很好奇,您能解释一下可选隐式的含义以及您在最后三个列表中得到的内容吗?我不明白,对不起...
    • 好吧,如果当你想使用提取器时你的代码中总是有一个隐式可见,那么你就不需要可选的隐式了,Travis 的回答表明了这一点。我已经理解您希望能够使用提取器编译模式匹配,无论是否给出了这种隐式。在这种情况下,您可以请求 始终 可用的 Optional[B](如果未找到 B 类型的潜在隐式,则返回到 Optional.None)。
    • 啊好吧!现在我明白了你的可选。这在其他情况下可能会派上用场。谢谢
    猜你喜欢
    • 2019-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多