How to detect dynamic extent


#21

@jducoeur, that’s a good observation. I hadn’t really considered the difference until now. I have two ways of creating an internal (non-leaf) Bdd object, 1) the apply methods of the Bdd object, declared as follows:

object Bdd {
  ...
  def apply(label: Int): Bdd = { ...}
    
  def apply(label: Int, positive: Bdd, negative: Bdd): Bdd = { ...}
  
}

And 2) since BddNode is a case class, then BddNode may be called as a constructer. In fact the Bdd.apply method calls BddNode and memoizes the result.

I don’t 100% understand what it means (in terms of consequence) to have a private constructor (as this concept does not exist in CLOS, the Common Lisp Object System) but it does seem that I want Bdd.apply() probably to be public and BddNode(...) to be private. However, if I say, “I don’t want BddNode() to be callable elsewhere,” what does that restriction really mean? Does it also mean: I don’t want unit tests to be able to call it? Does it also mean: I want subclasses of BddNode to be forced to copy/paste the code rather than calling it?

One part of my uncertainty is that one never knows how one might want to extend a program in the future. I may later want to create application specific subclasses of Bdd, BddNode, and BddLeaf when trying to use binary decision diagrams and extend the behavior build into the base classes. I don’t understand the philosophy of “private” well enough to know how to write good code, without crippling future applications.

I admit that I don’t have the correct scala-think installed into my brain yet, but I don’t really think in terms of privacy, but in terms of protocol. The restriction, currently in the semantics of my program, is that BddNode(...) should only be called in the context of something like what Bdd.apply(...) sets up. Additionally I would like any application subclass of BddNode to provide the analogous context when it creates an instance and BddNode constructor is called by inheritance.


#22

The notion of privacy here comes largely from the Java world, where it is pushed fairly hard. It makes sense from an OO perspective: a public entry point is one intended for use by outside consumers of this object, where a private one is only for use by this object itself. It’s less emphasized in the Scala world (I don’t often use private except in libraries), and some of the FP community think the whole thing is a bit pointless.

This is exactly why I rarely use private per se, although I do use the other variations a bit more often. Note that:

  • protected means that this entry point can be called by subclasses, but not by other types.
  • private[mypackage] means that this entry point can be called anywhere inside of package mypackage, but not from outside it. (In practice, this is what I use most often.)
  • private[this] means that this entry point can be called from just this instance – rarely but occasionally appropriate.

But keep in mind, enforcement of all of this is somewhat weak – as @tarsa has been pointing out (and this is why I bring it up), it’s hard to prevent a determined outside class from using an entry point. Most of this is about expressing intent, such that the compiler will, under normal circumstances, prevent incorrect calls.

Yep, and that’s one of the standard arguments against privacy. This has bitten me hard on the ass from time to time, when a library author thought that something obviously wouldn’t be extended, but I found a use case where it was entirely appropriate to do so.

That said, the argument for privacy (typically private[mypackage]) when writing a library is that anything not private is likely to wind up depended upon – which means that this is now kind of set in stone. If clients of the library wind up using entry points you didn’t intend, this can make it much harder to evolve the library in the future. Hence, it is common for library authors to mark everything that isn’t specifically part of the public API as package-private. While that doesn’t prevent a client from doing something stupid, it means that the onus is on them to make the necessary changes if that entry point changes in the future.

This is a somewhat subtle case, and I’m honestly unsure that the right answer for you exists yet. I haven’t quite grokked your use case yet, but it sounds like what it wants might be Dotty’s feature of Context Queries (previously named “Implicit Functions”), which are all about providing a specific capability within a specific lexical scope. It has little to do with privacy per se – instead, it’s about only making the function available in the scope where you should be using it.

That feature doesn’t exist in Scala 2, but something similar using explicit context objects might be the right program structure for now – that is, Bdd.apply() creates an explicit handler, which gets passed to the client code, and that is the only way to create a BddNode(). BddNode()'s constructor would be package-private, so that client code can’t create it otherwise by accident. From what you’re saying, I suspect that’s how I would currently tackle this, but I’m not certain I understand the use case well enough…


#23

Justin, that all makes good sense and it sounds like practical advise. As I learn more about implicits, there might indeed be a nice way to handle my situation. It seems to me like implicits seem to attempt some of the same problems dynamic variable solve, albeit in a different way.


#24

@jimka I don’t see any advantage in adding an implicit parameter over a simple visibility modifier.

Anyway, a case class is not your friend for your domain because its apply and copy methods are public so it will leak. You just need to supply equality on your own.

So what about

class BddNode private[jimka] (val id: Int)

object Bdd {
  def apply(id: Int): BddNode = new BddNode(id)
}

or

object Bdd {
  class Node private[Bdd] (val id: Int)

  def apply(id: Int): Node = new Node(id)
}