Best practices for people beginning Scala

1,066 views
Skip to first unread message

George Li

unread,
Sep 4, 2012, 12:37:03 AM9/4/12
to scala-l...@googlegroups.com
Hello,

As someone who just started learning Scala, there are a few things that seemed not straight forward. I have come up (tentatively) with the following "best practices." Any feedback from regular Scala programmers is appreciated.

1. toString vs toString()
The standard library has both method signatures scattered around. I plan to call the method by "toString" regardless of whether its signature is "toString" or "toString()". I do this with full knowledge that some library class might use the signature "toString()" because it might cache the value from a previous invocation and hence change the state of an object. I plan to use "toString" throughout because conceptually, the string representation of a class/object should be consistent. This applies to hashCode too (instead of hashCode()).

2. primary constructor without parameters.
If I am defining a class with a primary constructor that doesn't require any parameter, then I do so without a pair of parenthesis. i.e.
class Foo {

}

and NOT

class Foo() {

}

Also to create an object using the primary constructor without a parameter list, I use this notation:

val a = new Foo

and NOT

val a = new Foo()

Thanks,

Raoul Duke

unread,
Sep 4, 2012, 12:38:22 AM9/4/12
to scala-l...@googlegroups.com
On Mon, Sep 3, 2012 at 9:37 PM, George Li <glu...@gmail.com> wrote:
> to use "toString" throughout because conceptually, the string representation
> of a class/object should be consistent. This applies to hashCode too
> (instead of hashCode()).

?!?!?

Ken Scambler

unread,
Sep 4, 2012, 12:49:07 AM9/4/12
to scala-l...@googlegroups.com
A common convention is to omit braces when calling a no-arg method that's ostensibly a "property", or pure, -- in other words it has no side effects and returns the same thing every time you call it.  Conversely, braces would be deliberately used when calling impure or side-effecting no-arg methods.

So:

foo.toString
but
myPanel.repaint()

George Li

unread,
Sep 4, 2012, 12:57:23 AM9/4/12
to scala-l...@googlegroups.com
I just mean that if I have an object

val a = new SomeObject

then consecutive invocation of toString should be consistent:

a.toString == a.toString // this should be true

val b = a.toString
val c = a.toString // b should be equal to c

George Li

unread,
Sep 4, 2012, 1:04:31 AM9/4/12
to scala-l...@googlegroups.com
Yes but suppose I have to following implementation of toString() within a class

class Foo {
  var stringVal = ""

  override def toString(): String = {
    if (stringVal != "")
      stringVal
    else
      stringVal = "string representation"
      stringVal
  }
}

Then clearly, toString() has a "side effect". But I am saying that, since I don't know how toString() was implemented unless I go through the source code for Foo, I am just going to use "toString" as in:


val a = new Foo
a.toString

every single time. In other words, technically I should be using "a.toString()" since it has an "side effect", but I won't since I don't want to go through the source code.

Raoul Duke

unread,
Sep 4, 2012, 1:16:38 AM9/4/12
to scala-l...@googlegroups.com
On Mon, Sep 3, 2012 at 9:49 PM, Ken Scambler <ken.sc...@gmail.com> wrote:
> and returns the same thing every time you call it.

i don't see how toString can fit that requirement.

Ken Scambler

unread,
Sep 4, 2012, 1:20:20 AM9/4/12
to scala-l...@googlegroups.com
Then clearly, toString() has a "side effect". But I am saying that, since I don't know how toString() was implemented unless I go through the source code for Foo, I am just going to use "toString" as in:
 
Any OO method needs to take in "this" as it's first parameter, implicitly.  I would expect a well-behaved toString() method to return the same string every time for a given state of the object.

 


val a = new Foo
a.toString

Yes, that's fine.

Ken Scambler

unread,
Sep 4, 2012, 1:28:15 AM9/4/12
to scala-l...@googlegroups.com
Yes, I should have said "returns the same thing every time for a given state of the target object". 

Paul Phillips

unread,
Sep 4, 2012, 2:11:22 AM9/4/12
to scala-l...@googlegroups.com
On Mon, Sep 3, 2012 at 9:37 PM, George Li <glu...@gmail.com> wrote:
1. toString vs toString()

