Haskell-function

Functor trong Haskell là một loại biểu diễn chức năng của các Loại khác nhau có thể được ánh xạ. Đây là một khái niệm cấp cao về việc triển khai tính đa hình. Theo các nhà phát triển Haskell, tất cả các Loại như Danh sách, Bản đồ, Cây, v.v. đều là thể hiện của Haskell Functor. Một functor là một lớp sẵn có với một định nghĩa chức năng như

class Functor f where 
   fmap :: (a -> b) -> f a -> f b 

Theo định nghĩa này, chúng ta có thể kết luận rằng Functor là một hàm nhận một hàm, chẳng hạn như, fmap () và trả về một hàm khác. Trong ví dụ trên, fmap () là một biểu diễn tổng quát của bản đồ hàm () . Trong ví dụ sau, chúng ta sẽ xem Haskell Functor hoạt động như thế nào.

main = do  
   print(map (subtract 1) [2,4,8,16])      
   print(fmap (subtract 1) [2,4,8,16])

Ở đây, chúng tôi đã sử dụng cả map () và fmap () trên một danh sách cho một phép toán trừ. Bạn có thể thấy rằng cả hai câu lệnh sẽ mang lại cùng một kết quả là danh sách chứa các phần tử [1,3,7,15].

Cả hai hàm được gọi là một hàm khác được gọi là subtract () để mang lại kết quả.

[1,3,7,15][1,3,7,15]

Sau đó, sự khác biệt giữa map và fmap là gì? Sự khác biệt nằm ở cách sử dụng của chúng. Functor cho phép chúng tôi triển khai thêm một số hàm chức năng trong các kiểu dữ liệu khác nhau, như “just” và “Nothing”.

main = do 
   print (fmap  (+7)(Just 10)) 
   print (fmap  (+7) Nothing)

Đoạn mã trên sẽ mang lại kết quả đầu ra sau trên thiết bị đầu cuối:

Just 17
Nothing

Functor ứng dụng

Một Functor Ứng dụng là một Functor bình thường với một số tính năng bổ sung được cung cấp bởi Lớp Loại Ứng dụng.

Sử dụng Functor, chúng tôi thường ánh xạ một hàm hiện có với một hàm khác được định nghĩa bên trong nó. Nhưng không có bất kỳ cách nào để ánh xạ một hàm được định nghĩa bên trong Functor với Functor khác. Đó là lý do tại sao chúng tôi có một cơ sở khác được gọi là Application Functor . Cơ sở ánh xạ này được thực hiện bởi lớp Loại ứng dụng được xác định trong mô-đun Điều khiển . Lớp này chỉ cung cấp cho chúng ta hai phương thức để làm việc: một là thuần túy và một là <*> . Sau đây là định nghĩa lớp của Ứng dụng Functor

class (Functor f) => Applicative f where   
   pure :: a -> f a   
   (<*>) :: f (a -> b) -> f a -> f b 

Theo cách triển khai, chúng ta có thể ánh xạ Functor khác bằng hai phương pháp: “Pure” và “<*>” . Phương thức “Pure” phải nhận một giá trị thuộc bất kỳ loại nào và nó sẽ luôn trả về một Functor Ứng dụng có giá trị đó.

Ví dụ sau đây cho thấy cách một Functor Ứng dụng hoạt động:

import Control.Applicative 

f1:: Int -> Int -> Int 
f1 x y = 2*x+y  
main = do  
   print(show $ f1 <$> (Just 1) <*> (Just 2) ) 

Ở đây, chúng ta đã triển khai các hàm ứng dụng trong lời gọi hàm của hàm f1 . Chương trình của chúng tôi sẽ mang lại kết quả sau.

"Just 4"

Monoids

Chúng ta đều biết Haskell định nghĩa mọi thứ dưới dạng các hàm. Trong các hàm, chúng ta có các tùy chọn để lấy đầu vào của mình làm đầu ra của hàm. Đây là những gì một Monoid là.

Một monoid là một tập hợp các chức năng và các nhà khai thác mà đầu ra không phụ thuộc vào đầu vào của nó. Hãy lấy một hàm (*) và một số nguyên (1). Bây giờ, bất kể đầu vào có thể là gì, đầu ra của nó sẽ chỉ giữ nguyên một con số. Nghĩa là, nếu bạn nhân một số với 1, bạn sẽ nhận được cùng một số.

Đây là định nghĩa Loại Class của monoid.

class Monoid m where  
   mempty :: m 
   mappend :: m -> m -> m  
   mconcat :: [m] -> m 
   mconcat = foldr mappend mempty 

Hãy xem ví dụ sau để hiểu việc sử dụng Monoid trong Haskell

multi:: Int->Int 
multi x = x * 1 
add :: Int->Int 
add x = x + 0 

