2015-11-28 14 views
11

Tôi đã chơi với Rust theo số porting my Score4 AI engine cho nó - dựa trên công việc về triển khai thực hiện kiểu hàm của tôi trong OCaml. Tôi đặc biệt muốn xem Rust giá với mã kiểu chức năng như thế nào.Tại sao đối số cho kết quả tìm thấy cần hai ký hiệu và?

Kết quả cuối cùng: Nó hoạt động và rất nhanh - nhanh hơn nhiều so với OCaml. Nó gần như chạm vào tốc độ của C/C++ theo phong cách bắt buộc - điều thực sự tuyệt vời.

Có một điều khiến tôi phiền lòng, tuy nhiên - tại sao tôi cần hai ký hiệu ở dòng cuối cùng của mã này?

let moves_and_scores: Vec<_> = moves_and_boards 
    .iter() 
    .map(|&(column,board)| (column, score_board(&board))) 
    .collect(); 
let target_score = if maximize_or_minimize { 
    ORANGE_WINS 
} else { 
    YELLOW_WINS 
}; 
if let Some(killer_move) = moves_and_scores.iter() 
    .find(|& &(_,score)| score==target_score) { 
     ... 

Tôi đã thêm chúng là do lỗi trình biên dịch "hướng dẫn" tôi đến nó; nhưng tôi đang cố gắng để hiểu tại sao ... Tôi đã từng lừa đề cập ở những nơi khác trong Stack Overflow để "hỏi" trình biên dịch cho tôi biết những gì một cái gì đó kiểu là:

let moves_and_scores: Vec<_> = moves_and_boards 
    .iter() 
    .map(|&(column,board)| (column, score_board(&board))) 
    .collect(); 
let() = moves_and_scores; 

... mà gây ra lỗi này:

src/main.rs:108:9: 108:11 error: mismatched types: 
expected `collections::vec::Vec<(u32, i32)>`, 
    found `()` 
(expected struct `collections::vec::Vec`, 
    found()) [E0308] 
src/main.rs:108  let() = moves_and_scores; 

... như tôi đã dự kiến, moves_and_scores là một vectơ của bộ dữ liệu: Vec<(u32, i32)>. Nhưng sau đó, các dòng tiếp theo ngay lập tức, iter()find() buộc tôi sử dụng ampersands đôi gớm ghiếc trong tham số đóng cửa:

if let Some(killer_move) = moves_and_scores.iter() 
    .find(|& &(_,score)| score==target_score) { 

Tại sao find đóng cửa cần hai ampersands? Tôi có thể thấy lý do tại sao nó có thể cần một (vượt qua tuple bằng cách tham khảo để tiết kiệm thời gian/không gian) nhưng tại sao hai? Có phải vì iter không? Tức là, việc tạo tham chiếu iter và sau đó find có thể tham chiếu trên mỗi đầu vào không, do đó tham chiếu về tham chiếu?

Nếu điều này là như vậy, không phải là điều này, được cho là, một lỗ hổng thiết kế khá xấu xí trong Rust?

Thực tế, tôi mong đợi findmap và tất cả các phần còn lại của hàm nguyên thủy là một phần của bộ sưu tập. Buộc tôi phải iter() để thực hiện bất kỳ loại công việc kiểu chức năng nào có vẻ nặng nề, và thậm chí nhiều hơn thế nếu nó ép buộc loại "ampersands kép" này trong mọi chuỗi chức năng có thể.

Tôi hy vọng tôi thiếu điều gì đó hiển nhiên - bất kỳ trợ giúp/giải thích nào được hoan nghênh nhất.

+0

Chúc mừng bạn đã quản lý cổng, đánh bại OCaml trên mã kiểu chức năng có nghĩa là bạn đã làm điều gì đó đúng! –

+0

@MatthieuM. Cảm ơn! Tôi đã hy vọng có một cách sạch hơn để xử lý các chuỗi chức năng (tức là '.iter(). Map (...). Iter(). Filter() ... .iter(). Find (...)') mà không giới thiệu thêm một mức tham chiếu ở mọi bước - nhưng có vẻ như tôi không thể tránh được nó. – ttsiodras

Trả lời

13

này đây

moves_and_scores.iter() 

mang đến cho bạn một iterator qua mượn phần tử vectơ. Nếu bạn làm theo các tài liệu API loại này là gì, bạn sẽ nhận thấy rằng nó chỉ là iterator cho một lát mượn và điều này thực hiện Iterator với Item=&T nơi T(u32, i32) trong trường hợp của bạn.

Sau đó, bạn sử dụng find lấy một biến vị ngữ có tham số &Item làm tham số. Sice Item đã là một tham chiếu trong trường hợp của bạn, vị từ phải mất một &&(u32, i32).

pub trait Iterator { 
    ... 
    fn find<P>(&mut self, predicate: P) -> Option<Self::Item> 
    where P: FnMut(&Self::Item) -> bool {...} 
    ...   ^

Nó có thể được xác định là this vì nó chỉ được phép kiểm tra mục và trả về một bool.Điều này không yêu cầu mục được truyền theo giá trị.

Nếu bạn muốn một iterator qua (u32, i32) bạn có thể viết

moves_and_scores.iter().cloned() 

cloned() chuyển đổi iterator từ một với một Item loại &T để một với một loại ItemT nếu TClone. Một cách khác để thực hiện việc này là sử dụng into_iter() thay vì iter().

moves_and_scores.into_iter() 

Sự khác biệt giữa hai là tùy chọn đầu tiên nhân bản các phần tử vay trong khi phần thứ hai tiêu thụ vectơ và di chuyển các phần tử ra khỏi nó.

Bằng cách viết các lambda như thế này

|&&(_, score)| score == target_score 

bạn destructure các "tài liệu tham khảo đôi" và tạo ra một bản sao cục bộ của i32. Điều này được cho phép từ i32 là một loại đơn giản là Copy.

Thay vì destructuring các tham số của vị ngữ của bạn, bạn cũng có thể viết

|move_and_score| move_and_score.1 == target_score 

vì chấm nhà điều hành tự động dereferences nhiều lần khi cần thiết.

+0

Cảm ơn phản hồi của bạn! Có 'iter(). Cloned()' có tác động hiệu năng không? Tôi có nghĩa là, nó thực sự phân bổ "nhái"? Ngoài ra, tôi tin rằng 'in_iter()' chắc chắn sẽ có một tác động hiệu suất, vì nó di chuyển ngữ nghĩa sẽ biến đổi vector nguồn. Tôi nghĩ rằng tốt nhất là đề xuất thứ ba của bạn - sử dụng "tự động dereference nhiều lần khi cần thiết" - phải đọc lên trên đó. – ttsiodras

+0

BTW, bất kỳ suy nghĩ nào về lý do tại sao Rust chọn không có các toán tử chức năng (bản đồ, bộ lọc, vv) như một phần của bộ sưu tập, và cần '.iter()' trước (giới thiệu một mức bổ sung "tham chiếu-indirection") ở mỗi bước) ....? – ttsiodras

+1

@ttsiodras Đối với hiệu suất: Không ai trong số đó thực sự quan trọng. Nhân bản một tuple kiểu '(u32, i32)' rất rẻ. Đừng để từ "nhân bản" làm bạn sợ. :-) Đối với simpe "đồng bằng loại dữ liệu cũ" nhân bản và sao chép về cơ bản giống nhau. Nhưng một số loại không thể được sao chép vì chúng phức tạp hơn. Chỉ trong những trường hợp đó, tôi sẽ lo lắng về nhân bản. – sellibitze

Các vấn đề liên quan