Haskell: Máy đánh chữ vs truyền chức năng


16

Đối với tôi có vẻ như bạn luôn có thể truyền các đối số hàm thay vì sử dụng một kiểu chữ. Ví dụ, thay vì xác định kiểu chữ bình đẳng:

class Eq a where 
  (==)                  :: a -> a -> Bool

Và sử dụng nó trong các hàm khác để chỉ ra kiểu đối số phải là một thể hiện của Eq:

elem                    :: (Eq a) => a -> [a] -> Bool

Chúng ta không thể chỉ định nghĩa elemhàm của mình mà không sử dụng một kiểu chữ và thay vào đó vượt qua một đối số hàm thực hiện công việc?


2
đó gọi là từ điển đi qua. Bạn có thể nghĩ các ràng buộc kiểu chữ là các đối số ngầm.
Poscat

2
Bạn có thể làm điều đó, nhưng rõ ràng sẽ thuận tiện hơn nhiều khi không phải vượt qua một chức năng và chỉ cần sử dụng một chức năng "tiêu chuẩn" tùy thuộc vào loại.
Robin Zigmond

2
Bạn có thể đặt nó như thế, vâng. Nhưng tôi cho rằng có ít nhất một lợi thế quan trọng khác: khả năng viết các hàm đa hình hoạt động trên bất kỳ loại nào thực hiện một "giao diện" hoặc bộ tính năng cụ thể. Tôi nghĩ rằng các ràng buộc kiểu chữ thể hiện rất rõ ràng theo cách truyền các đối số hàm bổ sung không. Đặc biệt là vì "luật" buồn (ngầm) mà nhiều kiểu chữ phải đáp ứng. Một Monad mràng buộc nói với tôi nhiều hơn là truyền các đối số hàm bổ sung của các loại a -> m am a -> (a -> m b) -> m b.
Robin Zigmond


1
Phần TypeApplicationsmở rộng cho phép bạn làm cho đối số ngầm rõ ràng. (==) @Int 3 5so sánh 35cụ thể là Intgiá trị. Bạn có thể nghĩ về @Intnhư một khóa trong từ điển của các hàm đẳng thức cụ thể theo kiểu, chứ không phải là Intchính hàm so sánh cụ thể.
chepner

Câu trả lời:


19

Đúng. Đây được gọi là "phong cách từ điển". Thỉnh thoảng khi tôi đang làm một số việc đặc biệt khó khăn, tôi cần loại bỏ một kiểu chữ và biến nó thành một từ điển, bởi vì từ điển truyền qua mạnh hơn 1 , nhưng thường khá cồng kềnh, làm cho mã đơn giản về mặt khái niệm trông khá phức tạp. Đôi khi tôi sử dụng kiểu chuyển từ điển trong các ngôn ngữ không phải là Haskell để mô phỏng kiểu chữ (nhưng đã học được rằng đó thường không phải là một ý tưởng tuyệt vời như âm thanh).

Tất nhiên, bất cứ khi nào có sự khác biệt về sức mạnh biểu cảm, sẽ có sự đánh đổi. Mặc dù bạn có thể sử dụng một API nhất định theo nhiều cách hơn nếu nó được viết bằng DPS, API sẽ nhận được nhiều thông tin hơn nếu bạn không thể. Một cách điều này xuất hiện trong thực tế là Data.Set, điều này phụ thuộc vào thực tế là chỉ có một Ordtừ điển cho mỗi loại. Các Setphần tử lưu trữ được sắp xếp theo Ord, và nếu bạn xây dựng một bộ với một từ điển, sau đó chèn một phần tử bằng một phần tử khác, như có thể với DPS, bạn có thể phá vỡ Setbất biến và khiến nó bị sập. Vấn đề duy nhất này có thể được giảm thiểu bằng cách sử dụng một tồn tại ảogõ để đánh dấu từ điển, nhưng, một lần nữa, với chi phí khá phức tạp khó chịu trong API. Điều này cũng xuất hiện theo cách tương tự trong TypeableAPI.

