Toán tử chấm trong Haskell: cần giải thích thêm


86

Tôi đang cố gắng hiểu toán tử dấu chấm đang làm gì trong mã Haskell này:

sumEuler = sum . (map euler) . mkList

Toàn bộ mã nguồn ở bên dưới.

Sự hiểu biết của tôi

Toán tử dấu chấm đang lấy hai hàm sumvà kết quả của map eulervà kết quả của mkListlàm đầu vào.

Nhưng, sumkhông phải một hàm nó là đối số của hàm, phải không? Chuyện gì đang xảy ra ở đây vậy?

Ngoài ra, những gì đang (map euler)làm?

mkList :: Int -> [Int]
mkList n = [1..n-1]

euler :: Int -> Int
euler n = length (filter (relprime n) (mkList n))

sumEuler :: Int -> Int
sumEuler = sum . (map euler) . mkList

Câu trả lời:


138

Nói một cách đơn giản, .là thành phần hàm, giống như trong toán học:

f (g x) = (f . g) x

Trong trường hợp của bạn, bạn đang tạo một hàm mới, hàm sumEulerđó cũng có thể được định nghĩa như sau:

sumEuler x = sum (map euler (mkList x))

Kiểu trong ví dụ của bạn được gọi là kiểu "không có điểm" - các đối số của hàm bị bỏ qua. Điều này làm cho mã rõ ràng hơn trong nhiều trường hợp. (Có thể khó dò dẫm lần đầu tiên bạn nhìn thấy nó, nhưng bạn sẽ quen sau một thời gian. Đó là một thành ngữ Haskell phổ biến.)

Nếu bạn vẫn còn nhầm lẫn, có thể hữu ích khi liên hệ .với một cái gì đó như đường ống UNIX. Nếu fđầu ra của 's trở thành gđầu vào, đầu ra của nó trở thành hđầu vào của nó, bạn sẽ viết điều đó trên dòng lệnh như thế f < x | g | h. Trong Haskell, .hoạt động giống như UNIX |, nhưng "ngược" - h . g . f $ x. Tôi thấy ký hiệu này khá hữu ích khi xử lý một danh sách. Thay vì một số cấu trúc khó sử dụng như map (\x -> x * 2 + 10) [1..10], bạn có thể chỉ cần viết (+10) . (*2) <$> [1..10]. (Và, nếu bạn chỉ muốn áp dụng hàm đó cho một giá trị duy nhất; đó là (+10) . (*2) $ 10. Nhất quán!)

Haskell wiki có một bài viết hay với một số chi tiết hơn: http://www.haskell.org/haskellwiki/Pointfree


1
Phân minh nhỏ: đoạn mã đầu tiên không thực sự hợp lệ Haskell.
SwiftsNamesake

2
@SwiftsNamesake Đối với những người trong chúng ta, những người không thông thạo Haskell, có phải bạn chỉ muốn nói rằng dấu bằng đơn không có ý nghĩa ở đây? (Vì vậy, đoạn mã đáng lẽ phải được định dạng " f (g x)= (f . g) x"?) Hay một cái gì đó khác?
user234461

1
@ user234461 Chính xác, vâng. Bạn sẽ cần ==thay thế nếu bạn muốn Haskell tiêu chuẩn hợp lệ.
SwiftsNamesake

Đoạn mã nhỏ đó chỉ là vàng. Giống như các câu trả lời khác ở đây là đúng nhưng đoạn mã đó chỉ được nhấp trực tiếp vào đầu tôi khiến tôi không cần phải đọc phần còn lại của câu trả lời.
Tarick Welling

24

Các . toán tử soạn các hàm. Ví dụ,

a . b

Trong đó ab là các hàm là một hàm mới chạy b trên các đối số của nó, sau đó a trên các kết quả đó. Ma cua ban

sumEuler = sum . (map euler) . mkList

hoàn toàn giống như:

sumEuler myArgument = sum (map euler (mkList myArgument))

nhưng hy vọng dễ đọc hơn. Lý do có những dấu hiệu xung quanh map euler là vì nó nói rõ hơn rằng có 3 chức năng được tạo thành: sum , map eulermkList - map euler là một chức năng duy nhất.