Practical considerations dominate.  If you define it without parens, then any call sites which call .toString() will break.  If you define it with parens, you can call it either way.  If you can't control/change all the call sites then you will be pushed toward defining it with parens.

2. primary constructor without parameters.

You realize of course that it inserts the empty parameter list for you.  If the syntactic distinction is communicating something useful to other programmers, that's good - but that's all it would be doing.

George Li

unread,
Sep 4, 2012, 2:41:34 AM9/4/12
to scala-l...@googlegroups.com
Actually, you can call "toString" in either call sites and it works fine. This is specified in Programming in Scala, 2nd Edition

Paul Phillips

unread,
Sep 4, 2012, 2:43:14 AM9/4/12
to scala-l...@googlegroups.com


On Mon, Sep 3, 2012 at 11:41 PM, George Li <glu...@gmail.com> wrote:
Actually, you can call "toString" in either call sites and it works fine. This is specified in Programming in Scala, 2nd Edition

What I said is true exactly as written.  This is specified in "scala, the implementation", all versions.

nicola...@gmail.com

unread,
Sep 4, 2012, 4:48:59 AM9/4/12
to scala-l...@googlegroups.com
I am not an expert in Scala style, but I think mutability is not the
key to put bracket or not.
Observational equivalence is.
For any purpose, the object before and after calling toString is the
same. (Even if it caches the string, you can not
observe that by its interface).
So I won't put the brackets.

ARKBAN

unread,
Sep 4, 2012, 6:41:52 AM9/4/12
to scala-l...@googlegroups.com
FYI, the Scala Style Guide has section on method declaration
http://docs.scala-lang.org/style/naming-conventions.html#parentheses and
on method invocation
http://docs.scala-lang.org/style/method-invocation.html#arity0. Both
sections echo the practice that parenthesis should be omitted only when
there are no side-effects.

ARKBAN

George Li

unread,
Sep 5, 2012, 1:32:30 AM9/5/12
to scala-l...@googlegroups.com
Hi Paul,

Maybe we have an misunderstanding. As the following code demonstrates, when if I define a toString() method but use toString to call it and vice versa, it still works in the scala interpreter.

scala> class Foo {
     |   override def toString(): String = "foo"
     | }
defined class Foo

scala> (new Foo).toString
res1: String = foo

scala> class Foo {
     |   override def toString: String = "foo"
     | }
defined class Foo

scala> (new Foo).toString()
res2: String = foo

So I don't understand your statement, "If you define it without parens, then any call sites which call .toString() will break."

Could you explain what you mean?

Thanks,

Paul Phillips

unread,
Sep 5, 2012, 1:44:12 AM9/5/12
to scala-l...@googlegroups.com


On Tue, Sep 4, 2012 at 10:32 PM, George Li <glu...@gmail.com> wrote:
Could you explain what you mean?

I had this crazy notion we had uniform access... I apologize.  I was thinking of this, and now I'm not sure if I should be happy or sad that the nullary method and the val have different behavior.

  class A { override val toString = "foo" }

Here are some non-toString situations to consider.

class C {  def foo: () => Int = () => 1 }
class D extends C { override def foo(): () => Int = () => 2 }

scala> (new D).foo
res0: () => Int = <function0>

scala> (new D).foo()
res1: () => Int = <function0>

scala> (new D).foo()()
res2: Int = 2

scala> (new C).foo
res3: () => Int = <function0>

scala> (new C).foo()
res4: Int = 1

scala> ((new D): C).foo()
res5: Int = 2

George Li

unread,
Sep 5, 2012, 2:29:17 AM9/5/12
to scala-l...@googlegroups.com
These examples are really interesting, especially the last one.

scala> ((new D): C).foo()
res5: Int = 2

You are casting a new D instance into a C instance and calling foo() on it. The dynamic method lookup (if you will allow me to use this phrase from Java) finds the appropriate foo() function but treats it as if its signature is:

def foo: () => int = () => 2

This is indeed interesting.

Daniel Sobral