Các bit duy nhất không xuất hiện rất thường xuyên. Những gì typeclass là tuyệt vời là viết mã cho bạn. Ví dụ,

catProcs :: (i -> Maybe String) -> (i -> Maybe String) -> (i -> Maybe String)
catProcs f g = f <> g

trong đó có hai "bộ xử lý" lấy đầu vào và có thể đưa ra đầu ra, và ghép chúng lại, làm phẳng đi Nothing, sẽ phải được viết trong DPS giống như thế này:

catProcs f g = (<>) (funcSemi (maybeSemi listSemi)) f g

Về cơ bản, chúng tôi phải đánh vần loại chúng tôi sử dụng lại, mặc dù chúng tôi đã đánh vần nó trong chữ ký loại và thậm chí nó còn thừa vì trình biên dịch đã biết tất cả các loại. Bởi vì chỉ có một cách để xây dựng một kiểu nhất định Semigroup, trình biên dịch có thể làm điều đó cho bạn. Điều này có hiệu ứng loại "lãi kép" khi bạn bắt đầu xác định nhiều trường hợp tham số và sử dụng cấu trúc của các loại để tính toán cho bạn, như trong các Data.Functor.*tổ hợp, và điều này được sử dụng rất hiệu quả deriving viatrong đó về cơ bản bạn có thể nhận được tất cả Cấu trúc đại số "tiêu chuẩn" của loại của bạn được viết cho bạn.

Và thậm chí đừng để tôi bắt đầu với MPTC và các quỹ, những thông tin đưa thông tin trở lại vào việc đánh máy và suy luận. Tôi chưa bao giờ thử chuyển đổi một thứ như vậy sang DPS - Tôi nghi ngờ nó sẽ liên quan đến việc đưa ra rất nhiều bằng chứng về sự bình đẳng loại - nhưng trong mọi trường hợp tôi chắc chắn rằng nó sẽ giúp ích cho bộ não của tôi nhiều hơn tôi sẽ cảm thấy thoải mái với.

-

1 U nless bạn sử dụng reflectiontrong trường hợp này chúng trở nên tương đương cầm quyền - nhưng reflectioncũng có thể là rườm rà để sử dụng.



Tôi rất quan tâm đến các quỹ được thể hiện thông qua DPS. Bạn có biết một số tài nguyên khuyến nghị về chủ đề này? Dù sao, giải thích rất dễ hiểu.
bob

@bob, không phải tay, nhưng nó sẽ là một khám phá thú vị. Có thể hỏi một câu hỏi mới về nó?
luqui

5

Đúng. Điều đó (được gọi là chuyển từ điển) về cơ bản là những gì trình biên dịch thực hiện đối với kiểu chữ. Đối với chức năng đó, được thực hiện theo nghĩa đen, nó sẽ trông giống như thế này:

elemBy :: (a -> a -> Bool) -> a -> [a] -> Bool
elemBy _ _ [] = False
elemBy eq x (y:ys) = eq x y || elemBy eq x ys

Gọi elemBy (==) x xsbây giờ tương đương với elem x xs. Và trong trường hợp cụ thể này, bạn có thể tiến thêm một bước: eqcó cùng một đối số đầu tiên, vì vậy bạn có thể đặt trách nhiệm của người gọi để áp dụng điều đó và kết thúc bằng điều này:

elemBy2 :: (a -> Bool) -> [a] -> Bool
elemBy2 _ [] = False
elemBy2 eqx (y:ys) = eqx y || elemBy2 eqx ys

Gọi elemBy2 (x ==) xsbây giờ tương đương với elem x xs.

... Đợi đã. Đó chỉ là any. (Và trên thực tế, trong thư viện tiêu chuẩn ,elem = any . (==) .)


Từ điển AFAIU đi qua là cách tiếp cận của Scala đối với các lớp loại mã hóa. Các đối số bổ sung có thể được khai báo là implicitvà trình biên dịch sẽ thêm chúng cho bạn từ phạm vi.
michid
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.