23

sumlà một hàm trong Haskell Prelude, không phải là một đối số cho sumEuler. Nó có loại

Num a => [a] -> a

Toán tử cấu thành hàm . có kiểu

(b -> c) -> (a -> b) -> a -> c

Vì vậy chúng tôi có

           euler           ::  Int -> Int
       map                 :: (a   -> b  ) -> [a  ] -> [b  ]
      (map euler)          ::                 [Int] -> [Int]
                    mkList ::          Int -> [Int]
      (map euler) . mkList ::          Int ->          [Int]
sum                        :: Num a =>                 [a  ] -> a
sum . (map euler) . mkList ::          Int ->                   Int

Lưu ý rằng đó Intthực sự là một ví dụ của Numtypeclass.


11

Các . toán tử được sử dụng cho thành phần chức năng. Cũng giống như toán học, nếu bạn có các hàm f (x) và g (x) f. g trở thành f (g (x)).

bản đồ là một chức năng tích hợp áp dụng một chức năng cho một danh sách. Bằng cách đặt hàm trong dấu ngoặc đơn, hàm được coi như một đối số. Một thuật ngữ cho điều này là cà ri . Bạn nên tra cứu điều đó.

Cái gì là nó nhận một hàm với nói hai đối số, nó áp dụng đối số euler. (map euler) phải không? và kết quả là một hàm mới, chỉ nhận một đối số.

Tổng . (bản đồ euler). mkList về cơ bản là một cách ưa thích để kết hợp tất cả những thứ đó lại với nhau. Tôi phải nói rằng, Haskell của tôi là một chút gỉ nhưng có lẽ bạn có thể tự đặt chức năng cuối cùng đó lại với nhau?


5

Toán tử chấm trong Haskell

Tôi đang cố gắng hiểu toán tử dấu chấm đang làm gì trong mã Haskell này:

sumEuler = sum . (map euler) . mkList

Câu trả lời ngắn

Mã tương đương không có dấu chấm, đó chỉ là

sumEuler = \x -> sum ((map euler) (mkList x))

hoặc không có lambda

sumEuler x = sum ((map euler) (mkList x))

vì dấu chấm (.) biểu thị thành phần chức năng.

Câu trả lời dài hơn

Trước tiên, hãy đơn giản hóa việc áp dụng một phần eulerđể map:

map_euler = map euler
sumEuler = sum . map_euler . mkList

Bây giờ chúng ta chỉ có các dấu chấm. Điều gì được chỉ ra bởi những dấu chấm này?

Từ nguồn :

(.)    :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)

Như vậy (.)toán tử soạn thảo .

Soạn, biên soạn

Trong toán học, chúng ta có thể viết thành phần của các hàm, f (x) và g (x), nghĩa là f (g (x)), dưới dạng

(f ∘ g) (x)

có thể đọc là "f soạn với g".

Vì vậy, trong Haskell, f ∘ g, hoặc f được viết bằng g, có thể được viết:

f . g

Thành phần là liên kết, có nghĩa là f (g (h (x))), được viết bằng toán tử thành phần, có thể bỏ ngoặc đơn mà không có bất kỳ sự mơ hồ nào.

Nghĩa là, vì (f ∘ g) ∘ h tương đương với f ∘ (g ∘ h), chúng ta có thể viết đơn giản f ∘ g ∘ h.

Vòng trở lại

Quay trở lại đơn giản hóa trước đó của chúng tôi, điều này:

sumEuler = sum . map_euler . mkList

chỉ có nghĩa là đó sumEulerlà thành phần chưa được áp dụng của các chức năng đó:

sumEuler = \x -> sum (map_euler (mkList x))

4

Toán tử dấu chấm áp dụng hàm bên trái ( sum) cho đầu ra của hàm bên phải. Trong trường hợp của bạn, bạn đang xâu chuỗi một số hàm với nhau - bạn đang chuyển kết quả của mkListcho (map euler), và sau đó chuyển kết quả của cái đó cho sum. Trang web này có một giới thiệu tốt về một số khái niệm.

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.