Tại sao người tạo Ruby lại chọn sử dụng khái niệm Symbols?


15

tl; dr: Sẽ có một định nghĩa bất khả tri về ngôn ngữ của các Biểu tượng và một lý do để có chúng trong các ngôn ngữ khác?

Vậy, tại sao người tạo Ruby lại sử dụng khái niệm symbolstrong ngôn ngữ?

Tôi hỏi điều này từ quan điểm của một lập trình viên không phải là ruby. Tôi đã học được rất nhiều ngôn ngữ khác và không tìm thấy ngôn ngữ nào trong số đó, tôi cần phải xác định xem tôi có giao dịch hay không với những gì Ruby gọi symbols.

Câu hỏi chính là, liệu khái niệm về symbolsRuby có tồn tại cho hiệu suất, hay chỉ là thứ gì đó cần thiết vì cách ngôn ngữ được viết?

Liệu một chương trình trong Ruby sẽ nhẹ hơn và / hoặc nhanh hơn chương trình của nó, giả sử, đối tác Python hoặc Javascript? Nếu vậy, nó sẽ là vì symbols?

Vì một trong những mục đích của Ruby là dễ đọc và viết cho con người, nên những người tạo ra nó không thể làm giảm quá trình mã hóa bằng cách thực hiện những cải tiến đó trong chính trình thông dịch (như có thể bằng các ngôn ngữ khác)?

Có vẻ như mọi người chỉ muốn biết những gì symbolsvà cách sử dụng chúng, chứ không phải tại sao họ lại ở đó ngay từ đầu.


Scala có Symbols, ngoài đỉnh đầu của tôi. Tôi nghĩ rằng nhiều Lisps làm.
D. Ben Knoble

Câu trả lời:


17

Người tạo ra Ruby, Yukihiro "Matz" Matsumoto, đã đăng một lời giải thích về việc Ruby bị ảnh hưởng bởi Lisp, Smalltalk, Perl (và Wikipedia cũng nói Ada và Eiffel):

Ruby là một ngôn ngữ được thiết kế theo các bước sau:

  • sử dụng một ngôn ngữ đơn giản (như một ngôn ngữ trước CL).
  • loại bỏ các macro, biểu thức s.
  • thêm hệ thống đối tượng đơn giản (đơn giản hơn nhiều so với CLOS).
  • thêm khối, lấy cảm hứng từ các chức năng bậc cao hơn.
  • thêm các phương thức được tìm thấy trong Smalltalk.
  • thêm chức năng tìm thấy trong Perl (theo cách OO).

Vì vậy, Ruby là một Lisp ban đầu, theo lý thuyết.

Chúng ta hãy gọi nó là MatzLisp kể từ bây giờ. ;-)

Trong bất kỳ trình biên dịch nào, bạn sẽ quản lý mã định danh cho các hàm, biến, khối được đặt tên, loại, v.v. Thông thường, bạn lưu trữ chúng trong trình biên dịch và quên chúng trong tệp thực thi được sản xuất, ngoại trừ khi bạn thêm thông tin gỡ lỗi.

Trong Lisp, các biểu tượng như vậy là tài nguyên hạng nhất, được lưu trữ trong các gói khác nhau, có nghĩa là bạn có thể thêm các biểu tượng mới trong thời gian chạy, liên kết chúng với các loại đối tượng khác nhau. Điều này hữu ích khi lập trình meta vì bạn có thể chắc chắn rằng mình sẽ không có xung đột đặt tên với các phần khác của mã.

Ngoài ra, các biểu tượng được tập trung tại thời điểm đọc và có thể được so sánh bằng danh tính, đây là một cách hiệu quả để có loại giá trị mới (như số, nhưng trừu tượng). Điều này giúp viết mã nơi bạn sử dụng trực tiếp các giá trị tượng trưng, ​​thay vì xác định các loại enum của riêng bạn được hỗ trợ bởi các số nguyên. Ngoài ra, mỗi biểu tượng có thể chứa dữ liệu bổ sung. Ví dụ, đó là cách Emacs / Slime có thể đính kèm siêu dữ liệu từ Emacs vào danh sách thuộc tính của biểu tượng.

Khái niệm biểu tượng là trung tâm ở Lisp. Hãy xem ví dụ tại PAIP (Mô hình lập trình trí tuệ nhân tạo: Nghiên cứu trường hợp ở Lisp chung, Norvig) để biết ví dụ chi tiết.


