【问题标题】:Scala: Why use implicit on function argument?Scala:为什么在函数参数上使用隐式?
【发布时间】:2014-05-08 15:27:18
【问题描述】:

我有以下功能:

def getIntValue(x: Int)(implicit y: Int ) : Int = {x + y}

我到处都能看到上面的声明。我了解上述功能在做什么。这是一个带有两个参数的柯里化函数。如果省略第二个参数,它将调用返回 int 的隐式定义。所以我认为这与为参数定义默认值非常相似。

implicit val temp = 3

scala> getIntValue(3)
res8: Int = 6

我想知道上述声明有什么好处?

【问题讨论】:

    标签: scala implicit convention


    【解决方案1】:

    这是我的“务实”答案:您通常将柯里化用作一种“惯例”,而不是其他任何有意义的东西。当您的最后一个参数恰好是“按名称调用”参数(例如:: => Boolean)时,它会派上用场:

    def transaction(conn: Connection)(codeToExecuteInTransaction : => Boolean) = {
    
       conn.startTransaction  // start transaction
    
       val booleanResult = codeToExecuteInTransaction //invoke the code block they passed in
    
      //deal with errors and rollback if necessary, or commit
      //return connection to connection pool
    }
    

    这是说“我有一个名为transaction的函数,它的第一个参数是一个连接,它的第二个参数是一个代码块”。

    这允许我们像这样使用这个方法(使用“我可以使用大括号代替括号规则”):

    transaction(myConn) {
    
       //code to execute in a transaction
      //the code block's last executable statement must be a Boolean as per the second
      //parameter of the transaction method
    
    }
    

    如果您不使用该事务方法,那么这样做看起来很不自然:

    transaction(myConn, {
    
       //code block
    
    })
    

    implicit 怎么样?是的,它看起来像是一个非常模棱两可的构造,但一段时间后你就会习惯它,隐式函数的好处是它们有范围规则。所以这意味着对于生产,您可以定义一个隐式函数来从 PROD 数据库获取该数据库连接,但是在您的集成测试中,您将定义一个将取代 PROD 版本的隐式函数,它将用于获取连接来自 DEV 数据库,而不是在您的测试中使用。

    举个例子,我们给事务方法添加一个隐式参数怎么样?

    def transaction(implicit conn: Connection)(codeToExecuteInTransaction : => Boolean) = {
    
    }
    

    现在,假设我的代码库中有一个隐式函数somewhere,它返回一个连接,如下所示:

    def implicit getConnectionFromPool() : Connection = { ...}
    

    我可以像这样执行交易方法:

    transaction {
       //code to execute in transaction
    }
    

    Scala 会将其翻译为:

    transaction(getConnectionFromPool) {
      //code to execute in transaction
    }
    

    总而言之,隐式是一种非常好的方法,它不必让开发人员为所需参数提供值,因为该参数在 99% 的时间在您使用该函数的任何地方都是相同的。在这 1% 的时间里你需要一个不同的连接,你可以通过传入一个值来提供你自己的连接,而不是让 Scala 找出哪个隐式函数提供了这个值。

    【讨论】:

      【解决方案2】:

      在您的具体示例中,没有任何实际好处。事实上,对这个任务使用隐式只会混淆你的代码。

      implicits 的标准用例是Type Class Pattern。我会说这是唯一实际有用的用例。在所有其他情况下,最好明确说明。

      这是一个类型类的示例:

      // A typeclass
      trait Show[a] {
        def show(a: a): String
      }
      
      // Some data type
      case class Artist(name: String)
      
      // An instance of the `Show` typeclass for that data type
      implicit val artistShowInstance =
        new Show[Artist] {
          def show(a: Artist) = a.name
        }
      
      // A function that works for any type `a`, which has an instance of a class `Show`
      def showAListOfShowables[a](list: List[a])(implicit showInstance: Show[a]): String =
        list.view.map(showInstance.show).mkString(", ")
      
      // The following code outputs `Beatles, Michael Jackson, Rolling Stones`
      val list = List(Artist("Beatles"), Artist("Michael Jackson"), Artist("Rolling Stones"))
      println(showAListOfShowables(list))
      

      这种模式起源于一种名为 Haskell 的函数式编程语言,结果证明它比编写模块化和解耦软件的标准 OO 实践更实用。它的主要好处是它允许您使用新功能扩展已经存在的类型,而无需更改它们。

      有很多细节没有提到,比如语法糖、def 实例等等。这是一个巨大的主题,幸运的是它在整个网络上都有很大的覆盖面。只需谷歌搜索“scala type class”。

      【讨论】:

      【解决方案3】:

      除了您的示例之外,还有很多好处。 我只给一个;同时,这也是你可以在某些场合使用的技巧。

      假设您创建了一个 trait,它是其他值的通用容器,例如列表、集合、树或类似的东西。

      trait MyContainer[A] {
        def containedValue:A
      }
      

      现在,在某些时候,您会发现迭代包含值的所有元素很有用。 当然,这只有在包含的值是可迭代类型时才有意义。

      但是因为您希望您的类对所有类型都有用,所以您不想将 A 限制为 Seq 类型或 Traversable 或类似的类型。 基本上,您需要一个方法:“我只能在 A 属于 Seq 类型时被调用。” 如果有人调用它,比如MyContainer[Int],那应该会导致编译错误。

      这是可能的。 您需要的是A 属于序列类型的一些证据。 您可以使用 Scala 和隐式参数来做到这一点:

      trait MyContainer[A] {
        def containedValue:A
        def aggregate[B](f:B=>B)(implicit ev:A=>Seq[B]):B =
          ev(containedValue) reduce f
      }
      

      因此,如果您在 MyContainer[Seq[Int]] 上调用此方法,编译器将查找隐式 Seq[Int]=>Seq[B]。 对于编译器来说,这真的很容易解决。 因为有一个名为identity 的全局隐式函数,它始终在作用域内。 它的类型签名类似于:A=>A

      它只是返回传递给它的任何参数。

      我不知道这个模式是怎么叫的。 (有人可以帮忙吗?) 但我认为这是一个巧妙的技巧,有时会派上用场。 如果您查看 Seq.sum 的方法签名,您可以在 Scala 库中看到一个很好的示例。 在sum的情况下,使用了另一种隐式参数类型;在这种情况下,隐式参数证明包含的类型是数字,因此,可以从所有包含的值构建总和。

      这不是隐式的唯一使用,当然也不是最突出的,但我会说这是一个光荣的提及。 :-)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-22
        • 2011-02-12
        相关资源
        最近更新 更多