9

Assume that we have a class constructor that takes parameters that have default value.

class A(val p1 : Int = 3, val p2 : Int = 4) 

Let's say I don't have control over this class and can't modify it in anyway. What I want to do is to call A's constructor with p1 = 5, p2 = (if condition1 == true then 5 else default value). One way to do this is

if(condition1)
  x = new A(5,5)
else
  x = new A(5)

As you can see, this can easily get big if there are many parameters and each must be supplied conditionally. What I want is something like

x = new A(p1 = 5, p2 = <if condition1 = true then 5 else default>)

How can I do that? Note that the fields in class A are vals, so I cant change them after instantiating A.

om-nom-nom
  • 62,329
  • 13
  • 183
  • 228
DSR
  • 185
  • 1
  • 2
  • Is it expensive to create a throwaway `A` without arguments so that you can read all its default values and use them later in `if` expressions? – Ionuț G. Stan Oct 17 '13 at 17:10
  • I thought about that solution. It's not expensive but feels a little hacky. I'd like to avoid first reading default parameters if possible. – DSR Oct 17 '13 at 17:15
  • Take a look at the last section, "Default arguments" on http://docs.scala-lang.org/sips/completed/named-and-default-arguments.html . Looks like you can call methods to get the defaults. BTW, if you come up with something good, please answer your own question! – Ed Staub Oct 17 '13 at 17:28
  • @EdStaub nope, it is not -- for default arguments compiler generates method aka `f$default$1` and compiler substitutes it to the call in case user haven't provided one. – om-nom-nom Oct 17 '13 at 17:29
  • 1
    Why not just `val x = if (condition) new A(5, 5) else new A(5)`? – Travis Brown Oct 17 '13 at 17:30
  • @TravisBrown condition1, condition2, condition3, ... => combinatorial explosion. – om-nom-nom Oct 17 '13 at 17:32
  • 2
    I'd recommend not using default parameters for cases of more than a couple values. The compiler will no longer be able to tell you when you forgot to set a value. As the code base matures and new values are added, this practice will in inevitably lead to bugs, at least that's my experience. For only a few values, combinatorial explosion N/A. A good example of a default param failure is timeout in akka 1.3.x. – drstevens Oct 17 '13 at 17:57
  • 1
    @drstevens - The OP doesn't have control over the class being called; he's trying to figure out how to deal with it. – Ed Staub Oct 17 '13 at 18:21
  • 1
    This seems like the category of problem the builder pattern is applied to. – J Cracknell Oct 17 '13 at 18:37
  • @JCracknell how exactly builder pattern will help? One have to derive default values somehow first, and only then chose the way to apply them. – om-nom-nom Oct 17 '13 at 19:23
  • 2
    I've written up a blog post with a macro solution that provides precisely this syntax [here](http://meta.plasm.us/posts/2013/10/17/explicit-defaults-in-scala/). – Travis Brown Oct 18 '13 at 01:56

5 Answers5

1

It seems to me you have three possibilities:

  1. Create variables to hold each of the values you want to specify, do all the code to fill in the values, and instantiate A once at the end. This requires knowing the default values, as Ionut mentioned. I don't think creating a throwaway object to read the defaults is all that hackish -- certainly not as much as embedding the defaults themseves -- but whatever.

  2. Use the reflection API to create A. I'm not exactly sure how to do that but almost certainly you can pass in a list of parameters, with any unspecified parameters defaulted. This requires Scala 2.10; before that, only the Java reflection API was available and you'd have to hack through the internal implementation of optional parameters, which is hackish.

  3. Use macros. Also 2.10+. I think that quasiquotes should make it possible to do this without too much difficulty, although I'm not too familiar with them so I can't say for sure.

Urban Vagabond
  • 7,282
  • 3
  • 28
  • 31
1

Fetch the default values,

val defaultA = new A()

Then

val x = new A(p1 = 5, p2 = if (cond) 5 else defaultA.p2)
elm
  • 20,117
  • 14
  • 67
  • 113
0

Here's the quick and sort of wrong answer:

x = new A(p1 = 5, if (condition1) 5 else A.$lessinit$greater$default$2)

The "wrong" part is that this works in 2.10 but not in 2.9. The magick name for the default method has changed from version to version (notably, between 2.9 and 2.10), so it's safer to look up its name and call it via reflection. See Instantiating a case class with default args via reflection, How do I access default parameter values via Scala reflection? and Scala dynamic instantiation with default arguments.

Community
  • 1
  • 1
Ed Staub
  • 15,480
  • 3
  • 61
  • 91
0

What about having a derived class from A like this:

class D(val t2: (Boolean, Int)) extends A { 
  override val p2: Int = if(t2._1) t2._2 else A.init$default$2
}

Now you can instantiate A like this:

x = new D((condition1, 5))

If you have more parameters, then you can add a similar override statement and tuple parameter for each.

deepkimo
  • 3,187
  • 3
  • 23
  • 21
0

I can't help feeling what you are asking for is a bit unreasonable. As stated already it should be possible with macros. But generally if you are not satisfied with the provided constructor / factory, then you have to write you're own wrapper / factory. Personally I think that we may want to look a fresh at the whole issue of default values, Null objects and the Option class. However it is possible to cut out boilerplate, without macros. Put a an implicit Boolean in your utility package:

implicit class BooleanRich2(n : Boolean) {
  def apply[T](v1: T, v2: T): T = if (n) v1 else v2
}

Then say we wish to use the following class that we can't modify:

final class A(val p1: Int = 1, val p2: Int = 2, val p3: Int = 3, val p4: Int = 4){
  override def toString = s"p1: $p1, p2: $p2, p3: $p3, p4: $p4"
}

We can use it as follows:

var c1 = true
var c2 = false
def c3(s: String) = (s =="")

val a = new A(c1(20, 1)) //p1: 20, p2: 2, p3: 3, p4: 4
println(a) //p1: 20, p2: 2, p3: 3, p4: 4

val b = new A(p3 = c2(20, 3), p4 = c3("")(20, 4))
println(b) //p1: 1, p2: 2, p3: 3, p4: 20

val c = new A(p3 = c1(20, 3), p4 = c3("A non empty String")(20, 4))
println(c) //p1: 1, p2: 2, p3: 20, p4: 4 
Rich Oliver
  • 6,001
  • 4
  • 34
  • 57