2016-02-25 18 views
5

Đây là một nỗ lực để đơn giản hóa một phần câu hỏi mà tôi đã yêu cầu here:Tôi làm cách nào để viết một Trait trong Julia với các kiểu mở?

Tôi muốn viết một số mã được đảm bảo hoạt động trên các loại đáp ứng các tiêu chí nhất định. Hãy nói rằng hôm nay tôi viết một số mã:

immutable Example 
    whatever::ASCIIString 
end 
function step_one(x::Example) 
    length(x.whatever) 
end 
function step_two(x::Int64) 
    (x * 2.5)::Float64 
end 
function combine_two_steps{X}(x::X) 
    middle = step_one(x) 
    result = step_two(middle) 
    result 
end 
x = Example("Hi!") 
combine_two_steps(x) 

Chạy công trình này:

julia> x = Example("Hi!") 
Example("Hi!") 

julia> combine_two_steps(x) 
7.5 

Sau đó một ngày khác tôi viết một số mã khác:

immutable TotallyDifferentExample 
    whatever::Bool 
end 
function step_one(x::TotallyDifferentExample) 
    if x.whatever 
     "Hurray" 
    else 
     "Boo" 
    end 
end 
function step_two(x::ASCIIString) 
    (Int64(Char(x[end])) * 1.5)::Float64 
end 

Và những gì bạn biết, chung tôi chức năng kết hợp vẫn hoạt động!

julia> y = TotallyDifferentExample(false) 
TotallyDifferentExample(false) 

julia> combine_two_steps(y) 
166.5 

Hoan hô! Nhưng, nói đó là một đêm khuya và tôi đang cố gắng thực hiện điều này trong ví dụ thứ ba. Tôi nhớ triển khai step_one nhưng tôi quên triển khai step_two!

immutable ForgetfulExample 
    whatever::Float64 
end 
function step_one(x::ForgetfulExample) 
    x.whatever+1.0 
end 

Bây giờ khi tôi chạy điều này, tôi sẽ gặp lỗi thời gian chạy!

julia> z = ForgetfulExample(1.0) 
ForgetfulExample(1.0) 

julia> combine_two_steps(z) 
ERROR: MethodError: `step_two` has no method matching step_two(::Float64) 

Bây giờ, tôi làm việc cho người quản lý sẽ KILL ME nếu tôi gặp lỗi thời gian chạy. Vì vậy, những gì tôi cần làm để cứu mạng sống của tôi là viết một Trait về cơ bản nói "nếu loại thực hiện đặc điểm này, thì an toàn để gọi combine_two_steps."

Tôi muốn viết một cái gì đó giống như

using Traits 
@traitdef ImplementsBothSteps{X} begin 
    step_one(X) -> Y 
    step_two(Y) -> Float64 
end 
function combine_two_steps{X;ImplementsBothSteps{X}}(x::X) 
    middle = step_one(x) 
    result = step_two(middle) 
    result 
end 

b/c sau đó tôi muốn biết rằng nếu combine_two_steps là bao giờ cử, sau đó nó sẽ chạy mà không tăng một lỗi mà các phương pháp này don không tồn tại.

Tương đương, istrait(ImplementsBothSteps{X}) (là đúng) tương đương với combine_two_steps sẽ chạy mà không có lỗi-từ-không tồn tại-of-yêu cầu-phương pháp.

Nhưng, như mọi người đều biết, tôi không thể sử dụng định nghĩa đặc điểm đó, bởi vì Y không có ý nghĩa. (Trong thực tế, kỳ quặc đủ mã biên dịch mà không có lỗi,

julia> @traitdef ImplementsBothSteps{X} begin 
      step_one(X) -> Y 
      step_two(Y) -> Float64 
     end 

julia> immutable Example 
      whatever::ASCIIString 
     end 

julia> function step_one(x::Example) 
      length(x.whatever)::Int64 
     end 
step_one (generic function with 1 method) 

julia> function step_two(x::Int64) 
      (x * 2.5)::Float64 
     end 
step_two (generic function with 1 method) 

julia> istrait(ImplementsBothSteps{Example}) 
false 

nhưng các loại không đáp ứng các đặc điểm mặc dù các phương pháp tồn tại cho một số Y.) Suy nghĩ đầu tiên của tôi là tôi có thể thay đổi Y một cái gì đó giống như Any

using Traits 
@traitdef ImplementsBothSteps{X} begin 
    step_one(X) -> Any 
    step_two(Any) -> Float64 
end 

nhưng điều này không quá b/c các Any thực sự được coi là một cái gì đó giống như Some, không theo nghĩa đen những Any loại (kể từ khi tôi không bao giờ thực hiện một phương pháp step_two mà có thể mất bất kỳ loại như đầu vào), nhưng một số đặc biệt loại được chia sẻ trên cả hai dòng!

