Làm thế nào để concatMap bản đồ từ một đơn vị truyền tải có thể chuyển đổi được ra khỏi đối tượng phổ biến?


9

Tôi đang học Haskell và đang thực hiện một chương trình DB-seed đơn giản cho Yesod khi tôi tình cờ thấy hành vi này mà tôi thấy khó hiểu:

testFn :: Int -> Bool -> [Int]
testFn a b = if b then replicate 10 a else []

Phiên YesHC GHCI:

$ :t concatMap testFn [3]
concatMap testFn [3] :: Bool -> [Int]
$ (concatMap testFn [1,2,3]) True
[1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3]

Bằng cách nào đó, nó có thể "rút" ra "Bool" thứ hai từ mỗi ánh xạ thành một đối số được quấy rối.

Phiên bản cơ sở chuẩn Prelude GHCI từ chối biên dịch biểu thức này:

$ :t concatMap testFn [3]
error:
     Couldn't match type 'Bool -> [Int]' with '[b]'
      Expected type: Int -> [b]
        Actual type: Int -> Bool -> [Int]
     Probable cause: 'testFn' is applied to too few arguments
      In the first argument of 'concatMap', namely 'testFn'
      In the expression: concatMap testFn [3]

Hóa ra Yesod sử dụng thư viện truyền tải đơn âm có cái riêng concatMap:

$ :t concatMap
concatMap
  :: (MonoFoldable mono, Monoid m) =>
     (Element mono -> m) -> mono -> m

Ở cấp độ hiểu biết về Haskell hiện tại của tôi, tôi không thể tìm ra cách các loại được phân phối ở đây. Ai đó có thể giải thích cho tôi (càng nhiều người mới bắt đầu định hướng càng tốt) làm thế nào thủ thuật này được thực hiện? Phần nào testFnở trên là phù hợp với Element monoloại?

Câu trả lời:


6

Chúng tôi bắt đầu bằng cách liệt kê một số loại mà chúng tôi biết. (Chúng tôi giả vờ rằng các con số là Intđơn giản - điều này không thực sự phù hợp.)

testFn :: Int -> Bool -> [Int]
[1,2,3] :: [Int]
True :: Bool

(concatMap testFn [1,2,3]) Truelà giống như concatMap testFn [1,2,3] Truevậy, vì vậy concatMapphải có một loại khớp với tất cả các đối số đó:

concatMap :: (Int -> Bool -> [Int]) -> [Int] -> Bool -> ???

nơi ???là loại kết quả. Lưu ý rằng, do các quy tắc kết hợp, ->liên kết với bên phải, do đó, cách gõ ở trên giống như:

concatMap :: (Int -> (Bool -> [Int])) -> [Int] -> (Bool -> ???)

Hãy viết kiểu chung ở trên. Tôi đang thêm một vài khoảng trắng để đánh dấu sự giống nhau.

concatMap :: (MonoFoldable mono, Monoid m) =>
             (Element mono -> m              ) -> mono  -> m
concatMap :: (Int          -> (Bool -> [Int])) -> [Int] -> (Bool -> ???)

À ha! Chúng tôi có một trận đấu nếu chúng ta chọn mnhư Bool -> [Int], và mononhư [Int]. Nếu chúng ta làm như vậy, chúng ta sẽ thỏa mãn các ràng buộc MonoFoldable mono, Monoid m(xem bên dưới) và chúng ta cũng có Element mono ~ Int, vì vậy mọi thứ đều kiểm tra.

Chúng tôi suy luận đó ???[Int]từ định nghĩa của m.

Về những hạn chế: vì MonoFoldable [Int], có rất ít điều để nói. [Int]rõ ràng là một loại giống như danh sách với một Intloại phần tử, và điều đó đủ để biến nó thành một loại MonaFoldablevới IntElement.

Đối với Monoid (Bool -> [Int]), nó phức tạp hơn một chút. Chúng ta có bất kỳ loại chức năng nào A -> Blà một monoid nếu Blà một monoid. Điều này theo sau bằng cách thực hiện các hoạt động theo cách ngược chiều. Trong trường hợp cụ thể của chúng tôi, chúng tôi dựa vào [Int]việc là một monoid và chúng tôi nhận được:

mempty :: Bool -> [Int]
mempty = \_ -> []

(<>) :: (Bool -> [Int]) -> (Bool -> [Int]) -> (Bool -> [Int])
f <> g = \b -> f b ++ g b

1
Giải thích tuyệt vời! Cách bạn giải thích và căn chỉnh các loại chỉ là những gì tôi muốn thấy, cảm ơn!
dimsuz
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.