Why does Scala need parameterless in addition to zero-parameter methods?

前端 未结 5 1209
猫巷女王i
猫巷女王i 2020-12-04 17:58

I understand the difference between zero-parameter and parameterless methods, but what I don\'t really understand is the language design choice that made parameterless metho

相关标签:
5条回答
  • 2020-12-04 18:30

    First off, () => X and => X has absolutely nothing to do with parameterless methods.

    Now, it looks pretty silly to write something like this:

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

    Now, if you don't follow what the above has to do with parameterless methods, then you should look up uniform access principle. All of the above are method declarations, and all of them can be replaced by def. That is, assuming you remove their parenthesis.

    0 讨论(0)
  • 2020-12-04 18:33

    I would say both are possible because you can access mutable state with a parameterless method:

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

    The method value does not have side effects (it only accesses mutable state). This behavior is explicitly mentioned in Programming in Scala:

    Such parameterless methods are quite common in Scala. By contrast, methods defined with empty parentheses, such as def height(): Int, are called empty-paren methods. The recommended convention is to use a parameterless method whenever there are no parameters and the method accesses mutable state only by reading fields of the containing object (in particular, it does not change mutable state).

    This convention supports the uniform access principle [...]

    To summarize, it is encouraged style in Scala to define methods that take no parameters and have no side effects as parameterless methods, i.e., leaving off the empty parentheses. On the other hand, you should never define a method that has side-effects without parentheses, because then invocations of that method would look like a field selection.

    0 讨论(0)
  • 2020-12-04 18:35

    One nice thing about an issue coming up periodically on the ML is that there are periodic answers.

    Who can resist a thread called "What is wrong with us?"

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

    From: martin odersky Date: Fri, Mar 2, 2012 at 12:13 PM Subject: Re: [scala-debate] what is wrong with us...

    What some people think is "wrong with us" is that we are trying bend over backwards to make Java idioms work smoothly in Scala. The principaled thing would have been to say def length() and def length are different, and, sorry, String is a Java class so you have to write s.length(), not s.length. We work really hard to paper over it by admitting automatic conversions from s.length to s.length(). That's problematic as it is. Generalizing that so that the two are identified in the type system would be a sure way to doom. How then do you disambiguate:

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

    Is then foo of type Action or ()? What about foo()?

    Martin

    My favorite bit of paulp fiction from that thread:

    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(...)
    

    So the underlying motivation for the feature is to generate this sort of ML thread.

    One more bit of googlology:

    On Thu, Apr 1, 2010 at 8:04 PM, Rex Kerr <[hidden email]> wrote: On Thu, Apr 1, 2010 at 1:00 PM, richard emberson <[hidden email]> wrote:

    I assume "def getName: String" is the same as "def getName(): String"

    No, actually, they are not. Even though they both call a method without parameters, one is a "method with zero parameter lists" while the other is a "method with one empty parameter list". If you want to be even more perplexed, try def getName()(): String (and create a class with that signature)!

    Scala represents parameters as a list of lists, not just a list, and

    List() != List(List())

    It's kind of a quirky annoyance, especially since there are so few distinctions between the two otherwise, and since both can be automatically turned into the function signature () => String.

    True. In fact, any conflation between parameterless methods and methods with empty parameter lists is entirely due to Java interop. They should be different but then dealing with Java methods would be just too painful. Can you imagine having to write str.length() each time you take the length of a string?

    Cheers

    0 讨论(0)
  • 2020-12-04 18:39

    Besides the convention fact mentioned (side-effect versus non-side-effect), it helps with several cases:

    Usefulness of having empty-paren

    // 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
    

    Usefulness of having no-paren

    // 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
    }
    

    Thus in terms of regularity of the language, it makes sense to have the distinction (especially for the last shown case).

    0 讨论(0)
  • 2020-12-04 18:46

    Currying, That's Why

    Daniel did a great job at explaining why parameterless methods are necessary. I'll explain why they are regarded distinctly from zero-parameter methods.

    Many people view the distinction between parameterless and zero-parameter functions as some vague form of syntactic sugar. In truth it is purely an artifact of how Scala supports currying (for completeness, see below for a more thorough explanation of what currying is, and why we all like it so much).

    Formally, a function may have zero or more parameter lists, with zero or more parameters each.
    This means the following are valid: def a, def b(), but also the contrived def c()() and def d(x: Int)()()(y: Int) etc...

    A function def foo = ??? has zero parameter lists. A function def bar() = ??? has precisely one parameter list, with zero parameters. Introducing additional rules that conflate the two forms would have undermined currying as a consistent language feature: def a would be equivalent in form to def b() and def c()() both; def d(x: Int)()()(y: Int) would be equivalent to def e()(x: Int)(y: Int)()().

    One case where currying is irrelevant is when dealing with Java interop. Java does not support currying, so there's no problem with introducing syntactic sugar for zero-parameter methods like "test".length() (which directly invokes java.lang.String#length()) to also be invoked as "test".length.

    A quick explanation of currying

    Scala supports a language feature called 'currying', named after mathematician Haskell Curry.
    Currying allows you to define functions with several parameter lists, e.g.:

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

    This is useful, because you can now define inc in terms of a partial application of add:

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

    Currying is most often seen as a way of introducing control structures via libraries, e.g.:

    def repeat(n: Int)(thunk: => Any): Unit = (1 to n) foreach { _ => thunk }
    
    repeat(2) {
      println("Hello, world")
    }
    
    // Hello, world
    // Hello, world
    

    As a recap, see how repeat opens up another opportunity to use currying:

    def twice: (=> Any) => Unit = repeat(2)
    
    twice {
      println("Hello, world")
    }
    
    // ... you get the picture :-)
    
    0 讨论(0)
提交回复
热议问题