【问题标题】:What is the difference between anonymous functions and partial functions?匿名函数和偏函数有什么区别?
【发布时间】:2020-08-23 08:19:48
【问题描述】:

我正在阅读有关 scala 匿名函数 here 的信息,发现它们可以采用以下格式:

{ case p1 => b1 … case pn => bn }

但是,我认为这就是部分函数的编写方式。事实上,在this blog post 中,作者将部分函数称为匿名函数。起初他说collect 采用部分函数,​​但后来似乎称它为匿名函数(“collect 可以处理您的匿名函数......”)。

难道只是一些匿名函数是偏函数吗?如果是这样,所有部分函数都是匿名的吗?或者他们只是匿名,如果格式像 Alvin Alexander 在这里的例子:

val divide2: PartialFunction[Int, Int] = {
    case d: Int if d != 0 => 42 / d
}

【问题讨论】:

  • 好的,所以你混淆了很多东西。首先,Scala 中的 function 是什么。我们可以将其定义为只是一个计算,它类似于一个方法,但主要区别(还有很多其他)是函数是值(意味着它们是某个类的实例,因此它们是对象) 而方法不是。 - 函数字面量是语言提供的一种特殊语法,用于创建函数类的实例。 - 匿名函数只是一个没有名字的函数,所以map(x => x + 1) 是一个匿名函数,而val f = (x: Int) => x + 1 不是。
  • 现在偏函数只是一个没有与所有输入关联的值的函数。而PartialFunction 类扩展了Function1 类,所以所有偏函数都是函数。最后,是的,该语言为创建部分函数提供了特殊的语法(类似于模式匹配),但是当编译器需要一个完整的函数时,也可以使用相同的语法,并且编译器会检查匹配的完整性。
  • @RyanQuey 在 scala 库中的某处定义了从部分函数到常规函数的隐式转换。这就是为什么您可以将cb : PartialFunction[Try[_], Unit] 传递给onCompletegetPosts。但是,如果您尝试使用此常规函数支持的部分函数无法处理的参数运行常规函数,它将引发异常。
  • @RyanQuey 不会发生 cb ,因为它只是类型定义的部分函数,​​但实际上它是常规函数,因为它可以处理所有参数。
  • @BogdanVakulenko 不是隐式转换,只是基本的子类型。 PartialFunction 扩展了 Function.

标签: scala anonymous-function partialfunction


【解决方案1】:

匿名和部分是不同的概念。我们不会说下面的函数是匿名的

val divide2: PartialFunction[Int, Int] = {
    case d: Int if d != 0 => 42 / d
}

因为它绑定到名称divide2,但是我们可以说divide2 是根据匿名(函数)值定义的

{ case d: Int if d != 0 => 42 / d }

在同样的意义上,x 在下面的定义中被定义为匿名值42

val x: Int = 42

partial的正交概念是指函数的特殊子类型,而不是特定类型的值是否绑定到名称。

【讨论】:

    【解决方案2】:

    来自您链接的关于模式匹配匿名函数的文档:

    如果预期类型是 SAM 可转换为 scala.Functionk[S1,…,Sk, R],则该表达式被视为等同于匿名 功能:

    (x1:S1,…,xk:Sk) => (x1,…,xk) match {   
        case p1 => b1 … case pn => bn 
    }
    

    在这里,每个 xi 都是一个新名称。如此处所示,这个匿名 函数又等效于以下实例创建 表达式,其中 T 是所有类型的弱最小上界 双。

    new scala.Functionk[S1,…,Sk, T] {   
      def apply(x1:S1,…,xk:Sk): T = (x1,…,xk) match {
        case p1 => b1 … 
        case pn => bn   
      } 
    } 
    

    如果预期类型是scala.PartialFunction[S, R],则认为表达式等价 到以下实例创建表达式:

    new scala.PartialFunction[S, T] {   
      def apply(x: S): T = x match {
        case p1 => b1 … case pn => bn   
      }   
      def isDefinedAt(x: S): Boolean = {
        case p1 => true … case pn => true
        case _ => false   
      } 
    }
    

    您的第一个代码 sn-p 是模式匹配匿名函数,但不一定是偏函数。只有将它提供给带有PartialFunction 参数的方法或分配给PartialFunction 类型的变量时,它才会变成PartialFunction

    所以你是对的,只有一些(模式匹配)匿名函数是部分函数(AFAIK,用粗箭头定义的函数文字,例如x => x,只能用于创建FunctionN 实例而不是@987654331 @ 实例)。

    然而,并不是所有的偏函数都是匿名函数。定义PartialFunctions 的无糖方法是扩展PartialFunction 特征(扩展Function1)并手动覆盖isDefinedAtapply 方法。例如,divide2 也可以这样定义,带有一个匿名类:

    val divide2 = new PartialFunction[Int, Int] {
      override def isDefinedAt(x: Int) = x != 0
      override def apply(x: Int) = 42 / x
    }
    

    不过,您可能不会经常看到这种情况,因为仅使用模式匹配来定义 PartialFunction 要容易得多。


    在您链接的 Alvin Alexander 的博客文章中,作者将模式匹配匿名偏函数字面值称为匿名函数,只是因为它恰好是偏函数 匿名函数。你也可以像这样定义函数:

    
    List(42, "cat").collect(new PartialFunction[Any, Int] {
      def isDefinedAt(x: Any) = x.isInstanceOf[Int]
      def apply(x: Any) = x match {
        case i: Int => i + 1
      }
    })
    

    它不再是一个匿名函数,尽管它仍然是一个匿名对象,它是一个匿名类的实例。或者你可以预先定义一个单例对象,然后使用它。

    object Foo extends PartialFunction[Any, Int] {
      def isDefinedAt(x: Any) = x.isInstanceOf[Int]
      def apply(x: Any) = x match {
        case i: Int => i + 1
      }
    }
    List(42, "cat").collect(Foo)
    

    不管你怎么定义它,它都是一个偏函数。

    【讨论】:

      【解决方案3】:

      这是编写模式匹配部分函数的另一种方式。

      val divide = new PartialFunction[Int, Int] {
          def apply(x: Int) = 42 / x
          def isDefinedAt(x: Int) = x != 0
      }
      

      本质上,偏函数是没有为一组输入定义的函数。可能就像在示例中那样,除以 0 没有意义,或者您可能想要限制某些特定值。

      偏函数的妙处在于它与 orElse、andThen 和 collect 有协同作用。根据您是否在除法函数中输入 0,您的变量可以传递给 andThen,如果它不是 0,则可以通过 orElse,如果它是 0。最后,collect 只会应用您的部分如果在该输入上定义了函数。

      创建偏函数的方式通常是通过模式匹配大小写,如示例所示。

      最后,Scala 中的匿名函数就像 Python 中的 lambda。这只是一种无需“命名”即可创建函数的方法。

      例如

      val f: Int => Int = (x: Int) => x * x
      collect {
          case a: Int => 1-a
      }
      

      【讨论】:

      • 为什么 scaladoc 给出了以下作为匿名函数的示例,但有一个绑定文字 (f)? val f: Int => Int = (x: Int) => x * x
      • 从技术上讲,它是一个匿名函数,然后您将其分配给变量,从而将其绑定到名称。
      • 好的,知道了。谢谢路易斯米格尔:)
      猜你喜欢
      • 2017-04-04
      • 2021-12-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-27
      • 1970-01-01
      • 1970-01-01
      • 2017-04-26
      相关资源
      最近更新 更多