R5 (ReferenceClass) で Singleton

こんな感じでどうでしょうか?

Singleton <- setRefClass(
    "Singleton"
    )
(function(new) {
    new  # to avoid lazy evaluation
    Singleton$getInstance <- function() {
        instance <- new()
        Singleton$.instance <- instance
        Singleton$getInstance <- function() {
            return(Singleton$.instance)
        }
        return(instance)
    }
})(Singleton$new)
Singleton$new <- NULL

ポイントは次のとおりかと思います。

  • Singleton$new を closure に退避した上で Singleton$new に NULL を代入して使えなくする
  • 最初に Singleton$getInstance を実行した時に初めてインスタンスを作成する
  • インスタンスを作成したら Singleton$getInstance を書き換える

Singleton$getInstance を書き換えずに if で分岐させてもいいんですが、毎回条件式が評価されるのもなんか嫌なので・・・。”return(Singleton$.instance || (Singleton$.instance <- instance))” みたいに書けるといいんですけどね。

使ってみる

次のようにちょっとだけ手を加えたやつを使います。

Singleton <- setRefClass(
    "Singleton",
    fields = list(
      variable = "numeric"
      ),
    methods = list(
      initialize = function() {
          cat("create instance\n")
          variable <<- 1
      }
      )
    )
Singleton$accessors(names(Singleton$fields()))
(function(new) {
    new  # to avoid lazy evaluation
    Singleton$getInstance <- function() {
        instance <- new()
        Singleton$.instance <- instance
        Singleton$getInstance <- function() {
            return(Singleton$.instance)
        }
        return(instance)
    }
})(Singleton$new)
Singleton$new <- NULL

以下、動作確認です。

> a <- Singleton$getInstance()
create instance
> b <- Singleton$getInstance()
> a$getVariable()
[1] 1
> b$getVariable()
[1] 1
> a$setVariable(100)
> a$getVariable()
[1] 100
> b$getVariable()
[1] 100
> as.environment(a)
<environment: 0x106e1fed0>
> as.environment(b)
<environment: 0x106e1fed0>
> Singleton$new()  # 新しくインスタンスを作ることはできない
Error: attempt to apply non-function

おもしろいですね!!
もっと良い方法があったら教えてください。