5
Câu trả lời tốt. Tuy nhiên tôi không đồng ý với Matz: Tôi sẽ không bao giờ nghĩ đến việc gọi một ngôn ngữ mà không có macro là một phương ngữ không thể thiếu. Các phương tiện siêu lập trình thời gian chạy của lisp chính xác là thứ mang lại cho ngôn ngữ này sức mạnh tuyệt vời của nó, bù lại cho ngữ pháp đơn giản, khó hiểu của nó.
cmaster - phục hồi monica

11

Vậy, tại sao những người sáng tạo Ruby phải sử dụng khái niệm về symbolsngôn ngữ?

Chà, họ không nghiêm túc "phải", họ đã chọn. Ngoài ra, lưu ý rằng nói đúng Symbolkhông phải là một phần của ngôn ngữ, chúng là một phần của thư viện cốt lõi. Họ làm có cú pháp văn chương ngôn ngữ cấp, nhưng họ sẽ chỉ làm việc tốt nếu bạn có để xây dựng chúng bằng cách gọi Symbol::new.

Tôi hỏi từ quan điểm của một lập trình viên không phải là ruby ​​đang cố gắng hiểu nó. Tôi đã học được rất nhiều ngôn ngữ khác và thấy không có ngôn ngữ nào trong số chúng cần phải xác định xem tôi có giao dịch hay không với những gì Ruby gọi symbols.

Bạn đã không nói "rất nhiều ngôn ngữ khác" là gì, nhưng đây chỉ là một đoạn trích nhỏ về các ngôn ngữ có Symbolkiểu dữ liệu như của Ruby:

