Menu
Coddy logo textTech

פונקציות בנאי

חלק מהיחידה תכנות מונחה עצמים במסלול ה-R של Coddy. שיעור 3 מתוך 57.

מכיוון ש־R אף פעם לא בודקת את השדות של אובייקט S3, כל אובייקט ממחלקה צריך להיבנות באמצעות פונקציה אחת, הבנאי שלו. לפי המוסכמה, הבנאי ברמה הנמוכה נקרא new_ בתוספת שם המחלקה. הוא מקבל כל שדה כארגומנט, בודק את הטיפוסים באמצעות stopifnot() ומחזיר את האובייקט:

new_money <- function(amount = double(), currency = character()) {
  stopifnot(is.double(amount), is.character(currency))
  structure(list(amount = amount, currency = currency), class = "money")
}
m <- new_money(9.5, "EUR")
cat(m$amount, m$currency)

פלט:

9.5 EUR

stopifnot() עוצרת את התוכנית כאשר אחד התנאים שלה אינו TRUE, וההודעה מציינת איזו בדיקה נכשלה. סוג שגוי מזוהה ברגע שהאובייקט נבנה, ולא מאוחר יותר, כשפונקציה אחרת משתמשת בו:

new_money <- function(amount = double(), currency = character()) {
  stopifnot(is.double(amount), is.character(currency))
  structure(list(amount = amount, currency = currency), class = "money")
}
m <- new_money("9.5", "EUR")

פלט:

Error in new_money("9.5", "EUR") : is.double(amount) is not TRUE
Calls: new_money -> stopifnot
Execution halted

הבנאי הוא המקום היחיד שבו קוראים ל־structure() עבור המחלקה, כך שלכל האובייקטים יש אותם שדות באותו סדר. קוד שזקוק לאובייקטים רבים קורא לו בתוך לולאה או עם lapply():

new_point <- function(x = 0, y = 0) {
  stopifnot(is.numeric(x), is.numeric(y))
  structure(list(x = x, y = y), class = "point")
}
pts <- lapply(1:3, function(i) new_point(i, i * i))
for (p in pts) cat(p$x, p$y, "\n")

פלט:

1 1 
2 4 
3 9 

אובייקט S3 לא חייב להיות רשימה. בנאי יכול לתת תווית לווקטור פשוט ולאחסן עובדות נוספות כתכונות, וכך פועלים הגורמים והתאריכים של R עצמו. attr(x, "unit") קוראת תכונה:

new_distance <- function(value = double(), unit = "m") {
  stopifnot(is.double(value), is.character(unit))
  structure(value, unit = unit, class = "distance")
}
d <- new_distance(c(1.5, 2), "km")
print(attr(d, "unit"))
print(unclass(d)[2] * 1000)

פלט:

[1] "km"
[1] 2000
challenge icon

אתגר

קל

השלימו את הבנאי new_book(title, author, pages) בקובץ Book.R. הוא בודק באמצעות stopifnot() ש-title ו-author הם מסוג תו וש-pages הוא מספרי, ומחזיר רשימה עם שלושת השדות האלה והמחלקה book. לאחר מכן השלימו את book_label(b), שמחזירה Dune by Frank Herbert, 412 pages.

הקוד שסופק קורא שורות בפורמט title;author;pages. הוא מעביר את pages כמספר אם הוא מורכב מספרות, וכטקסט המקורי אחרת, מדפיס book: ואת התווית עבור כל אובייקט, ומדפיס rejected: ואת הכותרת כשהבנאי נעצר.

הקוד שלכם צריך להיכתב בקובץ Book.R. הקובץ main.R מכיל את קוד הקלט/פלט שסופק ואי אפשר לערוך אותו.

נסו בעצמכם

source("Book.R")

# קוד קלט/פלט שסופק: יש להשאיר אותו כפי שהוא
input <- suppressWarnings(readLines(file("stdin")))
for (line in input) {
  p <- strsplit(line, ";")[[1]]
  pages <- if (grepl("^[0-9]+$", p[3])) as.numeric(p[3]) else p[3]
  b <- tryCatch(new_book(p[1], p[2], pages), error = function(e) NULL)
  if (is.null(b)) {
    cat("rejected: ", p[1], "\n", sep = "")
  } else {
    cat(class(b), ": ", book_label(b), "\n", sep = "")
  }
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר R אונליין