main = do  
   print(multi 9)  
   print (add 7)

Mã của chúng tôi sẽ tạo ra kết quả sau: 9 7

Ở đây, hàm “multi” nhân đầu vào với “1”. Tương tự, hàm “add” thêm đầu vào bằng “0”. Trong cả hai trường hợp, đầu ra sẽ giống với đầu vào. Do đó, các hàm {(*), 1} và {(+), 0} là những ví dụ hoàn hảo về đơn chất.

Haskell – Đơn nguyên

Các đơn nguyên không là gì ngoài một loại Functor Ứng dụng với một số tính năng bổ sung. Nó là một lớp Loại điều chỉnh ba quy tắc cơ bản được gọi là quy tắc đơn nguyên .

Tất cả ba quy tắc này đều có thể áp dụng nghiêm ngặt cho một tuyên bố Monad như sau:

class Monad m where  
   return :: a -> m a 
   (>>=) :: m a -> (a -> m b) -> m b 
   (>>) :: m a -> m b -> m b 
   x >> y = x >>= \_ -> y 
   fail :: String -> m a  
   fail msg = error msg 

Ba luật cơ bản có thể áp dụng cho tuyên bố Monad là:

  • Luật Định danh Trái – Hàm trả về không thay đổi giá trị và nó không được thay đổi bất cứ điều gì trong Đơn nguyên. Nó có thể được biểu thị là “return> => mf = mf”.
  • Luật Nhận dạng Đúng – Hàm trả về không thay đổi giá trị và nó không được thay đổi bất cứ điều gì trong Đơn nguyên. Nó có thể được biểu thị là “mf> => return = mf”.
  • Tính liên kết – Theo luật này, cả Functors và cá thể Monad đều phải hoạt động theo cùng một cách. Nó có thể được biểu thị bằng toán học là “(f> ==> g)> => h = f> => (g> = h)”.

Hai luật đầu tiên lặp lại cùng một điểm, tức là, một trả về phải có hành vi nhận dạng ở cả hai phía của toán tử ràng buộc . Chúng tôi đã sử dụng rất nhiều Đơn nguyên trong các ví dụ trước đây của chúng tôi mà không nhận ra rằng chúng là Đơn nguyên. Hãy xem xét ví dụ sau, nơi chúng tôi đang sử dụng Đơn nguyên danh sách để tạo một danh sách cụ thể

main = do
   print([1..10] >>= (\x -> if odd x then [x*2] else []))

Mã này sẽ tạo ra kết quả sau: [2,6,10,14,18]

Haskell – Dây kéo

Các khóa kéo trong Haskell về cơ bản là các con trỏ trỏ đến một số vị trí cụ thể của cấu trúc dữ liệu chẳng hạn như cây .

Chúng ta hãy xem xét một cây có 5 phần tử [45,7,55,120,56] có thể được biểu diễn như một cây nhị phân hoàn hảo. Nếu tôi muốn cập nhật phần tử cuối cùng của danh sách này, thì tôi cần xem qua tất cả các phần tử để tiếp cận phần tử cuối cùng trước khi cập nhật nó. Bên phải?

Nhưng, điều gì sẽ xảy ra nếu chúng ta có thể xây dựng cây của mình theo cách mà một cây có N phần tử là tập hợp của [(N-1), N] . Sau đó, chúng ta không cần phải duyệt qua tất cả các phần tử (N-1) không mong muốn . Chúng tôi có thể cập nhật trực tiếp phần tử thứ N. Đây chính xác là khái niệm Zipper. Nó tập trung hoặc trỏ đến một vị trí cụ thể của một cây mà chúng ta có thể cập nhật giá trị đó mà không cần đi ngang qua toàn bộ cây. Trong ví dụ sau, chúng tôi đã triển khai khái niệm Zipper trong một danh sách. Theo cách tương tự, người ta có thể triển khai Zipper trong cấu trúc dữ liệu dạng cây hoặc tệp .

data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord)
type Zipper_List a = ([a],[a])    

go_Forward :: Zipper_List a -> Zipper_List a   
go_Forward (x:xs, bs) = (xs, x:bs)   
   
go_Back :: Zipper_List a -> Zipper_List a   
go_Back (xs, b:bs) = (b:xs, bs)    

main = do 
   let list_Ex = [1,2,3,4] 
   print(go_Forward (list_Ex,[]))       
   print(go_Back([4],[3,2,1]))

Khi bạn biên dịch và thực thi chương trình trên, nó sẽ tạo ra kết quả sau:

([2,3,4],[1])

([3,4],[2,1])

Ở đây chúng tôi đang tập trung vào một phần tử của toàn bộ chuỗi trong khi tiến lên hoặc trong khi lùi lại.

Trả lời