Ngoài ra còn có các ngôn ngữ khác cung cấp các tính năng của Symbols ở dạng khác. Ví dụ, trong Java, các tính năng của Ruby Stringđược chia thành hai loại (thực tế là ba): StringStringBuilder/ StringBuffer. Mặt khác, các tính năng của loại Ruby Symbolđược xếp lại thành Stringloại Java : Java Stringcó thể được thực hiện , các chuỗi ký tựStrings là kết quả của các biểu thức hằng số được đánh giá theo thời gian biên dịch được tự động thực hiện, Stringcó thể được tạo động bằng cách gọi các String.internphương pháp. Một thực tập sinh Stringtrong Java giống hệt như Symboltrong Ruby, nhưng nó không được triển khai như một loại riêng biệt, nó chỉ là một trạng thái khác với JavaString có thể có. (Lưu ý: trong các phiên bản trước của Ruby, String#to_symđược gọi làString#intern và phương pháp đó vẫn tồn tại cho đến ngày nay như một bí danh kế thừa.)

Câu hỏi chính có thể là: Liệu khái niệm về symbolsRuby có tồn tại như một mục đích hiệu suất đối với chính nó và các ngôn ngữ khác,

Symbols là đầu tiên và quan trọng nhất là một kiểu dữ liệu với ngữ nghĩa cụ thể . Các ngữ nghĩa này cũng cho phép thực hiện một số thao tác thực hiện (ví dụ: kiểm tra đẳng thức nhanh O (1)), nhưng đó không phải là mục đích chính.

hoặc chỉ một cái gì đó là cần thiết để tồn tại vì cách viết ngôn ngữ?

SymbolKhông cần thiết trong ngôn ngữ Ruby, Ruby sẽ hoạt động tốt nếu không có chúng. Chúng hoàn toàn là một tính năng của thư viện. Có chính xác một vị trí trong ngôn ngữ được gắn với Symbols: một defbiểu thức định nghĩa phương thức ước tính thành một Symbolbiểu thị tên của phương thức đang được định nghĩa. Tuy nhiên, đó là một thay đổi khá gần đây, trước đó, giá trị trả về chỉ đơn giản là không xác định. MRI chỉ đơn giản là đánh giá nil, Rubinius đã đánh giá một Rubinius::CompiledMethodđối tượng, v.v. Cũng có thể đánh giá một điểm UnboundMethodhay chỉ là a String.

Liệu một chương trình trong Ruby sẽ nhẹ hơn và / hoặc nhanh hơn chương trình của nó, giả sử, đối tác Python hoặc Node? Nếu vậy, nó sẽ là vì symbols?

Tôi không chắc chắn những gì bạn đang hỏi ở đây. Hiệu suất chủ yếu là vấn đề chất lượng thực hiện, không phải ngôn ngữ. Thêm vào đó, Node thậm chí không phải là một ngôn ngữ, đó là khung I / O được tổ chức cho ECMAScript. Chạy một kịch bản tương đương trên IronPython và MRI, IronPython có thể sẽ nhanh hơn. Chạy một kịch bản tương đương trên CPython và JRuby + Truffle, JRuby + Truffle có thể sẽ nhanh hơn. Điều này không liên quan gì đến Symbols nhưng với chất lượng thực hiện: JRuby + Truffle có trình biên dịch tối ưu hóa mạnh mẽ, cộng với toàn bộ máy móc tối ưu hóa của JVM hiệu suất cao, CPython là một trình thông dịch đơn giản.

Vì một trong những mục đích của Ruby là dễ đọc và viết cho con người, nên những người tạo ra nó không thể làm dịu quá trình mã hóa bằng cách thực hiện những cải tiến đó trong chính trình thông dịch (như có thể bằng các ngôn ngữ khác)?

Số Symbols không phải là một tối ưu hóa trình biên dịch. Chúng là một kiểu dữ liệu riêng biệt với ngữ nghĩa cụ thể. Chúng không giống như các bản sao của YARV , đó là một tối ưu hóa nội bộ riêng tư cho Floats. Tình huống này không giống như đối với Integer, BignumFixnum, đáng lẽ là một chi tiết tối ưu hóa nội bộ riêng tư vô hình, nhưng thật không may. (Điều này cuối cùng sẽ được sửa trong Ruby 2.4, nó sẽ loại bỏ FixnumBignumchỉ để lại Integer.)

Làm theo cách mà Java thực hiện, như một trạng thái đặc biệt của Strings bình thường có nghĩa là bạn luôn cần phải cảnh giác về việc liệu bạn Stringcó ở trạng thái đặc biệt đó hay không và trong trường hợp nào chúng sẽ tự động ở trạng thái đặc biệt đó và khi nào thì không. Đó là một gánh nặng cao hơn nhiều so với việc chỉ có một kiểu dữ liệu riêng biệt.

Sẽ có một định nghĩa ngôn ngữ bất khả tri về các Biểu tượng và một lý do để có chúng trong các ngôn ngữ khác?

Symbollà một kiểu dữ liệu biểu thị khái niệm tên hoặc nhãn . Symbols là các đối tượng giá trị , không thay đổi, thường là ngay lập tức (nếu ngôn ngữ phân biệt một thứ như vậy), không trạng thái và không có danh tính. Hai Symbols bằng nhau cũng được đảm bảo giống hệt nhau, nói cách khác, hai Symbols bằng nhau thực sự là một Symbol. Điều này có nghĩa là bình đẳng giá trị và đẳng thức tham chiếu là như nhau, và do đó, đẳng thức là hiệu quả và O (1).

Những lý do để có chúng trong một ngôn ngữ là thực sự giống nhau, độc lập với ngôn ngữ. Một số ngôn ngữ dựa nhiều vào chúng hơn những ngôn ngữ khác.

Trong gia đình Lisp, chẳng hạn, không có khái niệm "biến". Thay vào đó, bạn có Symbolliên quan đến các giá trị.

Trong các ngôn ngữ với khả năng phản xạ hay nội tâm, Symbols thường được sử dụng để biểu thị tên của các đơn vị phản ánh trong các API phản chiếu, ví dụ như trong Ruby, Object#methods, Object#singleton_methods, Object#public_methods, Object#protected_methods, và Object#public_methodstrả về một Arraycủa Symbols (mặc dù họ có thể chỉ cần cũng trả về một Arraytrong Methods). Object#public_sendlấy một Symbolký hiệu tên của tin nhắn để gửi làm đối số (mặc dù nó cũng chấp nhận Stringnhư vậy, nhưng Symbolđúng hơn về mặt ngữ nghĩa).

Trong ECMAScript, Symbols là một khối xây dựng cơ bản để làm cho khả năng ECMAScript an toàn trong tương lai. Họ cũng đóng một vai trò lớn trong sự phản ánh.


Các nguyên tử Erlang được lấy trực tiếp từ Prolog (Robert Virding nói với tôi rằng tại một số điểm)
Zachary K

2

Các biểu tượng rất hữu ích trong Ruby và bạn sẽ thấy chúng trên toàn bộ mã Ruby vì mỗi biểu tượng được sử dụng lại mỗi khi nó được tham chiếu. Đây là một cải tiến hiệu suất trên các chuỗi vì mỗi lần sử dụng một chuỗi không được lưu trong một biến sẽ tạo ra một đối tượng mới trong bộ nhớ. Ví dụ: nếu tôi sử dụng cùng một chuỗi nhiều lần làm khóa băm:

my_hash = {"a" => 1, "b" => 2, "c" => 3}
100_000.times { |i| puts my_hash["a"] }

Chuỗi "a" được tạo 101.000 lần trong bộ nhớ. Nếu tôi đã sử dụng một biểu tượng thay thế:

my_hash = {a: 1, b: 2, c: 3}
100_000.times { |i| puts my_hash[:a] }

Biểu tượng :avẫn là một đối tượng trong bộ nhớ. Điều này làm cho các biểu tượng hiệu quả hơn nhiều so với chuỗi.

CẬP NHẬT Đây là một điểm chuẩn (lấy từ Codecademy ) thể hiện sự khác biệt về hiệu suất:

require 'benchmark'

string_AZ = Hash[("a".."z").to_a.zip((1..26).to_a)]
symbol_AZ = Hash[(:a..:z).to_a.zip((1..26).to_a)]

string_time = Benchmark.realtime do
  100_000.times { string_AZ["r"] }
end

symbol_time = Benchmark.realtime do
  100_000.times { symbol_AZ[:r] }
end

puts "String time: #{string_time} seconds."
puts "Symbol time: #{symbol_time} seconds."

Đây là kết quả của tôi cho MBP của tôi:

String time: 0.1254125550040044 seconds.
Symbol time: 0.07360960397636518 seconds.

Có một sự khác biệt rõ ràng trong việc sử dụng các chuỗi so với các ký hiệu để chỉ xác định các khóa trong hàm băm.


Tôi không chắc chắn nếu đây là trường hợp. Tôi mong đợi một triển khai Ruby sẽ thực thi cùng một mã nhiều lần, không phân tích cú pháp mã nhiều lần cho mỗi lần lặp. Ngay cả khi mỗi lần xuất hiện từ vựng "a"thực sự là một chuỗi mới, tôi nghĩ trong ví dụ của bạn sẽ có chính xác hai "a"(và một triển khai thậm chí có thể chia sẻ bộ nhớ cho đến khi một trong số chúng bị đột biến). Để tạo hàng triệu chuỗi, có lẽ bạn cần sử dụng String.new ("a"). Nhưng tôi không rành về Ruby, nên có lẽ tôi đã sai.
coredump

1
Trong một trong những bài học của Codecademy, họ tạo ra một điểm chuẩn cho các chuỗi so với các biểu tượng, giống như ví dụ của tôi. Tôi sẽ thêm nó vào câu trả lời.
Keith Mattix

1
Cảm ơn đã thêm điểm chuẩn. Thử nghiệm của bạn cho thấy mức tăng dự kiến ​​thu được bằng cách sử dụng các ký hiệu thay vì chuỗi, do thử nghiệm nhanh hơn trong hàm băm (so sánh nhận dạng so với chuỗi), nhưng không có cách nào chúng ta có thể suy ra rằng các chuỗi được phân bổ ở mỗi lần lặp. Tôi đã thêm một phiên bản string_AZ[String.new("r")]để xem điều đó có tạo ra sự khác biệt không. Tôi nhận được 21ms cho chuỗi (phiên bản gốc), 7ms với các ký hiệu và 50ms với chuỗi mới mỗi lần. Vì vậy, tôi sẽ nói rằng các chuỗi không được phân bổ nhiều như với "r"phiên bản nghĩa đen .
coredump

1
À, vậy là tôi đã đào thêm một chút và trong Ruby 2.1, các chuỗi trên thực tế được chia sẻ. Tôi dường như đã bỏ lỡ bản cập nhật đó; cảm ơn đã chỉ ra rằng Quay trở lại câu hỏi ban đầu, tôi nghĩ cả hai điểm chuẩn đều cho thấy tiện ích của biểu tượng và chuỗi.
Keith Mattix
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.