Trộn các thuộc tính và công cụ riêng tư và công cộng trong Raku


12
#Private attribute example
class C { 
    has $!w;                            #private attribute
    multi method w { $!w }              #getter method
    multi method w ( $_ ) {                 #setter method
        warn “Don’t go changing my w!”;   #some side action
        $!w = $_
    }  
}
my $c = C.new
$c.w( 42 )
say $c.w #prints 42
$c.w: 43
say $c.w #prints 43

#but not
$c.w = 44
Cannot modify an immutable Int (43)

cho đến nay, rất hợp lý, và sau đó

#Public attribute example
class C { 
    has $.v is rw    #public attribute with automatic accessors
}
my $c = C.new
$c.v = 42
say $c.v #prints 42

#but not
$c.v( 43 ) #or $c.v: 43
Too many positionals passed; expected 1 argument but got 2

Tôi thích tính trực tiếp của phép gán '=', nhưng tôi cần sự dễ dàng trong việc thực hiện các hành động phụ mà nhiều phương thức cung cấp. Tôi hiểu rằng đây là hai thế giới khác nhau và chúng không hòa trộn.

NHƯNG - Tôi không hiểu tại sao tôi không thể đi $ cv (43) Để đặt thuộc tính công khai

  1. Tôi cảm thấy rằng raku đang hướng dẫn tôi không trộn lẫn hai chế độ này - một số thuộc tính riêng tư và công khai và áp lực là hướng tới phương pháp (với một số: đường từ ruột kết) - đây có phải là ý định thiết kế của Raku không?
  2. Tui bỏ lỡ điều gì vậy?

Bạn có thể sử dụng Proxy
user0721090601

Làm thế nào bạn có thể sử dụng một Proxy? Ý tôi là, bộ truy cập đã trả về một container khi is rwđược chỉ định. Trả lại một Proxy sẽ không thay đổi số lượng tham số cho phép trên bộ truy cập.
Elizabeth Mattijsen

@ElizabethMattijsen Có lẽ tôi đã hiểu nhầm câu hỏi. Nhưng điều này dường như hoạt động cho những gì anh ta muốn (bật cả = foo.(foo)cài đặt) và cho phép thực hiện các tác dụng phụ trong cả hai trường hợp (nhưng không phải khi chỉ tìm nạp): tio.run/iêu
user0721090601

Câu trả lời:


13

Đây có phải là ý định thiết kế của Raku?

Thật công bằng khi nói rằng Raku không hoàn toàn không bị ảnh hưởng trong lĩnh vực này. Câu hỏi của bạn chạm vào hai chủ đề trong thiết kế của Raku, cả hai đều đáng để thảo luận.

Raku có giá trị l hạng nhất

Raku sử dụng dồi dào các giá trị l là một thứ hạng nhất. Khi chúng tôi viết:

has $.x is rw;

Phương thức được tạo là:

method x() is rw { $!x }

is rwđây chỉ ra rằng phương thức đang trả về một giá trị l - nghĩa là, một cái gì đó có thể được gán cho. Do đó, khi chúng tôi viết:

$obj.x = 42;

Đây không phải là cú pháp đường: nó thực sự là một cuộc gọi phương thức, và sau đó toán tử gán được áp dụng cho kết quả của nó. Điều này hoạt động được, bởi vì lệnh gọi phương thức trả về vùng Scalarchứa của thuộc tính, sau đó có thể được gán vào. Người ta có thể sử dụng ràng buộc để chia điều này thành hai bước, để xem nó không phải là một biến đổi cú pháp tầm thường. Ví dụ: cái này:

my $target := $obj.x;
$target = 42;

Sẽ được gán cho thuộc tính đối tượng. Cơ chế tương tự này đứng sau nhiều tính năng khác, bao gồm cả việc gán danh sách. Ví dụ: cái này:

($x, $y) = "foo", "bar";

Hoạt động bằng cách xây dựng một Listchứa đựng $x$y, và sau đó là toán tử gán trong trường hợp này lặp mỗi cặp bên này sang làm nhiệm vụ. Điều này có nghĩa là chúng ta có thể sử dụng các bộ truy cập rwđối tượng ở đó:

($obj.x, $obj.y) = "foo", "bar";

Và tất cả chỉ tự nhiên hoạt động. Đây cũng là cơ chế đằng sau việc gán cho các lát của mảng và hàm băm.

Người ta cũng có thể sử dụng Proxyđể tạo một thùng chứa giá trị l trong đó hành vi đọc và viết nó nằm dưới sự kiểm soát của bạn. Vì vậy, bạn có thể đặt các hành động phụ vào STORE. Tuy nhiên...

Raku khuyến khích các phương pháp ngữ nghĩa trên "setters"

Khi chúng tôi mô tả OO, các thuật ngữ như "đóng gói" và "ẩn dữ liệu" thường xuất hiện. Ý tưởng chính ở đây là mô hình trạng thái bên trong đối tượng - nghĩa là cách nó chọn để biểu diễn dữ liệu cần thiết để thực hiện các hành vi của nó (các phương thức) - ví dụ như được phát triển miễn phí để xử lý các yêu cầu mới. Đối tượng càng phức tạp, điều này càng trở nên tự do.

