Menu
Coddy logo textTech

状態の保護

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

セッターは値を変更できる唯一の場所なので、ルールを適用する場所です。最初にチェックを行い、何かが変更される前に stop() を呼び出すため、オブジェクトは有効な状態を保ちます。

make_account <- function(balance = 0) {
  withdraw <- function(x) {
    if (x <= 0) stop("amount must be positive", call. = FALSE)
    if (x > balance) stop("insufficient funds", call. = FALSE)
    balance <<- balance - x
  }
  list(withdraw = withdraw, balance = function() balance)
}
acc <- make_account(50)
msg <- tryCatch(acc$withdraw(80), error = function(e) conditionMessage(e))
cat(msg, "/", acc$balance())

出力:

insufficient funds / 50

lockBinding("rate", e)は、環境内の1つの変数を読み取り専用にします。その後にそれへ代入するとエラーになりますが、他の変数は書き込み可能なままです。

loan <- new.env()
loan$rate <- 0.05
loan$owed <- 1000
lockBinding("rate", loan)
loan$owed <- 900
msg <- tryCatch({ loan$rate <- 0; "changed" }, error = function(e) conditionMessage(e))
cat(msg, "\n")
cat(loan$rate, loan$owed, "\n")

出力:

cannot change value of locked binding for 'rate' 
0.05 900 

lockEnvironment(e) は新しい変数が追加されるのを防ぎます。bindings = TRUE を指定すると、既存の変数もすべてロックされ、オブジェクト全体が読み取り専用になります:

cfg <- new.env()
cfg$theme <- "dark"
lockEnvironment(cfg, bindings = TRUE)
for (step in c("change", "add")) {
  msg <- tryCatch({
    if (step == "change") cfg$theme <- "light" else cfg$size <- 12
    "done"
  }, error = function(e) conditionMessage(e))
  cat(step, ":", msg, "\n")
}

出力:

change : cannot change value of locked binding for 'theme' 
add : cannot add bindings to a locked environment 

local() は新しい環境でコードを実行し、その最後の値を返します。内部で定義されたヘルパーは隠されたままになります。これにより、source() がそれ以外のすべてをグローバル環境に配置する場合でも、ファイル内の関数を非公開にできます。

circle_area <- local({
  square <- function(x) x * x
  function(r) pi * square(r)
})
print(round(circle_area(2), 2))
print(exists("square"))

出力:

[1] 12.57
[1] FALSE
challenge icon

チャレンジ

簡単

Account.R のファクトリ make_account(owner) を完成させてください。残高は 0 から始まり、非公開の履歴には変更のたびに +50 や -20 として記録されます。返されるリストには、次の 4 つの関数があります。

  • deposit(x) は、x が 0 より大きくない場合、amount must be positive で停止します。
  • withdraw(x) は、amount must be positive または insufficient funds で停止します。
  • balance() は残高を返します。
  • history() は履歴を文字ベクトルとして返します。

何かを変更する前にチェックし、call. = FALSE を使用してください。提供されているコードは所有者を読み取り、続いて dep 50 または wd 20 という行を読み込み、それぞれについて ok または error: とメッセージを出力し、履歴のコピーを変更しようとしてから、残高と履歴を出力します。

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

自分で試してみよう

source("Account.R")

# 提供された入出力コード: そのままにしておく
input <- suppressWarnings(readLines(file("stdin")))
acc <- make_account(input[1])
for (line in input[-1]) {
  p <- strsplit(line, " ")[[1]]
  amount <- as.numeric(p[2])
  msg <- tryCatch({
    if (p[1] == "dep") acc$deposit(amount) else acc$withdraw(amount)
    "ok"
  }, error = function(e) paste("error:", conditionMessage(e)))
  cat(line, ": ", msg, "\n", sep = "")
}
h <- acc$history()
h[1] <- "tampered"
cat("balance: ", acc$balance(), "\n", sep = "")
cat("history: ", paste(acc$history(), collapse = ", "), "\n", sep = "")
quiz icon腕試し

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

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

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