【问题标题】:Why does Scala need parameterless in addition to zero-parameter methods?为什么Scala除了零参数方法还需要无参数方法?
【发布时间】:2012-09-02 07:56:21
【问题描述】:

我了解零参数方法和无参数方法之间的区别,但我真正不了解的是使无参数方法成为必要的语言设计选择。

我能想到的缺点:

  • 令人困惑。每隔一两周,这里或 Scala 邮件列表上就会有关于它的问题。
  • 这很复杂;我们还必须区分() => X=> X
  • 含糊不清:x.toFoo(y) 是指它所说的意思,还是x.toFoo.apply(y)? (答案:这取决于xtoFoo 方法的重载和Fooapply 方法的重载,但如果发生冲突,您在尝试调用之前不会看到错误它。)
  • 它弄乱了操作符风格的方法调用语法:在链接方法时或在末尾避免分号干扰时,没有符号可用于代替参数。使用零参数方法,您可以使用空参数列表 ()

目前,您不能在一个类中同时定义这两者:您会收到一条错误消息,指出该方法已定义。他们也都转换为Function0

为什么不直接将方法def foodef foo() 制作成完全相同的东西,并允许在有或没有括号的情况下调用它们?它的优点是什么?

【问题讨论】:

  • 你甚至没有提到继承规则和与 vals 的交互。哦,名称参数缺乏对称性,这可能是一种可取之处(它们本身也转换为Function0)。
  • 您将两个完全不同的东西混为一谈:no-parens 与 empty-parens methods(如标题所示),以及 Function0 与 thunk(如您的项目符号列表)建议)。
  • @0__ 问题完全不同。那个问我们什么时候应该使用其中一个。这个人问为什么需要两者都有(可能 - 我要问的)一个可行的替代方案,使它们完全相同。我很确定它不支持命名约定,这无论如何都是没有意义的,因为空括号方法可以在有或没有括号的情况下调用(也可以在有或没有它们的情况下定义)。
  • @LuigiPlinge - 好的,它们可能不一样(但相关)。因此,我试图添加一个答案。

标签: scala


【解决方案1】:

柯里化,这就是为什么

Daniel 很好地解释了为什么需要无参数方法。我将解释为什么它们被区别于零参数方法。

许多人将无参数函数和零参数函数之间的区别视为某种模糊形式的语法糖。实际上,它纯粹是 Scala 如何支持柯里化的产物(为了完整起见,请参阅下文,了解柯里化是什么,以及为什么我们都这么喜欢它)。

形式上,一个函数可以有零个或多个参数列表,每个参数列表有零个或多个参数。
这意味着以下是有效的:def adef b(),还有人为的 def c()()def d(x: Int)()()(y: Int) 等......

函数def foo = ??? 的参数列表为零。函数def bar() = ??? 正好有一个参数列表,参数为零。 引入额外的规则将这两种形式混为一谈会破坏柯里化作为一种​​一致的语言特性def a 在形式上等同于 def b()def c()() 两者; def d(x: Int)()()(y: Int) 将等同于 def e()(x: Int)(y: Int)()()

