0

I'm trying to understand the following class declaration from the source code of Apache Spark:

case class HadoopFsRelation(location: FileIndex, ...)(val sparkSession: SparkSession)

I am wondering why it has the second constructor which takes SparkSession.

I just wrote some examples to clarify some points:

class Person(firstName: String)(lastName: String)(val age: Int) {
  def tellMeAboutYourSelf() = {
   //basically I have access to all three arguments
   s"$firstName, $lastName - $age"
  }
}

object Main extends App {
//no errors are shown, though it fails during compilation:
//missing argument lis for constructor Person
val singleArg: Person = new Person("Harry")

//surprisingly no errors are shown but it also fails during compilation:
//Person does not take parameters
val twoArgsCurrying: Person = singleArg("Potter")

//no errors, and the same compilation failure
//missing argument list for constructor Person
//a pure expression does nothing in statement position; you may be omitting necessary parentheses
val harry: Person = new Person("Harry")("Potter"){100}

//finally this works
val harry: Person = new Person("Harry")("Potter")(100)
println(harry.tellMeAboutYourSelf())

}

So the question what's the point in using such construction and why it is allowed in Scala?

Gelerion
  • 1,634
  • 10
  • 17
  • You are only showing the primary constructor. If you have a question about the second constructor, you need to show it! Hint: secondary constructors will always start with `def this`. – Jörg W Mittag Feb 24 '19 at 14:24
  • Thanks, I know Scala, but I am quite confused with such declaration of primary constructor. Why anyone would split it like so, I am interested in reasoning. – Gelerion Feb 24 '19 at 14:45
  • Ah, you don't mean a second constructor, you mean a single constructor with a second parameter list? That was not clear from the way you phrased the question, where you explicitly talk about a "second constructor" and "separate constructors" (plural). – Jörg W Mittag Feb 24 '19 at 14:55
  • Yes, that's exactly what I meant, sorry. I didn't know how to express it in the right way ) – Gelerion Feb 24 '19 at 15:02

2 Answers2

5

Unlike most other programming languages, which allow only one parameter list (or, in the case of Haskell, for example, even only a single parameter), Scala allows multiple parameter lists each with multiple parameters.

There are several reasons to use multiple parameter lists:

  • If a parameter list has only single parameter, then the argument corresponding to this parameter can be passed in curly braces as a "block", which is especially useful in the case where this parameter is a function or partial function.
  • Multiple parameter lists are Scala's approach to currying, i.e. you can leave out the last argument list and you get a partially-applied function.
  • Type inference flows from left to right between parameter lists, but not within a parameter list. Therefore, separating parameter lists can help type inference and reduce the amount of required type annotations.
  • Identifiers introduced in a parameter list are in scope in parameter lists to the right. This allows you to use path-dependent method types where the type of a parameter in a later parameter list depends on the value of an argument in an earlier argument list.
  • Only an entire parameter list can be marked implicit. If you want some parameters to be explicit and some to be implicit, you need to separate the implicit ones out into a separate parameter list.
  • Only one parameter in a parameter list can be a repeated parameter. If you want multiple repeated parameters, you need to separate them out into separate parameters lists.

This one only applies to constructors, more specifically only to primary constructors, and even more specifically only to primary constructors of case classes:

  • The parameters of the first parameter list are treated "special":
    • They are automatically public vals with accessors.
    • They are used to generate a sensible default implementation of ##
    • They are used to generate a sensible default implementation of ==
    • A case class will extend ProductN[A, … Z] where A, … Z are the types of the parameters of the first parameter list.
    • There will be an automatically generated companion object of the same name, which extends FunctionN[A, … Z] and has an apply method whose sole parameter list is the same as the first parameter list of the primary constructor of the case class.

So, if you do not want any of that for one of your parameters, you need to put that parameter into a separate parameter list.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
4

This is not different constructors. This is a single constructor with multiple parameter lists.

https://docs.scala-lang.org/tour/multiple-parameter-lists.html

Multiple parameter lists in a method are mostly for currying/partial application.

For a case class parameters in the first list are vals (i.e. accessible outside the class) automatically.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • I do understand the use of multiple parameters for methods, but why it is allowed to split primary constructor in such a way? Which benefits does it give? It can't be curried as I showed in the example. – Gelerion Feb 24 '19 at 14:41
  • 1
    Reasons are the same. Currying/partial application. `val function: SparkSession => HadoopFsRelation = HadoopFsRelation(??? : FileIndex, ??? : StructType, ??? : StructType, ??? : Option[BucketSpec], ??? : FileFormat, ??? : Map[String, String])` `val sparkSession: SparkSession = ???` `function(sparkSession)` – Dmytro Mitin Feb 24 '19 at 14:46
  • Ok, thank you. This actually does work only for case classes. person: Int => Person = new Person("Harry")("Potter") leads to compile error – Gelerion Feb 24 '19 at 14:55
  • 1
    One more reason is using path-dependent types (but not in your case) https://stackoverflow.com/questions/29043554/case-class-constructor-argument-type-depending-on-the-previous-argument-value – Dmytro Mitin Feb 24 '19 at 14:55
  • You can create primary constructor with multiple parameter lists not only for case class. – Dmytro Mitin Feb 24 '19 at 14:57
  • `class Person(firstName: String)(lastName: String)(val age: Int)` `val function: String => Int => Person = (new Person(("Harry"))) _` `val function1: Int => Person = function("Potter")` `function1(42)` – Dmytro Mitin Feb 24 '19 at 15:01
  • 1
    The third reason is using implicits in constructor (again not your case). – Dmytro Mitin Feb 24 '19 at 15:02
  • In your second example I see a compile error: Expression of type Person doesn't conform to expected type String => Int => Person – Gelerion Feb 24 '19 at 15:05
  • You missed underscore. – Dmytro Mitin Feb 24 '19 at 15:05
  • Ok, got it. Thank you very much. – Gelerion Feb 24 '19 at 15:10
  • Actually maybe you're right. This works with case classes. – Dmytro Mitin Feb 24 '19 at 15:10
  • More precisely, this (underscore) works with methods (e.g. `apply`) and not `new`. – Dmytro Mitin Feb 24 '19 at 15:14