Menu
Coddy logo textTech

オブジェクトとしてのクロージャ

CoddyのRジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 17/57。

Logic & Flow でクロージャについて学びました。ある関数の中で作られた関数は、その周囲にある変数を保持します。このような関数のリストを返す関数ファクトリは、オブジェクトを構築します。そのローカル変数はオブジェクトのデータであり、関数はそのメソッドです。

make_account <- function(balance = 0) {
  deposit <- function(x) balance <<- balance + x
  get_balance <- function() balance
  list(deposit = deposit, get_balance = get_balance)
}
acc <- make_account(100)
acc$deposit(50)
print(acc$get_balance())

出力:

[1] 150

ファクトリを呼び出すたびにその本体が再度実行されるため、各オブジェクトはそれぞれ独自の変数を持ちます。一方のアカウントを変更しても、もう一方には影響しません。

make_account <- function(balance = 0) {
  list(deposit = function(x) balance <<- balance + x,
       get_balance = function() balance)
}
ada <- make_account(100)
bo <- make_account(5)
ada$deposit(1)
cat(ada$get_balance(), bo$get_balance())

出力:

101 5

データはプライベートです。balanceはリストの要素ではないため、acc$balanceはNULLとなり、そこへの代入は無関係な要素を追加するだけです。リスト内の関数が、そのオブジェクトの公開インターフェース全体です:

make_account <- function(balance = 0) {
  list(get_balance = function() balance)
}
acc <- make_account(100)
print(acc$balance)
acc$balance <- 1e6
print(acc$get_balance())

出力:

NULL
[1] 100

メソッドは、リストが構築される前にローカル関数として定義されていれば、互いに呼び出すことができます。ここでは、add_many() が add() を再利用しています。

make_tally <- function() {
  total <- 0
  add <- function(x) total <<- total + x
  add_many <- function(xs) for (x in xs) add(x)
  get <- function() total
  list(add = add, add_many = add_many, get = get)
}
t <- make_tally()
t$add(1)
t$add_many(c(2, 3, 4))
print(t$get())

出力:

[1] 10
challenge icon

チャレンジ

簡単

make_counter(start = 0) ファクトリを Counter.R に完成させてください。現在の値を非公開に保ち、4つの関数を含むリストを返します。increment() と decrement() は値を1ずつ変更し、reset() は値を start に戻し、value() はその値を返します。

提供されたコードは開始値を読み取り、続いて +、-、または reset のコマンドを読み取り、それぞれの後に値を出力します。最後に、返されたリスト内の名前を出力します。このリストには4つの関数だけが含まれていなければなりません。

コードは Counter.R に記述してください。main.R には提供された入出力コードが含まれており、編集できません。

自分で試してみよう

source("Counter.R")

# 提供された入出力コード: そのままにしておく
input <- suppressWarnings(readLines(file("stdin")))
counter <- make_counter(as.numeric(input[1]))
for (cmd in input[-1]) {
  if (cmd == "+") counter$increment()
  if (cmd == "-") counter$decrement()
  if (cmd == "reset") counter$reset()
  cat(cmd, " -> ", counter$value(), "\n", sep = "")
}
cat("fields visible: ", paste(sort(names(counter)), collapse = ", "), "\n", sep = "")
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Rオンラインコンパイラ