与柯里化无关的一种情况是在处理 Java 互操作时。 Java 不支持柯里化,因此为"test".length()(直接调用java.lang.String#length())这样的零参数方法引入语法糖也可以作为"test".length 调用。

柯里化的简单解释

Scala 支持一种名为“currying”的语言功能,该功能以数学家 Haskell Curry 命名。
Currying 允许您使用多个参数列表定义函数,例如:

def add(a: Int)(b: Int): Int = a + b
add(2)(3) // 5

这很有用,因为您现在可以根据 add 的部分应用来定义 inc

def inc: Int => Int = add(1)
inc(2) // 3

柯里化通常被视为通过库引入控制结构的一种方式,例如:

def repeat(n: Int)(thunk: => Any): Unit = (1 to n) foreach { _ => thunk }

repeat(2) {
  println("Hello, world")
}

// Hello, world
// Hello, world

回顾一下,看看repeat 如何打开另一个使用柯里化的机会:

def twice: (=> Any) => Unit = repeat(2)

twice {
  println("Hello, world")
}

// ... you get the picture :-)

【讨论】:

  • 你的意思是“方法”,你一直说“功能”吗?
  • Scala 在“函数对象”和“方法”之间进行了技术区分,但在更广泛的意义上,两者都用于描述函数。柯里化,作为一个概念,定义在函数之上——因此术语“方法”和“函数”可以互换使用。
  • 谢谢。你也可以使用 Scala 的“函数对象”吗?
  • 当然:val add: Int => Int => Int = _ + _; val increment: Int => Int = add(1)
  • 否——这些是 Function* 特征的实现,它们都有一个参数列表。据我所知,scala 中唯一用于编码零参数列表函数与零参数函数的语言结构是方法和参数。
【解决方案2】:

关于 ML 定期出现的问题的一个好处是有定期的答案。

谁能抗拒一个叫做“我们怎么了?”的话题

https://groups.google.com/forum/#!topic/scala-debate/h2Rej7LlB2A

发件人:martin odersky 日期:2012 年 3 月 2 日星期五 12:13 PM 主题:回复:[scala-debate] 我们怎么了...

有些人认为“我们错了”的是我们正在尝试弯曲 倒退以使 Java 习惯用法在 Scala 中顺利运行。这 主要的事情是说 def length() 和 def length 是不同的,而且,对不起,String 是一个 Java 类,所以你必须写 s.length(),而不是 s.length。我们非常努力地通过 允许从 s.length 到 s.length() 的自动转换。那是 确实有问题。概括,以便识别两者 在类型系统中将是注定要失败的肯定方式。那你怎么办 消除歧义:

type Action = () => () def foo: Action

那么 foo 的类型是 Action 还是 ()?那么 foo() 呢?

马丁

我最喜欢那个帖子中的保罗小说:

On Fri, Mar 2, 2012 at 10:15 AM, Rex Kerr <ich...@gmail.com> wrote:

>This would leave you unable to distinguish between the two with 
>structural types, but how often is the case when you desperately 
>want to distinguish the two compared to the case where distinguishing 
>between the two is a hassle?


/** Note to maintenance programmer: It is important that this method be
 *  callable by classes which have a 'def foo(): Int' but not by classes which
 *  merely have a 'def foo: Int'.  The correctness of this application depends
 *  on maintaining this distinction.
 *  
 *  Additional note to maintenance programmer: I have moved to zambia.
 *  There is no forwarding address.  You will never find me.
 */
def actOnFoo(...)

因此,该功能的潜在动机是生成这种 ML 线程。

再来一点谷歌学:

2010 年 4 月 1 日星期四晚上 8:04,Rex Kerr 写道: 2010 年 4 月 1 日星期四下午 1:00,richard emberson 写道:

我假设 "def getName: String" 与 "def getName(): String" 相同

不,实际上,它们不是。即使他们都调用了一个方法 没有参数,一个是“零参数列表的方法”,而 另一种是“具有一个空参数列表的方法”。如果你想 更加困惑,尝试 def getName()(): String (并创建一个 具有该签名的类)!

Scala 将参数表示为列表的列表,而不仅仅是列表,并且

列表()!=列表(列表())

这是一种奇怪的烦恼,尤其是因为很少 否则两者之间的区别,因为两者都可以 自动变成函数签名() => String.

没错。事实上,无参数方法和 具有空参数列表的方法完全是由于 Java 互操作。 它们应该是不同的,但是处理 Java 方法将是 太痛苦了。你能想象每个都必须写 str.length() 你取一个字符串的长度的时间?

干杯

【讨论】:

    【解决方案3】:

    首先,() =&gt; X=&gt; X 与无参数方法完全无关。

    现在,写这样的东西看起来很傻:

    var x() = 5
    val y() = 2
    x() = x() + y()
    

    现在,如果您不遵循上述与无参数方法有关的内容,那么您应该查找统一访问原则。以上都是方法声明,都可以用def代替。也就是说,假设您删除了它们的括号。

    【讨论】:

    • 好的,读了8遍,思考了10分钟,我想我明白你的意思了!这样如果我们有一个def toFoo,我们可以用val toFoo 覆盖它,你不能用toFoo() 访问它。一种解决方案可能是让括号对 vals/vars 成为可选,就像它们对方法一样。
    • 这看起来很傻,但它比val b = 5 可以覆盖def b(): Int 更傻吗,后者而不是前者将匹配B &lt;: { def b(): Int },除非你将子类转换回超类? LSP 似乎在这里被淘汰了。
    • @LuigiPlinge 这不仅仅是将defval 子类化——它具有重写使用varvaldef 的类的能力,改变了在不破坏二进制兼容性的情况下实现。
    • 这里有一个关于 Scala 实现 uniform access principle 的教程,我觉得很有帮助。
    【解决方案4】:

    除了提到的约定事实(副作用与非副作用)之外,它还有助于解决以下几种情况:

    空括号的用处

    // short apply syntax
    
    object A {
      def apply() = 33
    }
    
    object B {
      def apply   = 33
    }
    
    A()   // works
    B()   // does not work
    
    // using in place of a curried function
    
    object C {
      def m()() = ()
    }
    
    val f: () => () => Unit = C.m
    

    没有父母的用处

    // val <=> def, var <=> two related defs
    
    trait T { def a:   Int; def a_=(v: Int): Unit }
    trait U { def a(): Int; def a_=(v: Int): Unit }
    
    def tt(t: T): Unit = t.a += 1  // works
    def tu(u: U): Unit = u.a += 1  // does not work
    
    // avoiding clutter with apply the other way round
    
    object D {
      def a   = Vector(1, 2, 3)
      def b() = Vector(1, 2, 3)
    }
    
    D.a(0)  // works
    D.b(0)  // does not work
    
    // object can stand for no-paren method
    
    trait E
    trait F { def f:   E }
    trait G { def f(): E }
    
    object H extends F {
      object f extends E  // works
    }
    
    object I extends G {
      object f extends E  // does not work
    }
    

    因此,就语言的规律性而言,区分是有意义的(尤其是对于最后显示的情况)。

    【讨论】:

    • 您已经展示了在该语言中哪些有效,哪些无效,但如果假设它们都相同,会有什么限制?在你的情况下,我不会有问题,因为没有区别。
    • (1) 主要限制是美学:丑陋和不规则。 (2)您建议平等对待空括号和无括号,并允许两者都被称为。它无法工作,因为您会产生歧义,除非您消除了在 Scala 中折叠 apply 的可能性(请记住,这用于案例类、集合等)。如果在最后一个示例中,F 被定义为trait F { def apply: E },那么H.fH.f() 是什么意思?你不知道他们应该返回E 还是F
    • Scala 已经不会崩溃 applyapply()。你需要括号来折叠后者,而前者永远不会崩溃。按名称参数不是你从f _ 得到的,def f: F 没有括号,这将是与柯里化一致的方式。只有您的 D 示例实际上显示了任何内容,然后您始终可以假设没有括号,如果该假设错误,请尝试重新解析。
    • @Rex - object Q { def apply[A] = 33 }; Q[Int] = 崩溃。请看我评论的最后一句话;您想在这里对给定的预期返回类型使用启发式算法,并在 val x = H.f 的情况下引发歧义错误吗?
    • object R { def apply[A]() = 33 }; R[Int] 也会崩溃,并且没有类型参数的 Q 示例不会调用 apply。你确定你记得现有的行为吗?
    【解决方案5】:

    我想说两者都是可能的,因为您可以使用无参数方法访问可变状态:

    class X(private var x: Int) {
      def inc() { x += 1 }
      def value = x
    }
    

    value 方法没有副作用(它只访问可变状态)。 Programming in Scala 中明确提到了这种行为:

    这种无参数方法在 Scala 中很常见。相比之下,用空括号定义的方法,例如 def height(): Int,称为空括号方法。推荐的约定是在没有参数时使用无参数方法,并且该方法仅通过读取包含对象的字段来访问可变状态(特别是,它不会更改可变状态)。

    此约定支持统一访问原则 [...]

    总而言之,Scala 鼓励风格将不带参数且没有副作用的方法定义为无参数方法,即省略空括号。另一方面,你永远不应该定义一个没有括号的有副作用的方法,因为那样的话,对该方法的调用看起来就像一个字段选择。

    【讨论】:

    • 这只是一个约定。编译器根本没有帮助强制执行这一点,这使得它只比无用好一点。 (如果你指望它,比没用更糟糕。)
    • 如果强制使用空括号方法的括号会(稍微)更有意义,但由于您可以省略它们,因此该约定不能成为在规范中包含无参数方法的原因。
    猜你喜欢
    • 2020-09-02
    • 1970-01-01
    • 1970-01-01
    • 2015-01-02
    • 2013-06-07
    • 1970-01-01
    • 2012-06-02
    • 2013-07-08
    • 1970-01-01
    相关资源
    最近更新 更多