Tuy nhiên, getters và setters là các phương thức có một kết nối ngầm với nhà nước. Mặc dù chúng tôi có thể cho rằng chúng tôi đang ẩn dữ liệu vì chúng tôi đang gọi một phương thức, không truy cập trực tiếp vào trạng thái, nhưng kinh nghiệm của tôi là chúng tôi nhanh chóng kết thúc tại một nơi mà mã bên ngoài đang thực hiện các chuỗi lệnh gọi setter để đạt được một hoạt động - đó là một hình thức của tính năng chống mẫu ghen tị. Và nếu chúng ta đang làm điều đó , chắc chắn chúng ta sẽ kết thúc bằng logic bên ngoài đối tượng thực hiện pha trộn các hoạt động getter và setter để đạt được một hoạt động. Thực sự, các hoạt động này đã được đưa ra như các phương thức với một tên mô tả những gì đang đạt được. Điều này càng trở nên quan trọng hơn nếu chúng ta ở trong một môi trường đồng thời; một đối tượng được thiết kế tốt thường khá dễ bảo vệ ở ranh giới phương thức.

Điều đó nói rằng, nhiều cách sử dụng classthực sự là các loại bản ghi / sản phẩm: chúng tồn tại để đơn giản nhóm lại với nhau một loạt các mục dữ liệu. Không phải ngẫu nhiên mà .sigil không chỉ tạo ra một phụ kiện, mà còn:

  • Mở thuộc tính thành được thiết lập bởi logic khởi tạo đối tượng mặc định (nghĩa là a class Point { has $.x; has $.y; }có thể được khởi tạo là Point.new(x => 1, y => 2)) và cũng biểu hiện điều đó trong .rakuphương thức kết xuất.
  • Mở thuộc tính vào .Captuređối tượng mặc định , có nghĩa là chúng ta có thể sử dụng nó trong việc hủy hoại (ví dụ sub translated(Point (:$x, :$y)) { ... }).

Đó là những điều bạn muốn nếu bạn đang viết theo phong cách thủ tục hoặc chức năng hơn và sử dụng classnhư một phương tiện để xác định loại bản ghi.

Thiết kế Raku không được tối ưu hóa để thực hiện những điều thông minh trong setters, vì đó được coi là một điều kém để tối ưu hóa. Nó vượt quá những gì cần thiết cho một loại hồ sơ; trong một số ngôn ngữ, chúng tôi có thể lập luận rằng chúng tôi muốn xác thực những gì được chỉ định, nhưng trong Raku, chúng tôi có thể chuyển sangsubset các loại cho điều đó. Đồng thời, nếu chúng ta thực sự thực hiện một thiết kế OO, thì chúng ta muốn có một API các hành vi có ý nghĩa che giấu mô hình trạng thái, thay vì suy nghĩ về các getters / setters, điều này có xu hướng dẫn đến thất bại trong việc colocate dữ liệu và hành vi, đó là phần lớn của việc thực hiện OO.


Điểm tốt trong việc cảnh báo các Proxys (mặc dù tôi đề nghị nó ha). Lần duy nhất tôi thấy chúng hữu ích khủng khiếp là cho tôi LanguageTag. Bên trong, các $tag.regiontrả về một đối tượng kiểu Region(như nó được lưu trữ nội bộ), nhưng thực tế là, nó là vô cùng thuận tiện hơn cho người dân để nói $tag.region = "JP"hơn $tag.region.code = "JP". Và điều đó thực sự chỉ là tạm thời cho đến khi tôi có thể thể hiện một sự ép buộc Strtrong loại, ví dụ, has Region(Str) $.region is rw(yêu cầu hai tính năng ưu tiên thấp nhưng được lên kế hoạch riêng biệt)
user0721090601

Cảm ơn bạn @Jonathan vì đã dành thời gian để xây dựng cơ sở thiết kế - Tôi đã nghi ngờ một số loại dầu và nước và đối với một người không CS như tôi, tôi thực sự có được sự khác biệt giữa OO thích hợp với trạng thái ẩn và ứng dụng của lớp es như một cách thân thiện hơn để xây dựng những người nắm giữ dữ liệu bí truyền sẽ lấy bằng tiến sĩ về C ++. Và với user072 ... cho những suy nghĩ của bạn về Proxy s. Tôi đã biết về Proxy trước đây nhưng nghi ngờ rằng chúng (và / hoặc đặc điểm) là cú pháp khá thô
lỗ

p6steve: Raku thực sự được thiết kế để biến những thứ phổ biến / dễ dàng nhất trở nên siêu dễ dàng. Khi bạn đi chệch khỏi các mô hình phổ biến, điều đó luôn luôn có thể (Tôi nghĩ rằng bạn đã thấy về ba cách khác nhau để làm những gì bạn muốn cho đến nay ... và chắc chắn là nhiều hơn nữa), nhưng nó khiến bạn làm việc một chút - chỉ đủ để thực hiện chắc chắn những gì bạn đang làm là thực sự muốn bạn muốn. Nhưng thông qua các đặc điểm, proxy, tiếng lóng, v.v., bạn có thể tạo ra nó để bạn chỉ cần thêm một vài ký tự để thực sự kích hoạt một số thứ hay ho khi bạn cần.
user0721090601

