{-# 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ậnFalse
về thất bạig :: B -> Maybe B'
lợi nhuậnNothing
về thất bạih :: C -> Either Error C'
lợi nhuậnLeft ...
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ậnTrue
về thất bạig' :: B -> Maybe Error
lợi nhuậnJust 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'
và 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), f
→ a
, g
→ b
, h
→ c
và f'
→ a'
, g'
→ b'
, h'
→ c'
cho các chức năng "không hợp lệ", nơi a'
, b'
và 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?
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
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. –