unread,
Sep 5, 2012, 7:56:30 AM9/5/12
to scala-l...@googlegroups.com
On Wed, Sep 5, 2012 at 2:32 AM, George Li <glu...@gmail.com> wrote:
> Hi Paul,
>
> Maybe we have an misunderstanding. As the following code demonstrates, when
> if I define a toString() method but use toString to call it and vice versa,

He said "if you define it *without* parens". You are not defining
without parens, are you? His second reply to you was a hint for you to
go back and re-read what he said. Since you are still fumbling around,
just type this in the REPL:

class Foo { override def toString: String = "foo" }
(new Foo).toString()

That way lies enlightenment. :-)

> it still works in the scala interpreter.
>
> scala> class Foo {
> | override def toString(): String = "foo"
> | }
> defined class Foo
>
> scala> (new Foo).toString
> res1: String = foo
>
> scala> class Foo {
> | override def toString: String = "foo"
> | }
> defined class Foo
>
> scala> (new Foo).toString()
> res2: String = foo
>
> So I don't understand your statement, "If you define it without parens, then
> any call sites which call .toString() will break."
>
> Could you explain what you mean?
>
> Thanks,
>
> On Monday, September 3, 2012 11:43:16 PM UTC-7, Paul Phillips wrote:
>>
>>
>>
>> On Mon, Sep 3, 2012 at 11:41 PM, George Li <glu...@gmail.com> wrote:
>>>
>>> Actually, you can call "toString" in either call sites and it works fine.
>>> This is specified in Programming in Scala, 2nd Edition
>>
>>
>> What I said is true exactly as written. This is specified in "scala, the
>> implementation", all versions.
>>
>



--
Daniel C. Sobral

I travel to the future all the time.

Olek Swirski

unread,
Sep 6, 2012, 9:47:10 AM9/6/12
to scala-l...@googlegroups.com
Hi, I'm not sure if I understand well. Could someone simply state
weather () have any practical meaning except from how they
look in the source code (visually signaling side effect). In my REPL
it seems to make no difference if I use () or not I can call the method
also either way it still works.

Welcome to Scala version 2.9.1-1 (OpenJDK Client VM, Java 1.7.0_03).
Type in expressions to have them evaluated.
Type :help for more information.

scala> class Foo{override def toString:String = "foo"}
defined class Foo

scala> (new Foo).toString()
res0: String = foo

scala> (new Foo).toString
res1: String = foo

scala> class Bar{override def toString():String="bar"}
defined class Bar

scala> (new Bar).toString()
res2: String = bar

scala> (new Bar).toString
res3: String = bar

Daniel Sobral

unread,
Sep 6, 2012, 10:41:33 AM9/6/12
to scala-l...@googlegroups.com

Ah, I see. This method is defined by java, so it's toString() even if you override it with parens. Try some other method.

Olek Swirski

unread,
Sep 6, 2012, 11:48:17 AM9/6/12
to scala-l...@googlegroups.com
ok, I made small test and the result is that, when we have inheritance and
Foo is extended by two classes Bar and Baz, if the method, for ex. If the
method in Foo, is hello vs hello() it makes some slight difference how it
works. In case of using hello in Foo, we can't call hello() from it and also
not by a class overriding hello. But if the child class overrides hello() instead,
it makes both hello and hello() available in child class, and always the version
from child class is called.
Second situation I defined hello() in Foo. In this case both hello and hello()
calls are available in Foo. Also in child classes weather we override hello
or hello() this gives same result of totally shadowing parent class definition
of hello(). What I didn't expect was that if hello() is defined in parent class
then I will be able to call hello() in child class, and even though child does
not override hello() but hello only, calling hello() on it will effectively call
this child's hello, and not parent's hello(). And if parent would not define
hello() but hello then I wouldn't be able to call hello() at all in child and it
would not point to child's hello.


Welcome to Scala version 2.9.1-1 (OpenJDK Client VM, Java 1.7.0_03).
Type in expressions to have them evaluated.
Type :help for more information.

scala> class Foo{def hello = "hello foo"}
defined class Foo

scala> (new Foo).hello()
<console>:9: error: not enough arguments for method apply: (index: Int)Char in class StringOps.
Unspecified value parameter index.
              (new Foo).hello()
                             ^

