0

I know that in Scala I can write getters/setters like this:

class Person() {
 // Private age variable, renamed to _age
 private var _age = 0
 var name = ""

 // Getter
 def age = _age

 // Setter
 def age_= (value:Int):Unit = _age = value
}

Where the _ in def age_= represents a whitespace. (Source)

Following this principle, I would like to write something like this:


object SubjectUnderObs {
  var x: Int = 0

  private var myListeners : Set[Int => Unit] = Set()

  def listeners_+= (listener: Int => Unit): Unit =
    myListeners = myListeners + listener
}

// This does not compile
SubjectUnderObs.listeners += { newX =>
  println(newX)
}

I basically want to add callbacks using this kind of syntax: SubjectUnderObs.listeners +=. In my case however, I cannot omit the _. Why doesn't this work the same way as the setter above and how can I achieve what I want?

Florian Baierl
  • 2,378
  • 3
  • 25
  • 50
  • 4
    The underscore `_` does not "represent a whitespace" anywhere (and I don't see what you are quoting, exactly; the linked blogpost does not seem to claim that). The `age_=` is just an ordinary method name, which happens to be treated specially, so that `x.age = y` is desugared into `x.age_=(y)`. If you want to implement the `+=` method, then just implement `def +=(foo: Foo): Bar = { ... }`, no tricks with underscores or spaces necessary. – Andrey Tyukin Jan 15 '19 at 21:47
  • 1
    What benefit do you hope to gain from having myListeners be a Set? I would use a Seq so that listeners are called in a defined order. – Brian McCutchon Jan 15 '19 at 21:55
  • Thanks guys. And good catch with the `Set` - changed it to a `Seq`. – Florian Baierl Jan 15 '19 at 22:10
  • The linked blog is misleading in at least one way I noticed. (RHS of a def is just an expr, which can be a simple expr or a block expr; there is no special exemption for braces; I think such talk is confusing.) – som-snytt Jan 15 '19 at 23:00
  • 1
    @AndreyTyukin It says: The underscore is a special character in Scala and in this case, allows for a space in the method name which essentially makes the name “age =”. I didn't read the whole thing carefully, but the blog is not great as to specifics. – som-snytt Jan 15 '19 at 23:02

2 Answers2

4

First, methods ending in _= are special methods that represent setters. That's why underscores only work the way you think they do in that case.

Now, you could get the behavior you want by having a public var listeners: Set[Int => Unit], but then people could do other things, like delete listeners. You can instead have listeners be an object that defines the += method:

object listeners {
  def +=(listener: Int => Unit): Unit =
    myListeners += listener
  }
}

Put that inside SubjectUnderObs and it should work.

Brian McCutchon
  • 8,354
  • 3
  • 33
  • 45
2

There is not a general rule for changing _ with a on method names.
In your first example what happened was one syntactic sugar rule.

a.b = c is equivalent to a.b_=(c). When you create a val/var x in a Class/Object, Scala creates the methods x and x_= for you.
-- Source

If you will only have one method like this (E.g. only listeners) you can define a += method that accepts a Listener, I am not sure if that would be readable enough for you.

Another option would be to use a nested object to provide the DSL you want.

final class SubjectUnderObs { self =>
  private[this] var _listeners: List[Int] = List.empty

  object ListenersMutator {
    def += (listener: Int): Unit = {
      self._listeners = listener :: self._listeners
    }
  }

  def listeners = ListenersMutator

  override def toString: String =
    _listeners.mkString("[", ", ", "]")
}

val res1 = new SubjectUnderObs
// res1: SubjectUnderObs = []

res1 += 10
// res1: SubjectUnderObs = [10]