7

NHƯNG - Tôi không hiểu tại sao tôi không thể đi $ cv (43) Để đặt thuộc tính công khai

Vâng, điều đó thực sự phụ thuộc vào kiến ​​trúc sư. Nhưng nghiêm túc, không, đó đơn giản không phải là cách Raku hoạt động tiêu chuẩn.

Bây giờ, nó sẽ là hoàn toàn có thể tạo ra một Attributeđặc điểm trong không gian mô-đun, một cái gì đó giống như is settable, điều đó sẽ tạo ra một phương pháp accessor thay thế mà sẽ chấp nhận một giá trị duy nhất để thiết lập giá trị. Vấn đề với việc thực hiện điều này trong cốt lõi là, tôi nghĩ rằng về cơ bản có 2 trại trên thế giới về giá trị trả về của một trình biến đổi như vậy: nó sẽ trả về giá trị mới hay giá trị ?

Vui lòng liên hệ với tôi nếu bạn quan tâm đến việc thực hiện một đặc điểm như vậy trong không gian mô-đun.


1
Cảm ơn @Elizabeth - đây là một góc độ thú vị - Tôi chỉ nhấn câu hỏi này ở đây và ở đó và không có đủ tiền hoàn trả trong việc xây dựng một đặc điểm để thực hiện mánh khóe (hoặc kỹ năng từ phía tôi). Tôi đã thực sự cố gắng tập trung vào thực tiễn mã hóa tốt nhất và phù hợp với điều đó - và hy vọng rằng thiết kế của Raku sẽ phù hợp với (các) thực tiễn tốt nhất mà tôi thu thập được.
p6steve

6

Tôi hiện đang nghi ngờ bạn vừa bị nhầm lẫn. 1 Trước khi tôi chạm vào điều đó, hãy bắt đầu lại với những gì bạn không bối rối:

Tôi thích tính trực tiếp của =bài tập, nhưng tôi cần sự dễ dàng trong việc thực hiện các hành động phụ mà nhiều phương thức cung cấp. ... Tôi không hiểu tại sao tôi không thể đi $c.v( 43 )Đặt thuộc tính công khai

Bạn có thể làm tất cả những điều này. Điều đó có nghĩa là bạn sử dụng =phép gán và nhiều phương thức và "chỉ cần đi $c.v( 43 )", tất cả cùng một lúc nếu bạn muốn:

class C {
  has $!v;
  multi method v                is rw {                  $!v }
  multi method v ( :$trace! )   is rw { say 'trace';     $!v }
  multi method v ( $new-value )       { say 'new-value'; $!v = $new-value }
}
my $c = C.new;
$c.v = 41;
say $c.v;            # 41
$c.v(:trace) = 42;   # trace
say $c.v;            # 42
$c.v(43);            # new-value
say $c.v;            # 43

Một nguồn có thể gây nhầm lẫn 1

Đằng sau hậu trường, has $.foo is rwtạo một thuộc tính và một phương thức duy nhất dọc theo dòng:

has $!foo;
method foo () is rw { $!foo }

Ở trên không hoàn toàn đúng. Với hành vi mà chúng ta đang thấy, foophương thức tự động của trình biên dịch bằng cách nào đó được khai báo theo cách mà bất kỳ phương thức mới nào cùng tên đều âm thầm làm mờ nó. 2

Vì vậy, nếu bạn muốn một hoặc nhiều phương thức tùy chỉnh có cùng tên với một thuộc tính, bạn phải sao chép thủ công phương thức được tạo tự động nếu bạn muốn giữ lại hành vi mà nó thường phải chịu trách nhiệm.

Chú thích

1 Xem câu trả lời của jnthn để biết rõ ràng, kỹ lưỡng, có thẩm quyền về ý kiến ​​của Raku về sự riêng tư so với getters / setters công cộng và những gì nó diễn ra sau hậu trường khi bạn tuyên bố getters / setters công khai (ví dụ viết has $.foo).

2 Nếu một phương thức truy cập được tạo tự động cho một thuộc tính được khai báo only, thì Raku sẽ, tôi đoán, sẽ ném một ngoại lệ nếu một phương thức có cùng tên được khai báo. Nếu nó được khai báo multi, thì nó sẽ không bị bóng nếu phương thức mới cũng được khai báo multivà sẽ ném ngoại lệ nếu không. Vì vậy, trình truy cập được tạo tự động đang được khai báo với cả không onlymultithay vào đó theo một cách nào đó cho phép tạo bóng im lặng.


Aha - cảm ơn @raiph - đó là điều tôi cảm thấy thiếu. Bây giờ nó có ý nghĩa. Mỗi Jnthn có lẽ tôi sẽ cố gắng trở thành một lập trình viên OO thực sự tốt hơn và giữ kiểu setter cho các thùng chứa dữ liệu thuần túy. Nhưng thật tốt khi biết rằng đây là trong hộp công cụ!
p6steve
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.