2015-05-31 50 views
10

Đây là mã:Lớp "{" trong R là gì?

mf = function(..., expr) { 
    expr = substitute(expr) 
    print(class(expr)) 
    print(str(expr)) 
    expr 
} 
mf(a = 1, b = 2, expr = {matrix(NA, 4, 4)}) 

Output:

[1] "{" 
length 2 { matrix(NA, 4, 4) } 
- attr(*, "srcref")=List of 2 
    ..$ :Class 'srcref' atomic [1:8] 1 25 1 25 25 25 1 1 
    .. .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x7fbcdbce3860> 
    ..$ :Class 'srcref' atomic [1:8] 1 26 1 41 26 41 1 1 
    .. .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x7fbcdbce3860> 
- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x7fbcdbce3860> 
- attr(*, "wholeSrcref")=Class 'srcref' atomic [1:8] 1 0 1 42 0 42 1 1 
    .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x7fbcdbce3860> 
NULL 
{ 
matrix(NA, 4, 4) 
} 

Rõ ràng là kết quả của substitute(expr) sản xuất cái gì đó của lớp "{". Lớp này chính xác là gì? Tại sao là {matrix(NA, 4, 4)} độ dài 2? Những attrs kỳ lạ này có ý nghĩa gì?

Trả lời

10

