From c53cf2f757fefffc7fef9f36bb46ad39b0b3ec69 Mon Sep 17 00:00:00 2001 From: Travis Lee Date: Fri, 17 Feb 2017 14:57:57 +0100 Subject: [PATCH 1/2] rewrote singleton objects tour --- _tour/singleton-objects.md | 94 ++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 35 deletions(-) diff --git a/_tour/singleton-objects.md b/_tour/singleton-objects.md index af76b4f90f..95d2876ccb 100644 --- a/_tour/singleton-objects.md +++ b/_tour/singleton-objects.md @@ -10,67 +10,91 @@ num: 13 next-page: regular-expression-patterns previous-page: pattern-matching - redirect_from: "/tutorials/tour/singleton-objects.html" +prerequisite-knowledge: classes, methods, private-methods, packages, option --- -Methods and values that aren't associated with individual instances of a [class](classes.html) belong in *singleton objects*, denoted by using the keyword `object` instead of `class`. +A singleton object is an instance of a new class. There is exactly one instance of each singleton object. They do not have constructors so they cannot be instantiated. +# Defining a singleton object +The simplest form of an object is the keyword `object` and an identifier: +```tut +object Box ``` -package test -object Blah { - def sum(l: List[Int]): Int = l.sum -} +Here's an example of an object with a method: ``` +package logging -This `sum` method is available globally, and can be referred to, or imported, as `test.Blah.sum`. - -Singleton objects are sort of a shorthand for defining a single-use class, which can't directly be instantiated, and a `val` member at the point of definition of the `object`, with the same name. Indeed, like `val`s, singleton objects can be defined as members of a [trait](traits.html) or class, though this is atypical. - -A singleton object can extend classes and traits. In fact, a [case class](case-classes.html) with no [type parameters](generic-classes.html) will by default create a singleton object of the same name, with a [`Function*`](http://www.scala-lang.org/api/current/scala/Function1.html) trait implemented. +object Logger { + def info(message: String): Unit = println(s"INFO: $message") +} +``` +The method `info` can be imported from anywhere in the program. Creating utility methods like this is a common use case for singleton objects (however, more sophisticated logging techniques exist). Let's see how to use `info` in another package: -## Companions ## +``` +import logging.Logger.info -Most singleton objects do not stand alone, but instead are associated with a class of the same name. The “singleton object of the same name” of a case class, mentioned above, is an example of this. When this happens, the singleton object is called the *companion object* of the class, and the class is called the *companion class* of the object. +class Project(name: String, daysToComplete: Int) -[Scaladoc]({{ site.baseurl }}/style/scaladoc.html) has special support for jumping between a class and its companion: if the big “C” or “O” circle has its edge folded up at the bottom, you can click the circle to jump to the companion. +val project1 = new Project("TPS Reports", 1) +val project2 = new Project("Website redesign", 5) +info("Created projects") // Prints "INFO: Created projects" +``` -A class and its companion object, if any, must be defined in the same source file. Like this: +The `info` method becomes visible in the scope of the package using `import logging.Logger.info`. You could also use `import logging.Logger._` to import everything from Logger. -```tut -class IntPair(val x: Int, val y: Int) +Note: If an `object` is nested within another construct such as a class, it is only a singleton in that context. This means there could be an `object NutritionInfo` in the `class Milk` and in the `class OrangeJuice`. -object IntPair { - import math.Ordering +## Companion objects - implicit def ipord: Ordering[IntPair] = - Ordering.by(ip => (ip.x, ip.y)) -} +A singleton object with the same name as a class is called a _companion object_. Conversely, the class is the object's companion class. The companion class and object can access each other's private members. Use a companion object for methods and values which are not specific to instances of the companion class. ``` +import scala.math._ -It's common to see typeclass instances as [implicit values](implicit-parameters.html), such as `ipord` above, defined in the companion, when following the typeclass pattern. This is because the companion's members are included in the default implicit search for related values. - -## Notes for Java programmers ## +class Circle(val radius: Double) { + def area: Double = Circle.calculateArea(radius) +} -`static` is not a keyword in Scala. Instead, all members that would be static, including classes, should go in a singleton object. They can be referred to with the same syntax, imported piecemeal or as a group, and so on. +object Circle { + def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0) +} -Frequently, Java programmers define static members, perhaps `private`, as implementation aids for their instance members. These move to the companion, too; a common pattern is to import the companion object's members in the class, like so: +val circle1 = new Circle(5.0) +circle1.area ``` -class X { - import X._ - def blah = foo +The `class Circle` contains the val `radius` which is specific to each instance whereas the `object Circle` contains the method `calculateArea` which is the same for every instance. + +The companion object can also contain factory methods: +```tut +class Email(val username: String, val domainName: String) + +object Email { + def fromString(emailString: String): Option[Email] = { + emailString.split('@') match { + case Array(a, b) => Some(new Email(a, b)) + case _ => None + } + } } -object X { - private def foo = 42 +val scalaCenterEmail = Email.fromString("scala.center@epfl.ch") +scalaCenterEmail match { + case Some(email) => println( + s"""Registered an email + |Username: ${email.username} + |Domain name: ${email.domainName} + """) + case None => println("Error: could not parse email") } ``` +The `object Email` contains a factory `fromString` which creates an `Email` instance from a String. We return it as an `Option[Email]` in case of parsing errors. -This illustrates another feature: in the context of `private`, a class and its companion are friends. `object X` can access private members of `class X`, and vice versa. To make a member *really* private to one or the other, use `private[this]`. +Note: If a class or object has a companion, both must be defined in the same file. To define them in the REPL, you must enter `:paste` and then paste in the class and companion object code. -For Java convenience, methods, including `var`s and `val`s, defined directly in a singleton object also have a static method defined in the companion class, called a *static forwarder*. Other members are accessible via the `X$.MODULE$` static field for `object X`. +## Notes for Java programmers ## -If you move everything to a companion object and find that all you have left is a class you don't want to be able to instantiate, simply delete the class. Static forwarders will still be created. +`static` is not a keyword in Scala. Instead, all members that would be static, including classes, should go in a singleton object instead. +When using a companion object from Java code, the members will be defined in a companion class with a `static` modifier. This is called _static forwarding_. It occurs even if you haven't defined a companion class yourself. From e0282689daf19fb2b5141f481beff6805a916dd2 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 10 Jan 2018 03:49:57 -0800 Subject: [PATCH 2/2] Tweak the text. --- _tour/singleton-objects.md | 40 ++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/_tour/singleton-objects.md b/_tour/singleton-objects.md index 95d2876ccb..2e05c851f2 100644 --- a/_tour/singleton-objects.md +++ b/_tour/singleton-objects.md @@ -13,11 +13,13 @@ previous-page: pattern-matching redirect_from: "/tutorials/tour/singleton-objects.html" prerequisite-knowledge: classes, methods, private-methods, packages, option --- +An object is a class that has exactly one instance. It is created lazily when it is referenced, like a lazy val. -A singleton object is an instance of a new class. There is exactly one instance of each singleton object. They do not have constructors so they cannot be instantiated. +As a top-level value, an object is a singleton. +As a member of an enclosing class or as a local value, it behaves exactly like a lazy val. # Defining a singleton object -The simplest form of an object is the keyword `object` and an identifier: +An object is a value. The definition of an object looks like a class, but uses the keyword `object`: ```tut object Box ``` @@ -30,34 +32,41 @@ object Logger { def info(message: String): Unit = println(s"INFO: $message") } ``` -The method `info` can be imported from anywhere in the program. Creating utility methods like this is a common use case for singleton objects (however, more sophisticated logging techniques exist). Let's see how to use `info` in another package: +The method `info` can be imported from anywhere in the program. Creating utility methods like this is a common use case for singleton objects. + +Let's see how to use `info` in another package: ``` import logging.Logger.info class Project(name: String, daysToComplete: Int) -val project1 = new Project("TPS Reports", 1) -val project2 = new Project("Website redesign", 5) -info("Created projects") // Prints "INFO: Created projects" +class Test { + val project1 = new Project("TPS Reports", 1) + val project2 = new Project("Website redesign", 5) + info("Created projects") // Prints "INFO: Created projects" +} ``` -The `info` method becomes visible in the scope of the package using `import logging.Logger.info`. You could also use `import logging.Logger._` to import everything from Logger. +The `info` method is visible because of the import statement, `import logging.Logger.info`. -Note: If an `object` is nested within another construct such as a class, it is only a singleton in that context. This means there could be an `object NutritionInfo` in the `class Milk` and in the `class OrangeJuice`. +Imports require a "stable path" to the imported symbol, and an object is a stable path. + +Note: If an `object` is not top-level but is nested in another class or object, then the object is "path-dependent" like any other member. This means that given two kinds of beverages, `class Milk` and `class OrangeJuice`, a class member `object NutritionInfo` "depends" on the enclosing instance, either milk or orange juice. `milk.NutritionInfo` is entirely distinct from `oj.NutritionInfo`. ## Companion objects -A singleton object with the same name as a class is called a _companion object_. Conversely, the class is the object's companion class. The companion class and object can access each other's private members. Use a companion object for methods and values which are not specific to instances of the companion class. +An object with the same name as a class is called a _companion object_. Conversely, the class is the object's companion class. A companion class or object can access the private members of its companion. Use a companion object for methods and values which are not specific to instances of the companion class. ``` import scala.math._ -class Circle(val radius: Double) { - def area: Double = Circle.calculateArea(radius) +case class Circle(radius: Double) { + import Circle._ + def area: Double = calculateArea(radius) } object Circle { - def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0) + private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0) } val circle1 = new Circle(5.0) @@ -65,7 +74,7 @@ val circle1 = new Circle(5.0) circle1.area ``` -The `class Circle` contains the val `radius` which is specific to each instance whereas the `object Circle` contains the method `calculateArea` which is the same for every instance. +The `class Circle` has a member `area` which is specific to each instance, and the singleton `object Circle` has a method `calculateArea` which is available to every instance. The companion object can also contain factory methods: ```tut @@ -92,9 +101,10 @@ scalaCenterEmail match { ``` The `object Email` contains a factory `fromString` which creates an `Email` instance from a String. We return it as an `Option[Email]` in case of parsing errors. -Note: If a class or object has a companion, both must be defined in the same file. To define them in the REPL, you must enter `:paste` and then paste in the class and companion object code. +Note: If a class or object has a companion, both must be defined in the same file. To define companions in the REPL, either define them on the same line or enter `:paste` mode. ## Notes for Java programmers ## -`static` is not a keyword in Scala. Instead, all members that would be static, including classes, should go in a singleton object instead. +`static` members in Java are modeled as ordinary members of a companion object in Scala. + When using a companion object from Java code, the members will be defined in a companion class with a `static` modifier. This is called _static forwarding_. It occurs even if you haven't defined a companion class yourself.