scala> (new Foo).hello
res1: java.lang.String = hello foo

scala> class Bar extends Foo{ override def hello = "hello bar" }
defined class Bar

scala> (new Bar).hello()
<console>:10: error: not enough arguments for method apply: (index: Int)Char in class StringOps.
Unspecified value parameter index.
              (new Bar).hello()
                             ^

scala> (new Bar).hello
res3: java.lang.String = hello bar

scala> class Baz extends Foo{ override def hello() = "hello baz" }
defined class Baz

scala> (new Baz).hello()
res4: java.lang.String = hello baz

scala> (new Baz).hello
res5: java.lang.String = hello baz

scala> "now let's try defining hello() instead of hello in the parent class"
res6: java.lang.String = now let's try defining hello() instead of hello in the parent class

scala> class Foo{ def hello() = "hello foo" }
defined class Foo

scala> class Bar extends Foo{ override def hello = "hello bar" }
defined class Bar

scala> (new Bar).hello()
res7: java.lang.String = hello bar

scala> (new Bar).hello
res8: java.lang.String = hello bar

scala> class Baz extends Foo{ override def hello() = "hello baz" }
defined class Baz

scala> (new Baz).hello()
res9: java.lang.String = hello baz

scala> (new Baz).hello
res10: java.lang.String = hello baz

Paolo Giarrusso

unread,
Sep 6, 2012, 5:05:25 PM9/6/12
to scala-l...@googlegroups.com


Il giorno mercoledì 5 settembre 2012 07:44:15 UTC+2, Paul Phillips ha scritto:


On Tue, Sep 4, 2012 at 10:32 PM, George Li <glu...@gmail.com> wrote:
Could you explain what you mean?

I had this crazy notion we had uniform access... I apologize.  I was thinking of this, and now I'm not sure if I should be happy or sad that the nullary method and the val have different behavior.

  class A { override val toString = "foo" }

Java's toString is declared with brackets, so that's an exception. Normally you do have uniform access:

scala> class Foo{def bar = 1}
defined class Foo

scala> (new Foo).bar
res2: Int = 1

scala> (new Foo).bar()
<console>:9: error: Int does not take parameters
              (new Foo).bar()
                           ^
 
Here are some non-toString situations to consider.

class C {  def foo: () => Int = () => 1 }
class D extends C { override def foo(): () => Int = () => 2 }
scala> (new D).foo()
res1: () => Int = <function0>

scala> (new C).foo()
res4: Int = 1

This difference of behavior is not unsound (given the example below) so it technically does not violate the Liskov substitution principle, but it still feels bad enough that this overloading should be disallowed - especially since D.foo is supposed to override C.foo (and it does). WDYT?

Paolo Giarrusso

unread,
Sep 6, 2012, 5:08:13 PM9/6/12
to scala-l...@googlegroups.com


Il giorno giovedì 6 settembre 2012 17:48:25 UTC+2, Olek Swirski ha scritto:
ok, I made small test
 
scala> class Foo{def hello = "hello foo"}

defined class Foo

scala> (new Foo).hello()
<console>:9: error: not enough arguments for method apply: (index: Int)Char in class StringOps.
Unspecified value parameter index.
              (new Foo).hello()
                             ^
That's because (thanks to implicit conversions) String has a method apply(index: Int), so String is not good to test this behavior. You should redo your experiment with a method returning, say, Int (which has no apply method AFAIK). A starter is below, I have to go otherwise I'd complete it:


scala> class Foo{def bar = 1}
defined class Foo

Paul Phillips

unread,
Sep 6, 2012, 5:08:26 PM9/6/12
to scala-l...@googlegroups.com


On Thu, Sep 6, 2012 at 2:05 PM, Paolo Giarrusso <p.gia...@gmail.com> wrote:
This difference of behavior is not unsound (given the example below) so it technically does not violate the Liskov substitution principle, but it still feels bad enough that this overloading should be disallowed - especially since D.foo is supposed to override C.foo (and it does). WDYT?

If you like warnings, throw in -Xlint and it'll warn you.
Reply all
Reply to author
Forward
0 new messages