{ là lớp dành cho một khối mã. Chỉ cần nhìn vào các lớp học, lưu ý sự khác biệt giữa các lớp này

mf(a = 1, b = 2, expr = {matrix(NA, 4, 4)}) 
# [1] "{" 
mf(a = 1, b = 2, expr = matrix(NA, 4, 4)) 
# [1] "call" 

Một lớp học { có thể chứa nhiều câu lệnh. Các length() cho biết có bao nhiêu câu trong khối (bao gồm cả sự bắt đầu của khối). Ví dụ:

length(quote({matrix(NA, 4, 4)})) 
# [1] 2 
length(quote({matrix(NA, 4, 4); matrix(NA,3,3)})) 
# [1] 3 
length(quote({})) 
# [1] 1 

Thuộc tính "srcref" và "srcfile" là cách R theo dõi hàm được xác định để cố gắng cung cấp thông báo lỗi thông tin. Bạn có thể xem trang trợ giúp ?srcfile để biết thêm thông tin về điều đó.

5

Điều bạn đang thấy là phản ánh cách R phơi bày cấu trúc ngôn ngữ nội bộ thông qua cấu trúc dữ liệu của chính nó.

Hàm substitute() trả về cây phân tích cú pháp của biểu thức R. Cây phân tích cú pháp là cây của các yếu tố ngôn ngữ. Chúng có thể bao gồm các giá trị theo nghĩa đen, ký hiệu (về cơ bản là các tên biến), các cuộc gọi hàm và các khối dấu ngoặc. Dưới đây là một cuộc biểu tình của tất cả các yếu tố ngôn ngữ R như trả về bởi substitute(), cho thấy loại của họ trong tất cả các chương trình kiểu phân loại R của:

tmc <- function(x) c(typeof(x),mode(x),class(x)); 
tmc(substitute(TRUE)); 
## [1] "logical" "logical" "logical" 
tmc(substitute(4e5L)); 
## [1] "integer" "numeric" "integer" 
tmc(substitute(4e5)); 
## [1] "double" "numeric" "numeric" 
tmc(substitute(4e5i)); 
## [1] "complex" "complex" "complex" 
tmc(substitute('a')); 
## [1] "character" "character" "character" 
tmc(substitute(somevar)); 
## [1] "symbol" "name" "name" 
tmc(substitute(T)); 
## [1] "symbol" "name" "name" 
tmc(substitute(sum(somevar))); 
## [1] "language" "call"  "call" 
tmc(substitute(somevec[1])); 
## [1] "language" "call"  "call" 
tmc(substitute(somelist[[1]])); 
## [1] "language" "call"  "call" 
tmc(substitute(somelist$x)); 
## [1] "language" "call"  "call" 
tmc(substitute({blah})); 
## [1] "language" "call"  "{" 

Ghi chú:

  • Note thế nào tất cả các hệ thống phân loại ba loại rất giống nhau , nhưng khác biệt một cách tinh tế. Đây có thể là một nguồn gây nhầm lẫn. typeof() cho kiểu lưu trữ của đối tượng, đôi khi được gọi là loại "nội bộ" (thành thật mà nói, có thể không được gọi là "nội bộ" vì nó thường xuyên được hiển thị trực tiếp với người dùng ở cấp R, nhưng thường được mô tả theo cách đó, tôi sẽ gọi nó là kiểu "cơ bản" hoặc "cơ bản"), mode() đưa ra một lược đồ phân loại tương tự mà mọi người có thể bỏ qua, và class() cho ẩn (nếu không có thuộc tính class) hoặc rõ ràng (nếu có là) lớp của đối tượng, được sử dụng để tra cứu phương thức S3 (và, nó nên được nói, đôi khi được kiểm tra trực tiếp bằng mã R, độc lập với quá trình tra cứu S3).
  • Lưu ý cách TRUE là một logic theo nghĩa đen, nhưng T là một biểu tượng, giống như bất kỳ tên biến khác, và chỉ xảy ra được gán cho TRUE theo mặc định (và như trên cho FFALSE). Đây là lý do tại sao đôi khi người khuyên không nên sử dụng TF ủng hộ sử dụng TRUEFALSE, vì TF có thể được bố trí (nhưng cá nhân tôi thích sử dụng TF cho tính súc tích, không ai bao giờ nên phân công lại những người!).
  • Người đọc sắc sảo sẽ nhận thấy rằng trong bản trình diễn của tôi về chữ, tôi đã bỏ qua kiểu thô. Điều này là bởi vì không có thứ gì như một nguyên văn trong R.Trong thực tế, có rất ít cách để có được một nắm giữ của vectơ nguyên trong R; raw(), as.raw(), charToRaw()rawConnectionValue() là những cách duy nhất tôi biết và nếu tôi sử dụng các chức năng đó trong một cuộc gọi substitute(), chúng sẽ được trả lại dưới dạng "call" đối tượng, giống như trong ví dụ sum(somevar) chứ không phải giá trị thô. Điều tương tự có thể được nói cho loại danh sách; không có thứ gì như một danh sách theo nghĩa đen (mặc dù có nhiều cách để có được một danh sách thông qua các cuộc gọi hàm). Các vectơ thô đơn giản trả lại 'raw' cho tất cả ba loại phân loại và danh sách đơn giản trả lại 'list' cho tất cả ba loại phân loại.

Bây giờ, khi bạn có cây phân tích phức tạp hơn giá trị hoặc ký hiệu đơn giản (có nghĩa là phải gọi hàm hoặc biểu thức có dấu), bạn thường có thể kiểm tra nội dung của cây phân tích đó liệt kê. Đây là cách R phơi bày cấu trúc ngôn ngữ nội bộ của nó thông qua các cấu trúc dữ liệu riêng của nó.

Lặn vào ví dụ của bạn:

pt <- as.list(substitute({matrix(NA,4,4)})); 
pt; 
## [[1]] 
## `{` 
## 
## [[2]] 
## matrix(NA, 4, 4) 

này làm cho nó rõ ràng lý do tại sao length() returns 2: đó là độ dài của danh sách đại diện cho cây phân tích cú pháp. Nói chung, các thanh giằng của biểu thức được dịch sang các thành phần danh sách đầu tiên, và các thành phần danh sách còn lại được xây dựng từ những điều khoản về dấu chấm phẩy trong niềng răng:

as.list(substitute({})); 
## [[1]] 
## `{` 
## 
as.list(substitute({a})); 
## [[1]] 
## `{` 
## 
## [[2]] 
## a 
## 
as.list(substitute({a;b})); 
## [[1]] 
## `{` 
## 
## [[2]] 
## a 
## 
## [[3]] 
## b 
## 
as.list(substitute({a;b;c})); 
## [[1]] 
## `{` 
## 
## [[2]] 
## a 
## 
## [[3]] 
## b 
## 
## [[4]] 
## c 

Lưu ý rằng đây là giống với cách gọi hàm làm việc , trừ trường hợp có sự khác biệt đó, cho các cuộc gọi chức năng, các thành phần danh sách được hình thành từ các đối số bằng dấu phẩy để gọi hàm:

as.list(substitute(sum())); 
## [[1]] 
## sum 
## 
as.list(substitute(sum(1))); 
## [[1]] 
## sum 
## 
## [[2]] 
## [1] 1 
## 
as.list(substitute(sum(1,3))); 
## [[1]] 
## sum 
## 
## [[2]] 
## [1] 1 
## 
## [[3]] 
## [1] 3 
## 
as.list(substitute(sum(1,3,5))); 
## [[1]] 
## sum 
## 
## [[2]] 
## [1] 1 
## 
## [[3]] 
## [1] 3 
## 
## [[4]] 
## [1] 5 

từ trên nó trở nên rõ ràng rằng các thành phần danh sách đầu tiên thực sự là một biểu tượng đại diện cho tên của một hàm, cho cả các biểu thức kết hợp và các cuộc gọi hàm. Nói cách khác, cú đúp mở một cuộc gọi hàm, một cuộc gọi đơn giản trả về đối số cuối cùng của nó. Cũng giống như dấu ngoặc vuông là chức năng bình thường gọi với một cú pháp thuận tiện xây dựng trên đầu trang của họ, nẹp mở là một chức năng cuộc gọi bình thường với một cú pháp thuận tiện xây dựng trên đầu trang của nó:

a <- 4:6; 
a[2]; 
## [1] 5 
`[`(a,2); 
## [1] 5 
{1;2}; 
## [1] 2 
`{`(1,2); 
## [1] 2 

Quay lại ví dụ của bạn, chúng tôi có thể khám phá toàn bộ cây phân tích bằng cách duyệt qua cấu trúc danh sách đại diện cho cây phân tích cú pháp. Tôi chỉ viết một chút hàm đệ quy tốt đẹp mà có thể làm được điều này rất dễ dàng:

unwrap <- function(x) if (typeof(x) == 'language') lapply(as.list(x),unwrap) else x; 
unwrap(substitute(3)); 
## [1] 3 
unwrap(substitute(a)); 
## a 
unwrap(substitute(a+3)); 
## [[1]] 
## `+` 
## 
## [[2]] 
## a 
## 
## [[3]] 
## [1] 3 
## 
unwrap(substitute({matrix(NA,4,4)})); 
## [[1]] 
## `{` 
## 
## [[2]] 
## [[2]][[1]] 
## matrix 
## 
## [[2]][[2]] 
## [1] NA 
## 
## [[2]][[3]] 
## [1] 4 
## 
## [[2]][[4]] 
## [1] 4 

Như bạn thấy, khái niệm chuẩn bị tinh thần biến thành một cuộc gọi chức năng bình thường của hàm `{`(), lấy một đối số, đó là tuyên bố đơn bạn đã mã hóa nó. Tuyên bố đó bao gồm một cuộc gọi hàm duy nhất tới matrix(), lấy ba đối số, mỗi đối số là một giá trị theo nghĩa đen: NA, 44. Và đó là toàn bộ cây phân tích.

Vì vậy, bây giờ chúng ta có thể hiểu ý nghĩa của lớp "{" ở mức độ sâu: nó đại diện cho phần tử của cây phân tích cú pháp là hàm gọi hàm `{`(). Nó xảy ra để được phân loại khác nhau từ các cuộc gọi chức năng khác ("{" thay vì "call"), nhưng theo như tôi có thể nói, điều đó không có ý nghĩa ở bất cứ đâu. Cũng quan sát rằng các số typeof()mode() giống hệt nhau ("language""call", tương ứng) giữa tất cả các phần tử phân tích cú pháp đại diện cho các cuộc gọi hàm, cho cả số `{`() và các số khác.

+0

Bạn chỉ có thể sử dụng 'trích dẫn' trong tất cả các ví dụ, vì ví dụ' thay thế ({ma trận (NA, 4, 4)}) == trích dẫn ({ma trận (NA, 4, 4)}) ' – qed