Vì vậy, câu hỏi đặt ra là: bạn sẽ làm gì trong tình huống này?Bạn muốn truyền xung quanh một "spec" (ở đây dưới dạng hợp đồng được thể hiện bởi Trait) sao cho bất kỳ lập trình viên nào đáp ứng được thông số được đảm bảo để có thể sử dụng hàm combine_two_steps của bạn. trong định nghĩa của nó.

Có cách giải quyết khác không? Một cách tiếp cận tốt hơn để viết "spec" (ví dụ: "Không sử dụng Traits, sử dụng cái gì khác"?) Vv.

Nhân tiện, nó có vẻ gây tranh cãi, nhưng câu hỏi được liên kết ở trên và câu hỏi này đang đến lên thường xuyên trong một dự án tôi đang làm việc. Tôi về cơ bản bị mắc kẹt tại một rào cản gây ra bởi vấn đề này và có cách giải quyết xấu mà làm việc từng trường hợp, nhưng không có cách tiếp cận cho trường hợp chung.

Trả lời

1

Khái quát về đề xuất trong câu hỏi của tôi về việc sử dụng Any thực sự cũng có thể hoạt động, mặc dù nó xấu và không thực sự đạt được điểm. Giả sử bạn đã thực hiện phương pháp

step_one(X) -> Y 
step_two(Y) -> Z 

Sau đó, bạn có thể viết các đặc điểm như

@traitdef implements_both_steps begin 
    step_one(X) -> Any 
    step_two(Any) -> Z 
end 

Và chỉ cần thêm một phương pháp giả

function step_two(x::Any) 
    typeof(x)==Y ? step_two(x::Y) : error("Invalid type") 
end 

này có thể được gói gọn trong một macro cũng để lưu trên lặp lại mẫu, và sau đó một khi phương pháp đó được thực hiện thì đặc điểm được thỏa mãn. Đó là một hack mà tôi đã sử dụng (và các công trình) b/c nó khá đơn giản, nhưng giải pháp không phải là trong tinh thần của câu hỏi của tôi.

+0

Bạn có thể vui lòng đăng chi tiết không. –

1

Đây có phải là thỏa đáng:

@traitdef ImplementsStep2{Y} begin 
    step_two(Y) -> Float64 
end 

# consider replacing `any` with `all` 
@traitdef AnotherImplementsBothSteps{X} begin 
    step_one(X) 
    @constraints begin 
     any([istrait(ImplementsStep2{Y}) for Y in Base.return_types(step_one,(X,))]) 
    end 
end 

Với những định nghĩa đặc điểm chúng ta có:

julia> istrait(ImplementsStep2{Int64}) 
true 

julia> istrait(AnotherImplementsBothSteps{Example}) 
true 

Bí quyết là sử dụng @constraints về cơ bản làm những thứ không đơn giản. Và sử dụng Base.return_types để nhận các loại trả lại cho một phương thức. Phải thừa nhận rằng đây là một chút của một hack, nhưng đây là những gì đào của tôi đã đưa ra. Có lẽ một phiên bản tương lai của Traits.jl sẽ có các công cụ tốt hơn cho việc này.

Tôi đã sử dụng any trong định nghĩa đặc điểm. Đây là một chút lỏng lẻo. Sử dụng all có thể chặt chẽ hơn nhưng đại diện cho ràng buộc tốt hơn, tùy thuộc vào mức độ kiểm tra thời gian biên dịch là mong muốn.

Tất nhiên, cái nhìn sâu sắc của Julia và try ... catch cho phép thực hiện tất cả việc kiểm tra này vào thời gian chạy.

+0

Chiến lược sử dụng Base.return_types rất thú vị. Bạn đã viết một cách hiệu quả mã phản ánh thực hiện bước "định lượng hiện tại". Tôi thích điều này. Tôi sẽ nghĩ về nó nhiều hơn một chút. Toàn bộ điều này có thể được bao bọc thành một macro "meta", trên thực tế, để làm cho quy trình đơn giản hơn cho người dùng cuối. Tuy nhiên, tôi muốn điều tra hiệu suất của chiến lược này. Bạn có biết ngoài đỉnh đầu của bạn nếu điều này sẽ gây ra phương pháp công văn trên các đặc điểm để có một hit hiệu suất? – Philip

+0

Nếu trình biên dịch có thể suy ra các loại tham số trong khi biên dịch, không nên có hiệu suất đạt được (ngoại trừ thế hệ ban đầu và biên dịch các hàm liên quan). Mặt khác, nếu các kiểu không xác định, nó có thể có thể là một hiệu suất nhấn để suy ra hàm đúng, nhưng hàm được tạo ra được lưu trữ, vì vậy hy vọng, điều này sẽ vẫn được thực hiện một lần.Đây là câu chuyện bình thường ở Julia, khi các loại được ổn định và suy ra, hiệu suất hàng đầu đạt được (BTW một upvote lớn cho 'mauro3' người đã viết' Traits.jl'). –

+0

Các phương thức được sử dụng bởi 'Traits.jl' được giải thích trong' README.md' của github repo khá sâu (Link: https://github.com/mauro3/Traits.jl) –

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