2015-02-23 28 views
5

{-# LANGUAGE LambdaCase #-}Chức năng như trường hợp của typeclasses?

Tôi có một loạt chức năng mã hóa lỗi theo nhiều cách khác nhau. Ví dụ:

  • f :: A -> Bool lợi nhuận False về thất bại
  • g :: B -> Maybe B' lợi nhuận Nothing về thất bại
  • h :: C -> Either Error C' lợi nhuận Left ... về thất bại

Tôi muốn chuỗi các hoạt động trong cùng một cách như Maybe đơn nguyên , do đó, chức năng chuỗi cần phải biết liệu mỗi chức năng thất bại trước khi tiến hành tiếp theo. Đối với điều này tôi đã viết lớp này:

class Fail a where 
    isFail :: a -> Bool 
instance Fail() where 
    isFail() = False 
instance Fail Bool where -- a 
    isFail = not 
instance Fail (Maybe a) where -- b 
    isFail = not . isJust 
instance Fail (Either a b) where -- c 
    isFail (Left _) = True 
    isFail _ = False 

Tuy nhiên, nó có thể là chức năng mà không phù hợp với tồn tại:

  • f' :: A -> Bool lợi nhuận True về thất bại
  • g' :: B -> Maybe Error lợi nhuận Just Error về thất bại (Nothing trên thành công)
  • h' :: C -> Either C' Error trả về Right ... về lỗi

Đây có thể được khắc phục bằng cách đơn giản gói chúng với chức năng mà biến đổi chúng, ví dụ:

  • f'' = not . f'.
  • g'' = (\case Nothing -> Right(); Just e -> Left e) . g'
  • h'' = (\case Left c -> Right c; Right e -> Left e) . h'

Tuy nhiên, người sử dụng của hàm chaining hy vọng để có thể kết hợp f, g, h, f', g', và h' và có họ chỉ làm việc. Anh ta sẽ không biết rằng kiểu trả về của một hàm cần phải được biến đổi trừ khi anh ta nhìn vào ngữ nghĩa của từng chức năng mà anh ta đang kết hợp, và kiểm tra xem chúng có khớp với bất kỳ ví dụ nào của Fail mà anh ta có trong phạm vi hay không. Điều này là tẻ nhạt và quá tinh tế để người dùng trung bình thậm chí còn nhận thấy, đặc biệt là với suy luận kiểu bỏ qua người dùng phải chọn đúng phiên bản.

Các chức năng này không được tạo bằng kiến ​​thức về cách chúng được sử dụng. Vì vậy, tôi có thể tạo một loại data Result a b = Fail a | Success b và tạo các hàm bao quanh mỗi hàm.Ví dụ:

  • fR = (\case True -> Sucess(); False -> Fail()) . f
  • f'R = (\case False -> Sucess(); True -> Fail()) . f'
  • gR = (\case Just a -> Sucess a; Nothing -> Fail()) . g
  • g'R = (\case Nothing -> Sucess(); Just e -> Fail e) . g'
  • hR = (\case Left e -> Fail e; Right a -> Sucess a) . h
  • h'R = (\case Right e -> Fail e; Left a -> Sucess a) . h'

Tuy nhiên, điều này cảm thấy bẩn. Những gì chúng tôi đang thực hiện chỉ là xác nhận/giải thích cách mỗi một trong số f, g, h, f', g'h' được sử dụng trong ngữ cảnh của hàm kết hợp. Có cách nào trực tiếp hơn để làm việc này không? Những gì tôi muốn chính xác là một cách để nói mà thể hiện của các Fail typeclass nên được sử dụng đối với từng chức năng, tức là, (bằng cách sử dụng tên đặt cho các trường hợp typeclass ở trên), fa, gb, hcf'a', g'b', h'c' cho các chức năng "không hợp lệ", nơi a', b'c' được định nghĩa là các trường hợp sau đây (mà chồng chéo những người trước đây, vì vậy bạn sẽ cần để có thể chọn chúng theo tên bằng cách nào đó):

instance Fail Bool where -- a' 
    isFail = id 
instance Fail (Maybe a) where -- b' 
    isFail = isJust 
instance Fail (Either a b) where -- c' 
    isFail (Right _) = True 
    isFail _ = False 

Mặc dù vậy, nó không nhất thiết phải được thực hiện thông qua typeclasses. Có lẽ có một số cách để làm điều này khác hơn với typeclasses?

+1

Nếu bạn muốn kết nối chúng với nhau như một đơn nguyên (với ký hiệu 'do') thì bạn sẽ cần phải chuyển đổi tất cả thành một loại duy nhất mà bạn có thể tạo một bản sao Monad cho. Bạn nói rằng bạn muốn hệ thống kiểu chỉ tìm ra một chức năng có nghĩa là gì bởi thất bại mà không có gợi ý hay ngữ cảnh. Về lý thuyết, tôi có thể có một số lượng vô hạn các hàm mà mỗi hàm trả về một số nguyên khác nhau biểu diễn lỗi, trình biên dịch phải biết khi nào một số nguyên cụ thể là một lỗi? Nó chỉ có một giá trị như bối cảnh. Bạn không thể mong đợi trình biên dịch viết chương trình của bạn cho bạn, nếu không chúng ta sẽ sử dụng Agda. – bheklilr

+0

Vâng, tôi không nhất thiết muốn làm một cái này, nhưng nó sẽ tương tự. Tôi không mong đợi trình biên dịch biết số nguyên nào là thất bại, tôi muốn bằng cách nào đó chỉ ra các số nguyên nào thất bại cho 'f', và chỉ ra một tập hợp khác cho' f2', v.v. Ví dụ, trong Python tôi chỉ có thể tạo ra một hàm toàn cục các hàm ánh xạ tới một số hàm tương đương với các cá thể kiểu chữ. Sau đó, nó sẽ sụp đổ trong thời gian chạy thay vì biên dịch thời gian nếu không có ánh xạ từ một số chức năng người dùng muốn sử dụng. Nhưng nó vẫn sẽ an toàn hơn. –

Trả lời

10

Đừng làm điều này. Hệ thống kiểu tĩnh của Haskell và tính minh bạch tham chiếu cung cấp cho bạn sự bảo đảm cực kỳ hữu ích: bạn có thể chắc chắn rằng một số giá trị cụ thể có nghĩa là cùng một điều , bất kể nó được tạo ra như thế nào. Không có khả năng gây nhiễu nào để can thiệp vào việc này, cũng như cách diễn giải lại thời gian chạy ” theo kiểu động của phong cách động, như bạn cần cho tác vụ mà bạn dường như hình dung.

Nếu những chức năng bạn có ở đó không tuân theo đặc điểm kỹ thuật như vậy cho phù hợp, tốt, thì điều này là xấu. Tốt hơn để loại bỏ chúng (ít nhất, ẩn chúng và chỉ xuất phiên bản được xác định lại với hành vi thống nhất). Hoặc nói với người dùng rằng họ sẽ phải sống với việc tra cứu đặc điểm kỹ thuật của từng người dùng. Nhưng đừng cố gắng hack một số cách xung quanh triệu chứng cụ thể này của các định nghĩa bị hỏng.

Một thay đổi dễ dàng, bạn có thể áp dụng để chỉ “ cờ ” các chức năng mà thất bại có nghĩa là ngược lại vì nó khác, không có gì để họ có trả về một kết quả như vậy quấn:

newtype Anti a = Anti { profail :: a } 

instance (Anti a) => Fail (Anti a) where 
    isFail (Anti a) = not $ isFail a 

Tâm trí: “ điều tương tự ” theo nghĩa có thể rất trừu tượng.Không cần cho Left được phổ biến một “ thất bại constructor ”, nó đủ rằng đó là rõ ràng rằng đó là constructor biến thể liên quan đến các đối số kiểu đầu tiên, đó là không những gì các functor/Ví dụ đơn nguyên hoạt động trên – từ đó nó tự động theo dõi rằng điều này sẽ “ có nghĩa là ” thất bại trong một ứng dụng đơn thuần.
I.e., khi bạn đã chọn đúng loại, nội dung phải vô cùng tự động; rõ ràng điều ngược lại là đúng khi bạn chỉ là tossing around booleans, vì vậy có lẽ bạn nên loại bỏ hoàn toàn ...

+0

Tôi sẽ phải đọc lại câu trả lời của bạn, nhưng, việc xuất ra các phép toán là hữu ích trong trường hợp sử dụng thực tế của tôi, thay vì các hàm tôi thực sự sử dụng các phép toán đơn điệu xuất hiện cho dù chúng đã thành công hay không. –

+1

Lưu ý rằng cơ sở 4.8 bổ sung một loại 'Alt' vào' Data.Monoid' (chuyển 'Thay thế' thành' Monoid'), do đó có thể không phải là tên tốt nhất để chọn. – dfeuer

+0

@og_loc: hoạt động đơn điệu không được xuất ra boolean để báo hiệu lỗi, không nhiều hơn các hàm bình thường. Sau này, bạn sử dụng một wrapper 'Maybe' hoặc' Either' phù hợp; trước đây nên được thực hiện với các biến áp monad tương ứng. – leftaroundabout

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