Haskell - Hướng dẫn nhanh
Haskell là một Ngôn ngữ lập trình chức năng được thiết kế đặc biệt để xử lý các ứng dụng tính toán biểu tượng và xử lý danh sách. Lập trình hàm dựa trên các hàm toán học. Bên cạnh Haskell, một số ngôn ngữ phổ biến khác tuân theo mô hình Lập trình chức năng bao gồm: Lisp, Python, Erlang, Racket, F #, Clojure, v.v.
Trong conventional programing, hướng dẫn được coi là một tập hợp các khai báo theo một cú pháp hoặc định dạng cụ thể, nhưng trong trường hợp functional programing, tất cả các tính toán được coi là sự kết hợp của các hàm toán học riêng biệt.
Hoạt động với Haskell
Haskell là một ngôn ngữ thuần chức năng được sử dụng rộng rãi. Ở đây, chúng tôi đã liệt kê ra một vài điểm khiến ngôn ngữ này trở nên đặc biệt so với các ngôn ngữ lập trình thông thường khác như Java, C, C ++, PHP, v.v.
Functional Language- Trong ngôn ngữ lập trình thông thường, chúng tôi hướng dẫn trình biên dịch một loạt các tác vụ không gì khác ngoài việc nói với máy tính của bạn "phải làm gì" và "làm như thế nào?" Nhưng trong Haskell, chúng ta sẽ nói với máy tính của mình "nó là gì?"
Laziness- Haskell là một ngôn ngữ lười biếng. Bởilazy, ý của chúng tôi là Haskell sẽ không đánh giá bất kỳ biểu hiện nào mà không có lý do. Khi công cụ đánh giá nhận thấy rằng một biểu thức cần được đánh giá, thì nó sẽ tạo ra mộtthunk data structure để thu thập tất cả thông tin cần thiết cho đánh giá cụ thể đó và một con trỏ đến thunk data structure. Công cụ đánh giá sẽ chỉ bắt đầu hoạt động khi nó được yêu cầu đánh giá biểu hiện cụ thể đó.
Modularity- Một ứng dụng Haskell không có gì khác ngoài một loạt các chức năng. Có thể nói rằng một ứng dụng Haskell là một tập hợp của rất nhiều ứng dụng Haskell nhỏ.
Statically Typed- Trong ngôn ngữ lập trình thông thường, chúng ta cần xác định một loạt các biến cùng với kiểu của chúng. Ngược lại, Haskell là một ngôn ngữ được đánh máy nghiêm ngặt. Theo thuật ngữ, ngôn ngữ được nhập đúng, chúng tôi có nghĩa là trình biên dịch Haskell đủ thông minh để tìm ra loại biến được khai báo, do đó chúng tôi không cần đề cập rõ ràng đến loại biến được sử dụng.
Maintainability - Các ứng dụng Haskell là mô-đun và do đó, rất dễ dàng và tiết kiệm chi phí để duy trì chúng.
Các chương trình chức năng đồng thời hơn và chúng tuân theo song song trong quá trình thực thi để cung cấp hiệu suất chính xác hơn và tốt hơn. Haskell không phải là ngoại lệ; nó đã được phát triển theo một cách để xử lýmultithreading một cách hiệu quả.
Chào thế giới
Đó là một ví dụ đơn giản để chứng minh sự năng động của Haskell. Hãy xem đoạn mã sau. Tất cả những gì chúng ta cần chỉ là một dòng để in "Hello Word" trên bảng điều khiển.
main = putStrLn "Hello World"
Khi trình biên dịch Haskell gặp đoạn mã trên, nó sẽ nhanh chóng tạo ra kết quả sau:
Hello World
Chúng tôi sẽ cung cấp nhiều ví dụ trong suốt hướng dẫn này để giới thiệu sức mạnh và sự đơn giản của Haskell.
Chúng tôi đã thiết lập môi trường lập trình Haskell trực tuyến tại - https://www.tutorialspoint.com/compile_haskell_online.php
Trình soạn thảo trực tuyến này có rất nhiều tùy chọn để thực hành các ví dụ lập trình Haskell. Đi tới phần đầu cuối của trang và nhập"ghci". Lệnh này tự động tải trình biên dịch Haskell và khởi động Haskell trực tuyến. Bạn sẽ nhận được kết quả đầu ra sau khi sử dụngghci chỉ huy.
sh-4.3$ ghci
GHCi,version7.8.4:http://www.haskell.org/ghc/:?forhelp
Loading package ghc-prim...linking...done.
Loading packageinteger gmp...linking... done.
Loading package base...linking...done.
Prelude>
Nếu bạn vẫn muốn sử dụng Haskell ngoại tuyến trong hệ thống cục bộ của mình, thì bạn cần tải xuống thiết lập Haskell có sẵn từ trang web chính thức của nó - https://www.haskell.org/downloads
Có ba loại khác nhau installers có sẵn trên thị trường -
Minimal Installer - Nó cung cấp GHC (Trình biên dịch Glasgow Haskell), CABAL (Kiến trúc chung cho các ứng dụng và thư viện xây dựng), và các công cụ ngăn xếp.
Stack Installer- Trong trình cài đặt này, GHC có thể được tải xuống trên nhiều nền tảng của chuỗi thu phí được quản lý. Nó sẽ cài đặt ứng dụng của bạn trên toàn cầu để có thể cập nhật các công cụ API của mình bất cứ khi nào được yêu cầu. Nó tự động giải quyết tất cả các phụ thuộc theo hướng Haskell.
Haskell Platform- Đây là cách tốt nhất để cài đặt Haskell vì nó sẽ cài đặt toàn bộ nền tảng trong máy của bạn và từ một vị trí cụ thể. Trình cài đặt này không phân phối giống như hai trình cài đặt trên.
Chúng tôi đã thấy các loại trình cài đặt khác nhau có sẵn trên thị trường, hãy xem cách sử dụng các trình cài đặt đó trong máy của chúng tôi. Trong hướng dẫn này, chúng tôi sẽ sử dụng trình cài đặt nền tảng Haskell để cài đặt trình biên dịch Haskell trong hệ thống của chúng tôi.
Môi trường thiết lập trong Windows
Để thiết lập môi trường Haskell trên máy tính Windows của bạn, hãy truy cập trang web chính thức của họ https://www.haskell.org/platform/windows.html và tải xuống Trình cài đặt theo kiến trúc có thể tùy chỉnh của bạn.
Kiểm tra kiến trúc hệ thống của bạn và tải xuống tệp thiết lập tương ứng và chạy nó. Nó sẽ cài đặt giống như bất kỳ ứng dụng Windows nào khác. Bạn có thể cần cập nhật cấu hình CABAL của hệ thống.
Môi trường thiết lập trong MAC
Để thiết lập môi trường Haskell trên hệ thống MAC của bạn, hãy truy cập trang web chính thức của họ https://www.haskell.org/platform/mac.html và tải xuống trình cài đặt Mac.
Môi trường thiết lập trong Linux
Cài đặt Haskell trên một hệ thống dựa trên Linux yêu cầu phải chạy một số lệnh không dễ dàng như MAC và Windows. Vâng, nó là mệt mỏi nhưng nó là đáng tin cậy.
Bạn có thể làm theo các bước dưới đây để cài đặt Haskell trên hệ thống Linux của mình -
Step 1 - Để thiết lập môi trường Haskell trên hệ thống Linux của bạn, hãy truy cập trang web chính thức https://www.haskell.org/platform/linux.htmlvà chọn phân phối của bạn. Bạn sẽ tìm thấy màn hình sau trên trình duyệt của mình.
Step 2- Chọn Phân phối của bạn. Trong trường hợp của chúng tôi, chúng tôi đang sử dụng Ubuntu. Sau khi chọn tùy chọn này, bạn sẽ nhận được trang sau trên màn hình với lệnh cài đặt Haskell trong hệ thống cục bộ của chúng tôi.
Step 3 - Mở một thiết bị đầu cuối bằng cách nhấn Ctrl + Alt + T. Chạy lệnh "$ sudo apt-get install haskell-platform"và nhấn Enter. Nó sẽ tự động bắt đầu tải xuống Haskell trên hệ thống của bạn sau khi xác thực bạn bằng mật khẩu gốc. Sau khi cài đặt, bạn sẽ nhận được thông báo xác nhận.
Step 4- Quay lại thiết bị đầu cuối của bạn và chạy lệnh GHCI. Khi bạn nhận được lời nhắc Prelude, bạn đã sẵn sàng sử dụng Haskell trên hệ thống cục bộ của mình.
Để thoát khỏi GHCI prolog, bạn có thể sử dụng lệnh ": thoát khỏi lối ra".
Haskell là một ngôn ngữ lập trình chức năng thuần túy, do đó nó tương tác và thông minh hơn nhiều so với các ngôn ngữ lập trình khác. Trong chương này, chúng ta sẽ tìm hiểu về các mô hình dữ liệu cơ bản của Haskell thực sự được xác định trước hoặc bằng cách nào đó được giải mã một cách thông minh vào bộ nhớ máy tính.
Trong suốt hướng dẫn này, chúng tôi sẽ sử dụng nền tảng trực tuyến Haskell có sẵn trên trang web của chúng tôi (https://www.tutorialspoint.com/codingground.htm).
Số
Haskell đủ thông minh để giải mã một số nào đó dưới dạng số. Do đó, bạn không cần phải đề cập đến kiểu của nó bên ngoài như chúng ta thường làm trong trường hợp các ngôn ngữ lập trình khác. Theo ví dụ, hãy truy cập dấu nhắc lệnh dạo đầu của bạn và chỉ cần chạy "2 + 2" và nhấn enter.
sh-4.3$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> 2+2
Kết quả là bạn sẽ nhận được kết quả sau.
4
Trong đoạn mã trên, chúng tôi chỉ chuyển hai số làm đối số cho trình biên dịch GHCI mà không xác định trước kiểu của chúng, nhưng trình biên dịch có thể dễ dàng giải mã hai mục này dưới dạng số.
Bây giờ, chúng ta hãy thử một phép tính toán học phức tạp hơn một chút và xem liệu trình biên dịch thông minh của chúng ta có cung cấp cho chúng ta kết quả chính xác hay không. Thử với "15+ (5 * 5) -40"
Prelude> 15+(5*5)-40
Biểu thức trên mang lại "0" theo đầu ra mong đợi.
0
Nhân vật
Giống như các con số, Haskell có thể xác định một cách thông minh một ký tự được đưa vào làm đầu vào cho nó. Đi tới dấu nhắc lệnh Haskell của bạn và nhập bất kỳ ký tự nào có dấu ngoặc kép hoặc đơn.
Hãy để chúng tôi cung cấp dòng sau làm đầu vào và kiểm tra đầu ra của nó.
Prelude> :t "a"
Nó sẽ tạo ra kết quả sau:
"a" :: [Char]
Hãy nhớ rằng bạn sử dụng (:t) trong khi cung cấp đầu vào. Trong ví dụ trên,(:t)là bao gồm loại cụ thể liên quan đến đầu vào. Chúng ta sẽ tìm hiểu thêm về loại này trong các chương sắp tới.
Hãy xem ví dụ sau, nơi chúng tôi đang chuyển một số đầu vào không hợp lệ dưới dạng ký tự, điều này dẫn đến lỗi.
Prelude> :t a
<interactive>:1:1: Not in scope: 'a'
Prelude> a
<interactive>:4:1: Not in scope: 'a'
Bằng thông báo lỗi "<interactive>: 4: 1: Not in scope:` a '", trình biên dịch Haskell đang cảnh báo chúng tôi rằng nó không thể nhận dạng đầu vào của bạn. Haskell là một loại ngôn ngữ mà mọi thứ được biểu diễn bằng một con số.
Haskell tuân theo kiểu mã hóa ASCII thông thường. Chúng ta hãy xem ví dụ sau để hiểu thêm -
Prelude> '\97'
'a'
Prelude> '\67'
'C'
Xem cách đầu vào của bạn được giải mã thành định dạng ASCII.
Chuỗi
A stringkhông là gì ngoài một tập hợp các nhân vật. Không có cú pháp cụ thể để sử dụng chuỗi, nhưng Haskell tuân theo phong cách thông thường là biểu diễn một chuỗi với dấu ngoặc kép.
Hãy xem ví dụ sau, nơi chúng ta đang chuyển chuỗi “Tutorialspoint.com”.
Prelude> :t "tutorialspoint.com"
Nó sẽ tạo ra kết quả sau trên màn hình:
"tutorialspoint.com" :: [Char]
Xem cách toàn bộ chuỗi đã được giải mã dưới dạng một mảng chỉ Char. Chúng ta hãy chuyển sang kiểu dữ liệu khác và cú pháp của nó. Khi chúng tôi bắt đầu thực hành, chúng tôi sẽ quen với tất cả các loại dữ liệu và việc sử dụng nó.
Boolean
Kiểu dữ liệu Boolean cũng khá đơn giản giống như kiểu dữ liệu khác. Hãy xem ví dụ sau, nơi chúng ta sẽ sử dụng các phép toán Boolean khác nhau bằng cách sử dụng một số đầu vào Boolean như "True" hoặc "False".
Prelude> True && True
True
Prelude> True && False
False
Prelude> True || True
True
Prelude> True || False
True
Trong ví dụ trên, chúng ta không cần đề cập rằng "True" và "False" là các giá trị Boolean. Bản thân Haskell có thể giải mã nó và thực hiện các thao tác tương ứng. Hãy để chúng tôi sửa đổi đầu vào của chúng tôi bằng "true" hoặc "false".
Prelude> true
Nó sẽ tạo ra kết quả sau:
<interactive>:9:1: Not in scope: 'true'
Trong ví dụ trên, Haskell không thể phân biệt giữa giá trị "true" và giá trị số, do đó đầu vào của chúng ta "true" không phải là số. Do đó, trình biên dịch Haskell gặp lỗi cho biết rằng đầu vào của chúng tôi không thuộc phạm vi của nó.
Danh sách và Tổng hợp danh sách
Giống như các kiểu dữ liệu khác, Listcũng là một kiểu dữ liệu rất hữu ích được sử dụng trong Haskell. Theo ví dụ, [a, b, c] là một danh sách các ký tự, do đó, theo định nghĩa, Danh sách là một tập hợp cùng kiểu dữ liệu được phân tách bằng dấu phẩy.
Giống như các kiểu dữ liệu khác, bạn không cần khai báo Danh sách dưới dạng Danh sách. Haskell đủ thông minh để giải mã thông tin đầu vào của bạn bằng cách xem cú pháp được sử dụng trong biểu thức.
Hãy xem ví dụ sau đây cho thấy cách Haskell xử lý một Danh sách.
Prelude> [1,2,3,4,5]
Nó sẽ tạo ra kết quả sau:
[1,2,3,4,5]
Các danh sách trong Haskell có bản chất đồng nhất, có nghĩa là chúng sẽ không cho phép bạn khai báo một danh sách kiểu dữ liệu khác nhau. Bất kỳ danh sách nào như [1,2,3,4,5, a, b, c, d, e, f] sẽ tạo ra lỗi.
Prelude> [1,2,3,4,5,a,b,c,d,e,f]
Mã này sẽ tạo ra lỗi sau:
<interactive>:17:12: Not in scope: 'a'
<interactive>:17:14: Not in scope: 'b'
<interactive>:17:16: Not in scope: 'c'
<interactive>:17:18: Not in scope: 'd'
<interactive>:17:20: Not in scope: 'e'
<interactive>:17:22: Not in scope: 'f'
Danh sách hiểu
Hiểu danh sách là quá trình tạo một danh sách bằng cách sử dụng biểu thức toán học. Hãy xem ví dụ sau, nơi chúng ta đang tạo một danh sách bằng cách sử dụng biểu thức toán học ở định dạng [output | phạm vi, điều kiện].
Prelude> [x*2| x<-[1..10]]
[2,4,6,8,10,12,14,16,18,20]
Prelude> [x*2| x<-[1..5]]
[2,4,6,8,10]
Prelude> [x| x<-[1..5]]
[1,2,3,4,5]
Phương pháp tạo một Danh sách sử dụng biểu thức toán học này được gọi là List Comprehension.
Tuple
Haskell cung cấp một cách khác để khai báo nhiều giá trị trong một kiểu dữ liệu duy nhất. Nó được biết đến nhưTuple. Một Tuple có thể được coi là một Danh sách, tuy nhiên có một số khác biệt về kỹ thuật giữa Tuple và một Danh sách.
Tuple là kiểu dữ liệu bất biến, vì chúng ta không thể sửa đổi số lượng phần tử trong thời gian chạy, trong khi Danh sách là kiểu dữ liệu có thể thay đổi.
Mặt khác, Danh sách là một kiểu dữ liệu đồng nhất, nhưng Tuple không đồng nhất về bản chất, bởi vì một Tuple có thể chứa các kiểu dữ liệu khác nhau bên trong nó.
Các bộ số được biểu diễn bằng một dấu ngoặc đơn. Hãy xem ví dụ sau để biết cách Haskell đối xử với Tuple.
Prelude> (1,1,'a')
Nó sẽ tạo ra kết quả sau:
(1,1,'a')
Trong ví dụ trên, chúng tôi đã sử dụng một Tuple với hai number biến kiểu và một char biến kiểu.
Trong chương này, chúng ta sẽ tìm hiểu về các toán tử khác nhau được sử dụng trong Haskell. Giống như các ngôn ngữ lập trình khác, Haskell xử lý thông minh một số phép toán cơ bản như cộng, trừ, nhân, ... Trong các chương sắp tới, chúng ta sẽ tìm hiểu thêm về các toán tử khác nhau và cách sử dụng chúng.
Trong chương này, chúng tôi sẽ sử dụng các toán tử khác nhau trong Haskell bằng nền tảng trực tuyến của chúng tôi (https://www.tutorialspoint.com/codingground.htm). Hãy nhớ rằng chúng tôi chỉ đang sử dụnginteger nhập số vì chúng ta sẽ tìm hiểu thêm về decimal đánh số trong các chương tiếp theo.
Toán tử bổ sung
Như tên cho thấy, toán tử cộng (+) được sử dụng cho hàm cộng. Mã mẫu sau đây cho thấy cách bạn có thể thêm hai số nguyên trong Haskell:
main = do
let var1 = 2
let var2 = 3
putStrLn "The addition of the two numbers is:"
print(var1 + var2)
Trong tệp trên, chúng tôi đã tạo hai biến riêng biệt var1 và var2. Cuối cùng, chúng tôi in kết quả bằng cách sử dụngadditionnhà điều hành. Sử dụngcompile và execute để chạy mã của bạn.
Mã này sẽ tạo ra kết quả sau trên màn hình:
The addition of the two numbers is:
5
Toán tử phép trừ
Như tên cho thấy, toán tử này được sử dụng cho phép toán trừ. Mã mẫu sau đây cho thấy cách bạn có thể trừ hai số nguyên trong Haskell:
main = do
let var1 = 10
let var2 = 6
putStrLn "The Subtraction of the two numbers is:"
print(var1 - var2)
Trong ví dụ này, chúng tôi đã tạo hai biến var1 và var2. Sau đó, chúng ta sử dụng toán tử trừ (-) để trừ hai giá trị.
Mã này sẽ tạo ra kết quả sau trên màn hình:
The Subtraction of the two numbers is:
4
Toán tử nhân
Toán tử này được sử dụng cho các phép toán nhân. Đoạn mã sau đây cho thấy cách nhân hai số trong Haskell bằng cách sử dụng Toán tử nhân -
main = do
let var1 = 2
let var2 = 3
putStrLn "The Multiplication of the Two Numbers is:"
print(var1 * var2)
Mã này sẽ tạo ra kết quả sau, khi bạn chạy nó trong nền tảng trực tuyến của chúng tôi -
The Multiplication of the Two Numbers is:
6
Người điều hành bộ phận
Hãy xem đoạn mã sau. Nó chỉ ra cách bạn có thể chia hai số trong Haskell -
main = do
let var1 = 12
let var2 = 3
putStrLn "The Division of the Two Numbers is:"
print(var1/var2)
Nó sẽ tạo ra kết quả sau:
The Division of the Two Numbers is:
4.0
Toán tử dãy / dãy
Sequence hoặc Range là một toán tử đặc biệt trong Haskell. Nó được ký hiệu là "(..)". Bạn có thể sử dụng toán tử này trong khi khai báo danh sách với một chuỗi giá trị.
Nếu bạn muốn in tất cả các giá trị từ 1 đến 10, thì bạn có thể sử dụng một cái gì đó như "[1..10]". Tương tự, nếu bạn muốn tạo tất cả các bảng chữ cái từ "a" đến "z", thì bạn chỉ cần nhập"[a..z]".
Đoạn mã sau cho biết cách bạn có thể sử dụng toán tử Trình tự để in tất cả các giá trị từ 1 đến 10 -
main :: IO()
main = do
print [1..10]
Nó sẽ tạo ra kết quả sau:
[1,2,3,4,5,6,7,8,9,10]
Ra quyết định là một tính năng cho phép người lập trình áp dụng một điều kiện trong dòng mã. Người lập trình có thể thực hiện một tập hợp các lệnh tùy thuộc vào một điều kiện xác định trước. Lưu đồ sau đây cho thấy cấu trúc ra quyết định của Haskell:
Haskell cung cấp các loại tuyên bố ra quyết định sau:
Sr.No. | Tuyên bố & Mô tả |
---|---|
1 | câu lệnh if – else Một if tuyên bố với một elsetuyên bố. Hướng dẫn trongelse khối sẽ chỉ thực thi khi điều kiện Boolean đã cho không thỏa mãn. |
2 | Câu lệnh if-else lồng nhau Nhiều if các khối tiếp theo là else khối |
Haskell là một ngôn ngữ chức năng và nó được gõ đúng, có nghĩa là kiểu dữ liệu được sử dụng trong toàn bộ ứng dụng sẽ được trình biên dịch biết tại thời điểm biên dịch.
Loại sẵn có
Trong Haskell, mọi câu lệnh được coi là một biểu thức toán học và phạm trù của biểu thức này được gọi là Type. Bạn có thể nói rằng "Kiểu" là kiểu dữ liệu của biểu thức được sử dụng tại thời điểm biên dịch.
Để tìm hiểu thêm về Type, chúng ta sẽ sử dụng lệnh ": t". Nói một cách chung chung,Type có thể được coi là một giá trị, trong khi Type Classcó thể được coi là một tập hợp các loại Tương tự. Trong chương này, chúng ta sẽ tìm hiểu về các loại Inbuilt khác nhau.
Int
Intlà một lớp kiểu đại diện cho dữ liệu kiểu Số nguyên. Mọi số nguyên trong phạm vi từ 2147483647 đến -2147483647 đều nằm dướiIntloại lớp. Trong ví dụ sau, hàmfType() sẽ hoạt động theo kiểu được xác định.
fType :: Int -> Int -> Int
fType x y = x*x + y*y
main = print (fType 2 4)
Ở đây, chúng tôi đã thiết lập loại hàm fType() như int. Hàm có haiint giá trị và trả về một intgiá trị. Nếu bạn biên dịch và thực thi đoạn mã này, thì nó sẽ tạo ra kết quả sau:
sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts
sh-4.3$ main
20
Số nguyên
Integer có thể được coi là một tập hợp siêu của Int. Giá trị này không bị giới hạn bởi bất kỳ số nào, do đó một Số nguyên có thể có độ dài bất kỳ mà không có bất kỳ giới hạn nào. Để thấy sự khác biệt cơ bản giữaInt và Integer loại, hãy để chúng tôi sửa đổi mã trên như sau:
fType :: Int -> Int -> Int
fType x y = x*x + y*y
main = print (fType 212124454 44545454454554545445454544545)
Nếu bạn biên dịch đoạn mã trên, thông báo lỗi sau sẽ được đưa ra:
main.hs:3:31: Warning:
Literal 44545454454554545445454544545 is out of the Int range -
9223372036854775808..9223372036854775807
Linking main ...
Lỗi này xảy ra do hàm fType () của chúng tôi mong đợi một giá trị kiểu Int và chúng tôi đang chuyển một số giá trị kiểu Int lớn thực. Để tránh lỗi này, Hãy để chúng tôi sửa đổi kiểu "Int" bằng "Số nguyên" và quan sát sự khác biệt.
fType :: Integer -> Integer -> Integer
fType x y = x*x + y*y
main = print (fType 212124454 4454545445455454545445445454544545)
Bây giờ, nó sẽ tạo ra kết quả sau:
sh-4.3$ main
1984297512562793395882644631364297686099210302577374055141
Phao nổi
Hãy xem đoạn mã sau. Nó cho thấy cách Float type hoạt động trong Haskell -
fType :: Float -> Float -> Float
fType x y = x*x + y*y
main = print (fType 2.5 3.8)
Hàm nhận hai giá trị float làm đầu vào và mang lại một giá trị float khác làm đầu ra. Khi bạn biên dịch và thực thi mã này, nó sẽ tạo ra kết quả sau:
sh-4.3$ main
20.689999
Gấp đôi
Doublelà một số dấu phẩy động với độ chính xác gấp đôi ở cuối. Hãy xem ví dụ sau:
fType :: Double -> Double -> Double
fType x y = x*x + y*y
main = print (fType 2.56 3.81)
Khi bạn thực thi đoạn mã trên, nó sẽ tạo ra kết quả sau:
sh-4.3$ main
21.0697
Bool
Boollà một kiểu Boolean. Nó có thể là Đúng hoặc Sai. Thực thi đoạn mã sau để hiểu cách hoạt động của kiểu Bool trong Haskell:
main = do
let x = True
if x == False
then putStrLn "X matches with Bool Type"
else putStrLn "X is not a Bool Type"
Ở đây, chúng tôi đang xác định một biến "x" là Bool và so sánh nó với một giá trị Boolean khác để kiểm tra tính nguyên bản của nó. Nó sẽ tạo ra kết quả sau:
sh-4.3$ main
X is not a Bool Type
Char
Char đại diện cho các ký tự. Bất cứ điều gì trong một trích dẫn duy nhất được coi là một Nhân vật. Trong đoạn mã sau, chúng tôi đã sửa đổifType() chức năng chấp nhận giá trị Char và trả về giá trị Char dưới dạng đầu ra.
fType :: Char-> Char
fType x = 'K'
main = do
let x = 'v'
print (fType x)
Đoạn mã trên sẽ gọi fType() chức năng với một chargiá trị của 'v' nhưng nó trả về một giá trị char khác, đó là 'K'. Đây là đầu ra của nó -
sh-4.3$ main
'K'
Lưu ý rằng chúng ta sẽ không sử dụng những kiểu này một cách rõ ràng vì Haskell đủ thông minh để bắt kiểu trước khi nó được khai báo. Trong các chương tiếp theo của hướng dẫn này, chúng ta sẽ xem các kiểu khác nhau và các lớp Kiểu làm cho Haskell trở thành một ngôn ngữ được đánh máy mạnh như thế nào.
Loại EQ
EQtype class là một giao diện cung cấp chức năng kiểm tra tính bình đẳng của một biểu thức. Bất kỳ lớp Kiểu nào muốn kiểm tra tính bình đẳng của một biểu thức phải là một phần của Lớp Kiểu EQ này.
Tất cả các lớp Loại chuẩn được đề cập ở trên là một phần của EQlớp học. Bất cứ khi nào chúng tôi kiểm tra bất kỳ sự bình đẳng nào bằng cách sử dụng bất kỳ loại nào được đề cập ở trên, chúng tôi đang thực sự gọi đếnEQ loại lớp.
Trong ví dụ sau, chúng tôi đang sử dụng EQ Nhập nội bộ bằng thao tác "==" hoặc "/ =".
main = do
if 8 /= 8
then putStrLn "The values are Equal"
else putStrLn "The values are not Equal"
Nó sẽ mang lại kết quả sau:
sh-4.3$ main
The values are not Equal
Loại thông thường Lớp
Ordlà một lớp giao diện khác cung cấp cho chúng tôi chức năng đặt hàng. Tất cảtypes mà chúng tôi đã sử dụng cho đến nay là một phần của điều này Ordgiao diện. Giống như giao diện EQ, giao diện Ord có thể được gọi bằng cách sử dụng ">", "<", "<=", "> =", "so sánh".
Vui lòng tìm ví dụ dưới đây mà chúng tôi đã sử dụng chức năng "so sánh" của Loại Loại này.
main = print (4 <= 2)
Tại đây, trình biên dịch Haskell sẽ kiểm tra xem 4 có nhỏ hơn hoặc bằng 2. Vì không, mã sẽ tạo ra kết quả sau:
sh-4.3$ main
False
Chỉ
Showcó chức năng in đối số của nó dưới dạng Chuỗi. Dù có thể là đối số của nó, nó luôn in kết quả dưới dạng Chuỗi. Trong ví dụ sau, chúng tôi sẽ in toàn bộ danh sách bằng giao diện này. "show" có thể được sử dụng để gọi giao diện này.
main = print (show [1..10])
Nó sẽ tạo ra kết quả sau trên bảng điều khiển. Ở đây, dấu ngoặc kép chỉ ra rằng đó là một giá trị kiểu Chuỗi.
sh-4.3$ main
"[1,2,3,4,5,6,7,8,9,10]"
Đọc
Readgiao diện thực hiện tương tự như Hiển thị, nhưng nó sẽ không in kết quả ở định dạng Chuỗi. Trong đoạn mã sau, chúng tôi đã sử dụngread giao diện để đọc giá trị chuỗi và chuyển đổi giá trị tương tự thành giá trị Int.
main = print (readInt "12")
readInt :: String -> Int
readInt = read
Ở đây, chúng tôi đang chuyển một biến Chuỗi ("12") đến readIntphương thức này lần lượt trả về 12 (một giá trị Int) sau khi chuyển đổi. Đây là đầu ra của nó -
sh-4.3$ main
12
Enum
Enumlà một loại lớp Type khác cho phép chức năng tuần tự hoặc có thứ tự trong Haskell. Lớp Type này có thể được truy cập bằng các lệnh nhưSucc, Pred, Bool, Char, Vân vân.
Đoạn mã sau đây cho biết cách tìm giá trị kế tiếp của 12.
main = print (succ 12)
Nó sẽ tạo ra kết quả sau:
sh-4.3$ main
13
Bị ràng buộc
Tất cả các loại có giới hạn trên và dưới đều thuộc Loại Loại này. Ví dụ,Int dữ liệu kiểu có giới hạn tối đa là "9223372036854775807" và giới hạn tối thiểu là "-9223372036854775808".
Đoạn mã sau đây cho thấy cách Haskell xác định giới hạn tối đa và tối thiểu của loại Int.
main = do
print (maxBound :: Int)
print (minBound :: Int)
Nó sẽ tạo ra kết quả sau:
sh-4.3$ main
9223372036854775807
-9223372036854775808
Bây giờ, hãy cố gắng tìm giới hạn tối đa và tối thiểu của các loại Char, Float và Bool.
Không
Lớp kiểu này được sử dụng cho các phép toán số. Các kiểu như Int, Integer, Float và Double thuộc lớp Type này. Hãy xem đoạn mã sau:
main = do
print(2 :: Int)
print(2 :: Float)
Nó sẽ tạo ra kết quả sau:
sh-4.3$ main
2
2.0
Tích phân
Integralcó thể được coi là một lớp con của Lớp Loại Num. Lớp Num Type chứa tất cả các loại số, trong khi lớp Integral type chỉ được sử dụng cho các số nguyên. Int và Integer là các kiểu thuộc lớp Kiểu này.
Nổi
Giống như Integral, Floating cũng là một phần của lớp Num Type, nhưng nó chỉ chứa các số dấu phẩy động. Vì thế,Float và Double thuộc loại này.
Loại tùy chỉnh
Giống như bất kỳ ngôn ngữ lập trình nào khác, Haskell cho phép các nhà phát triển xác định các kiểu do người dùng xác định. Trong ví dụ sau, chúng tôi sẽ tạo một kiểu do người dùng xác định và sử dụng nó.
data Area = Circle Float Float Float
surface :: Area -> Float
surface (Circle _ _ r) = pi * r ^ 2
main = print (surface $ Circle 10 20 10 )
Ở đây, chúng tôi đã tạo một loại mới có tên là Area. Tiếp theo, chúng ta đang sử dụng kiểu này để tính diện tích của một hình tròn. Trong ví dụ trên, "surface" là một hàm cóArea như một đầu vào và sản xuất Float như đầu ra.
Hãy nhớ rằng "dữ liệu" là một từ khóa ở đây và tất cả các loại do người dùng xác định trong Haskell luôn bắt đầu bằng một chữ cái viết hoa.
Nó sẽ tạo ra kết quả sau:
sh-4.3$ main
314.15927
Các hàm đóng một vai trò quan trọng trong Haskell, vì nó là một ngôn ngữ lập trình hàm. Giống như các ngôn ngữ khác, Haskell có định nghĩa và khai báo chức năng của riêng nó.
Khai báo hàm bao gồm tên hàm và danh sách đối số của nó cùng với đầu ra của nó.
Định nghĩa hàm là nơi bạn thực sự xác định một hàm.
Hãy để chúng tôi lấy một ví dụ nhỏ về add chức năng để hiểu khái niệm này một cách chi tiết.
add :: Integer -> Integer -> Integer --function declaration
add x y = x + y --function definition
main = do
putStrLn "The addition of the two numbers is:"
print(add 2 5) --calling a function
Ở đây, chúng ta đã khai báo hàm của mình ở dòng đầu tiên và ở dòng thứ hai, chúng ta đã viết hàm thực của mình sẽ nhận hai đối số và tạo ra một đầu ra kiểu số nguyên.
Giống như hầu hết các ngôn ngữ khác, Haskell bắt đầu biên dịch mã từ mainphương pháp. Mã của chúng tôi sẽ tạo ra kết quả sau:
The addition of the two numbers is:
7
Khớp mẫu
Khớp mẫu là quá trình đối sánh loại biểu thức cụ thể. Nó không là gì ngoài một kỹ thuật để đơn giản hóa mã của bạn. Kỹ thuật này có thể được triển khai vào bất kỳ loại lớp Type nào. If-Else có thể được sử dụng như một tùy chọn thay thế của đối sánh mẫu.
Đối sánh mẫu có thể được coi là một biến thể của đa hình động trong đó tại thời gian chạy, các phương thức khác nhau có thể được thực thi tùy thuộc vào danh sách đối số của chúng.
Hãy xem khối mã sau. Ở đây chúng ta đã sử dụng kỹ thuật Khớp mẫu để tính giai thừa của một số.
fact :: Int -> Int
fact 0 = 1
fact n = n * fact ( n - 1 )
main = do
putStrLn "The factorial of 5 is:"
print (fact 5)
Tất cả chúng ta đều biết cách tính giai thừa của một số. Trình biên dịch sẽ bắt đầu tìm kiếm một hàm được gọi là "fact" với một đối số. Nếu đối số không bằng 0, thì số sẽ tiếp tục gọi cùng một hàm với số nhỏ hơn 1 so với đối số thực.
Khi mẫu của đối số khớp chính xác với 0, nó sẽ gọi mẫu của chúng ta là "fact 0 = 1". Mã của chúng tôi sẽ tạo ra kết quả sau:
The factorial of 5 is:
120
Lính canh
Guardslà một khái niệm rất giống với đối sánh mẫu. Trong đối sánh mẫu, chúng tôi thường đối sánh một hoặc nhiều biểu thức, nhưng chúng tôi sử dụngguards để kiểm tra một số thuộc tính của một biểu thức.
Mặc dù nó được khuyến khích sử dụng so khớp mẫu trên guards, nhưng từ quan điểm của một nhà phát triển, guardsdễ đọc và đơn giản hơn. Đối với người dùng lần đầu,guards có thể trông rất giống với câu lệnh If-Else, nhưng chúng khác nhau về mặt chức năng.
Trong đoạn mã sau, chúng tôi đã sửa đổi factorial chương trình bằng cách sử dụng khái niệm guards.
fact :: Integer -> Integer
fact n | n == 0 = 1
| n /= 0 = n * fact (n-1)
main = do
putStrLn "The factorial of 5 is:"
print (fact 5)
Ở đây, chúng tôi đã tuyên bố hai guards, được phân tách bằng "|" và gọifact chức năng từ main. Bên trong, trình biên dịch sẽ hoạt động theo cách tương tự như trong trường hợp khớp mẫu để mang lại kết quả sau:
The factorial of 5 is:
120
Mệnh đề Where
Wherelà một từ khóa hoặc hàm có sẵn có thể được sử dụng trong thời gian chạy để tạo đầu ra mong muốn. Nó có thể rất hữu ích khi việc tính toán hàm trở nên phức tạp.
Hãy xem xét một tình huống trong đó đầu vào của bạn là một biểu thức phức tạp với nhiều tham số. Trong những trường hợp như vậy, bạn có thể chia toàn bộ biểu thức thành các phần nhỏ bằng cách sử dụng mệnh đề "where".
Trong ví dụ sau, chúng ta đang sử dụng một biểu thức toán học phức tạp. Chúng tôi sẽ chỉ ra cách bạn có thể tìm nghiệm nguyên của phương trình đa thức [x ^ 2 - 8x + 6] bằng cách sử dụng Haskell.
roots :: (Float, Float, Float) -> (Float, Float)
roots (a,b,c) = (x1, x2) where
x1 = e + sqrt d / (2 * a)
x2 = e - sqrt d / (2 * a)
d = b * b - 4 * a * c
e = - b / (2 * a)
main = do
putStrLn "The roots of our Polynomial equation are:"
print (roots(1,-8,6))
Lưu ý mức độ phức tạp của biểu thức của chúng ta để tính các nghiệm nguyên của hàm đa thức đã cho. Nó khá phức tạp. Do đó, chúng tôi đang phá vỡ biểu thức bằng cách sử dụngwheremệnh đề. Đoạn mã trên sẽ tạo ra kết quả sau:
The roots of our Polynomial equation are:
(7.1622777,0.8377223)
Hàm đệ quy
Đệ quy là một tình huống mà một hàm gọi chính nó nhiều lần. Haskell không cung cấp bất kỳ cơ sở lặp lại bất kỳ biểu thức nào nhiều hơn một lần. Thay vào đó, Haskell muốn bạn chia toàn bộ chức năng của mình thành một tập hợp các chức năng khác nhau và sử dụng kỹ thuật đệ quy để triển khai chức năng của bạn.
Chúng ta hãy xem xét lại ví dụ đối sánh mẫu của chúng ta, nơi chúng ta đã tính giai thừa của một số. Tìm giai thừa của một số là một trường hợp cổ điển của việc sử dụng Đệ quy. Ở đây, bạn có thể, "So khớp mẫu khác với đệ quy như thế nào?" Sự khác biệt giữa hai điều này nằm ở cách chúng được sử dụng. So khớp mẫu hoạt động khi thiết lập ràng buộc đầu cuối, trong khi đệ quy là một lời gọi hàm.
Trong ví dụ sau, chúng tôi đã sử dụng cả đối sánh mẫu và đệ quy để tính giai thừa của 5.
fact :: Int -> Int
fact 0 = 1
fact n = n * fact ( n - 1 )
main = do
putStrLn "The factorial of 5 is:"
print (fact 5)
Nó sẽ tạo ra kết quả sau:
The factorial of 5 is:
120
Chức năng đặt hàng cao hơn
Cho đến bây giờ, những gì chúng ta đã thấy là các hàm Haskell có một type làm đầu vào và tạo ra một typedưới dạng đầu ra, tương tự như trong các ngôn ngữ mệnh lệnh khác. Các hàm thứ tự cao hơn là một tính năng độc đáo của Haskell, nơi bạn có thể sử dụng một hàm làm đối số đầu vào hoặc đầu ra.
Mặc dù nó là một khái niệm ảo, nhưng trong các chương trình thế giới thực, mọi chức năng mà chúng ta xác định trong Haskell đều sử dụng cơ chế bậc cao hơn để cung cấp đầu ra. Nếu bạn có cơ hội xem xét hàm thư viện của Haskell, bạn sẽ thấy rằng hầu hết các hàm thư viện đã được viết theo cách thức cao hơn.
Hãy để chúng tôi lấy một ví dụ trong đó chúng tôi sẽ nhập một bản đồ hàm bậc cao có sẵn và sử dụng bản đồ đó để triển khai một hàm bậc cao khác theo lựa chọn của chúng ta.
import Data.Char
import Prelude hiding (map)
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map func (x : abc) = func x : map func abc
main = print $ map toUpper "tutorialspoint.com"
Trong ví dụ trên, chúng tôi đã sử dụng toUpper chức năng của Type Class Charđể chuyển đổi đầu vào của chúng tôi thành chữ hoa. Ở đây, phương thức "map" đang nhận một hàm làm đối số và trả về kết quả đầu ra cần thiết. Đây là đầu ra của nó -
sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts sh-4.3$ main
"TUTORIALSPOINT.COM"
Lambda Expression
Đôi khi chúng ta phải viết một hàm chỉ được sử dụng một lần, trong suốt vòng đời của một ứng dụng. Để đối phó với loại tình huống này, các nhà phát triển Haskell sử dụng một khối ẩn danh khác được gọi làlambda expression hoặc là lambda function.
Một hàm không có định nghĩa được gọi là hàm lambda. Một hàm lambda được biểu thị bằng ký tự "\". Chúng ta hãy lấy ví dụ sau, nơi chúng ta sẽ tăng giá trị đầu vào lên 1 mà không cần tạo bất kỳ hàm nào.
main = do
putStrLn "The successor of 4 is:"
print ((\x -> x + 1) 4)
Ở đây, chúng tôi đã tạo một hàm ẩn danh không có tên. Nó lấy số nguyên 4 làm đối số và in ra giá trị đầu ra. Về cơ bản chúng ta đang vận hành một hàm mà không cần khai báo nó đúng cách. Đó là vẻ đẹp của biểu thức lambda.
Biểu thức lambda của chúng tôi sẽ tạo ra kết quả sau:
sh-4.3$ main
The successor of 4 is:
5
Cho đến bây giờ, chúng ta đã thảo luận về nhiều loại hàm Haskell và sử dụng các cách khác nhau để gọi các hàm đó. Trong chương này, chúng ta sẽ tìm hiểu về một số hàm cơ bản có thể dễ dàng sử dụng trong Haskell mà không cần nhập bất kỳ lớp Type đặc biệt nào. Hầu hết các chức năng này là một phần của các chức năng bậc cao khác.
Chức năng đầu
Headchức năng hoạt động trên một Danh sách. Nó trả về đối số đầu tiên về cơ bản là một danh sách. Trong ví dụ sau, chúng tôi đang chuyển một danh sách có 10 giá trị và chúng tôi đang tạo phần tử đầu tiên của danh sách đó bằng cách sử dụnghead chức năng.
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The first element of the list is:"
print (head x)
Nó sẽ tạo ra kết quả sau:
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The first element of the list is:
1
Chức năng đuôi
Tail là hàm bổ sung cho headchức năng. Nó cần mộtlistlàm đầu vào và tạo ra toàn bộ danh sách mà không có phần đầu. Điều đó có nghĩa là,tailhàm trả về toàn bộ danh sách mà không có phần tử đầu tiên. Hãy xem ví dụ sau:
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The tail of our list is:"
print (tail x)
Nó sẽ tạo ra kết quả sau:
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The tail of our list is:
[2,3,4,5,6,7,8,9,10]
Chức năng cuối cùng
Như tên cho thấy, nó mang lại phần tử cuối cùng của danh sách được cung cấp làm đầu vào. Kiểm tra ví dụ sau.
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The last element of our list is:"
print (last x)
Nó sẽ tạo ra kết quả sau:
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The last element of our list is:
10
Chức năng Init
Init hoạt động hoàn toàn ngược lại với tailchức năng. Nó nhận một danh sách làm đối số và trả về toàn bộ danh sách mà không có mục nhập cuối cùng.
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "Our list without the last entry:"
print (init x)
Bây giờ, hãy quan sát đầu ra của nó -
Our list is:
[1,2,3,4,5,6,7,8,9,10]
Our list without the last entry:
[1,2,3,4,5,6,7,8,9]
Hàm Null
Null là một hàm kiểm tra Boolean hoạt động trên một Chuỗi và trả về True chỉ khi danh sách đã cho trống, nếu không nó sẽ trả về False. Đoạn mã sau kiểm tra xem danh sách được cung cấp có trống hay không.
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "Is our list empty?"
print (null x)
Nó sẽ tạo ra kết quả sau:
Our list is:
[1,2,3,4,5,6,7,8,9,10]
Is our list empty?
False
Chức năng Đảo ngược
Nó hoạt động trên đầu vào Chuỗi và chuyển toàn bộ đầu vào thành thứ tự ngược lại và kết quả là một đầu ra. Dưới đây là mã cơ sở cho chức năng này.
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The list in Reverse Order is:"
print (reverse x)
Nó sẽ tạo ra kết quả sau:
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The list in Reverse Order is:
[10,9,8,7,6,5,4,3,2,1]
Hàm chiều dài
Hàm này được sử dụng để tính toán độ dài của listđưa ra như một đối số. Hãy xem ví dụ sau:
main = do
let x = [1..10]
putStrLn "Our list is:"
print (x)
putStrLn "The length of this list is:"
print (length x)
Chúng tôi có 10 phần tử trong danh sách của mình, do đó mã của chúng tôi sẽ mang lại 10 phần tử như đầu ra.
Our list is:
[1,2,3,4,5,6,7,8,9,10]
The length of this list is:
10
Thực hiện chức năng
Takehàm được sử dụng để tạo một chuỗi con từ một Chuỗi khác. Đoạn mã sau đây cho thấy cách bạn có thể sử dụng hàm take trong Haskell:
main = print(take 5 ([1 .. 10]))
Mã tạo một chuỗi con chứa 5 phần tử từ danh sách được cung cấp -
[1,2,3,4,5]
Chức năng thả
Hàm này cũng được sử dụng để tạo một chuỗi con. Nó hoạt động như đối lập vớitakechức năng. Hãy xem đoạn mã sau:
main = print(drop 5 ([1 .. 10]))
Mã loại bỏ 5 phần tử đầu tiên khỏi danh sách được cung cấp và in 5 phần tử còn lại. Nó sẽ tạo ra kết quả sau:
[6,7,8,9,10]
Chức năng tối đa
Hàm này được sử dụng để tìm phần tử có giá trị lớn nhất từ danh sách được cung cấp. Hãy để chúng tôi xem cách sử dụng nó trong thực tế -
main = do
let x = [1,45,565,1245,02,2]
putStrLn "The maximum value element of the list is:"
print (maximum x)
Đoạn mã trên sẽ tạo ra kết quả sau:
The maximum value element of the list is:
1245
Chức năng tối thiểu
Hàm này được sử dụng để tìm phần tử có giá trị nhỏ nhất từ danh sách được cung cấp. Nó hoàn toàn ngược lại vớimaximum chức năng.
main = do
let x = [1,45,565,1245,02,2]
putStrLn "The minimum value element of the list is:"
print (minimum x)
Đầu ra của đoạn mã trên là -
The minimum value element of the list is:
1
Hàm Sum
Như tên cho thấy, hàm này trả về tổng của tất cả các phần tử có trong danh sách được cung cấp. Đoạn mã sau lấy một danh sách gồm 5 phần tử và trả về tổng kết của chúng dưới dạng đầu ra.
main = do
let x = [1..5]
putStrLn "Our list is:"
print (x)
putStrLn "The summation of the list elements is:"
print (sum x)
Nó sẽ tạo ra kết quả sau:
Our list is:
[1,2,3,4,5]
The summation of the list elements is:
15
Chức năng sản phẩm
Bạn có thể sử dụng hàm này để nhân tất cả các phần tử trong danh sách và in giá trị của nó.
main = do
let x = [1..5]
putStrLn "Our list is:"
print (x)
putStrLn "The multiplication of the list elements is:"
print (product x)
Mã của chúng tôi sẽ tạo ra kết quả sau:
Our list is:
[1,2,3,4,5]
The multiplication of the list elements is:
120
Chức năng Elem
Chức năng này được sử dụng để kiểm tra xem danh sách được cung cấp có chứa một phần tử cụ thể hay không. Theo đó, nó trả về mộttrue hoặc một false.
Đoạn mã sau kiểm tra xem danh sách các phần tử được cung cấp có chứa giá trị 786 hay không.
main = do
let x = [1,45,155,1785]
putStrLn "Our list is:"
print (x)
putStrLn "Does it contain 786?"
print (elem 786 (x))
Nó sẽ tạo ra kết quả sau:
Our list is:
[1,45,155,1785]
Does it contain 786?
False
Sử dụng mã tương tự để kiểm tra xem danh sách được cung cấp có chứa giá trị 1785 hay không.
Function Compositionlà quá trình sử dụng đầu ra của một chức năng này như một đầu vào của chức năng khác. Sẽ tốt hơn nếu chúng ta học toán học đằng saucomposition. Trong toán học,composition được ký hiệu bởi f{g(x)} Ở đâu g() là một hàm và đầu ra của nó được sử dụng làm đầu vào của một hàm khác, nghĩa là f().
Thành phần chức năng có thể được thực hiện bằng cách sử dụng hai chức năng bất kỳ, miễn là kiểu đầu ra của một chức năng phù hợp với kiểu đầu vào của chức năng thứ hai. Chúng tôi sử dụng toán tử dấu chấm (.) Để triển khai thành phần hàm trong Haskell.
Hãy xem đoạn mã ví dụ sau. Ở đây, chúng tôi đã sử dụng thành phần hàm để tính toán xem một số đầu vào là chẵn hay lẻ.
eveno :: Int -> Bool
noto :: Bool -> String
eveno x = if x `rem` 2 == 0
then True
else False
noto x = if x == True
then "This is an even Number"
else "This is an ODD number"
main = do
putStrLn "Example of Haskell Function composition"
print ((noto.eveno)(16))
Đây, trong main , chúng tôi đang gọi hai hàm, noto và eveno, đồng thời. Trình biên dịch đầu tiên sẽ gọi hàm"eveno()" với 16như một lập luận. Sau đó, trình biên dịch sẽ sử dụng đầu ra củaeveno phương thức như một đầu vào của noto() phương pháp.
Đầu ra của nó sẽ như sau:
Example of Haskell Function composition
"This is an even Number"
Vì chúng tôi đang cung cấp số 16 làm đầu vào (là số chẵn), eveno() hàm trả về true, trở thành đầu vào cho noto() và trả về kết quả: "Đây là một số chẵn".
Nếu bạn đã làm việc trên Java, thì bạn sẽ biết cách tất cả các lớp được liên kết vào một thư mục được gọi là package. Tương tự, Haskell có thể được coi là một tập hợp củamodules.
Haskell là một ngôn ngữ chức năng và mọi thứ được biểu thị dưới dạng một biểu thức, do đó, một Mô-đun có thể được gọi là một tập hợp các loại chức năng tương tự hoặc có liên quan.
Bạn có thể importmột chức năng từ mô-đun này sang mô-đun khác. Tất cả các câu lệnh "nhập" phải xuất hiện đầu tiên trước khi bạn bắt đầu xác định các hàm khác. Trong chương này, chúng ta sẽ tìm hiểu các tính năng khác nhau của các mô-đun Haskell.
Danh sách mô-đun
List cung cấp một số chức năng tuyệt vời để làm việc với listnhập dữ liệu. Sau khi nhập mô-đun Danh sách, bạn có nhiều chức năng theo ý của mình.
Trong ví dụ sau, chúng tôi đã sử dụng một số chức năng quan trọng có sẵn trong mô-đun Danh sách.
import Data.List
main = do
putStrLn("Different methods of List Module")
print(intersperse '.' "Tutorialspoint.com")
print(intercalate " " ["Lets","Start","with","Haskell"])
print(splitAt 7 "HaskellTutorial")
print (sort [8,5,3,2,1,6,4,2])
Ở đây, chúng ta có nhiều chức năng mà không cần xác định chúng. Đó là bởi vì các chức năng này có sẵn trong mô-đun Danh sách. Sau khi nhập mô-đun Danh sách, trình biên dịch Haskell đã cung cấp tất cả các chức năng này trong không gian tên chung. Do đó, chúng tôi có thể sử dụng các chức năng này.
Mã của chúng tôi sẽ mang lại kết quả sau:
Different methods of List Module
"T.u.t.o.r.i.a.l.s.p.o.i.n.t...c.o.m"
"Lets Start with Haskell"
("Haskell","Tutorial")
[1,2,2,3,4,5,6,8]
Mô-đun Char
Các Charmô-đun có rất nhiều chức năng được xác định trước để làm việc với loại Character. Hãy xem khối mã sau:
import Data.Char
main = do
putStrLn("Different methods of Char Module")
print(toUpper 'a')
print(words "Let us study tonight")
print(toLower 'A')
Tại đây, các chức năng toUpper và toLower đã được xác định bên trong Charmô-đun. Nó sẽ tạo ra kết quả sau:
Different methods of Char Module
'A'
["Let","us","study","tonight"]
'a'
Mô-đun bản đồ
Maplà kiểu dữ liệu kiểu cặp giá trị gia tăng không được sắp xếp. Nó là một mô-đun được sử dụng rộng rãi với nhiều chức năng hữu ích. Ví dụ sau cho thấy cách bạn có thể sử dụng một chức năng được xác định trước có sẵn trong mô-đun Bản đồ.
import Data.Map (Map)
import qualified Data.Map as Map --required for GHCI
myMap :: Integer -> Map Integer [Integer]
myMap n = Map.fromList (map makePair [1..n])
where makePair x = (x, [x])
main = print(myMap 3)
Nó sẽ tạo ra kết quả sau:
fromList [(1,[1]),(2,[2]),(3,[3])]
Đặt mô-đun
Mô-đun Set có một số chức năng được xác định trước rất hữu ích để thao tác với dữ liệu toán học. Một tập hợp được thực hiện dưới dạng cây nhị phân, vì vậy tất cả các phần tử trong một tập hợp phải là duy nhất.
Hãy xem đoạn mã ví dụ sau
import qualified Data.Set as Set
text1 = "Hey buddy"
text2 = "This tutorial is for Haskell"
main = do
let set1 = Set.fromList text1
set2 = Set.fromList text2
print(set1)
print(set2)
Ở đây, chúng tôi đang sửa đổi một Chuỗi thành một Tập hợp. Nó sẽ tạo ra kết quả sau. Quan sát rằng tập hợp đầu ra không có ký tự lặp lại.
fromList " Hbdeuy"
fromList " HTaefhiklorstu"
Mô-đun tùy chỉnh
Hãy xem cách chúng ta có thể tạo một mô-đun tùy chỉnh có thể được gọi ở các chương trình khác. Để triển khai mô-đun tùy chỉnh này, chúng tôi sẽ tạo một tệp riêng có tên"custom.hs" cùng với của chúng tôi "main.hs".
Hãy để chúng tôi tạo mô-đun tùy chỉnh và xác định một vài chức năng trong đó.
custom.hs
module Custom (
showEven,
showBoolean
) where
showEven:: Int-> Bool
showEven x = do
if x 'rem' 2 == 0
then True
else False
showBoolean :: Bool->Int
showBoolean c = do
if c == True
then 1
else 0
Mô-đun tùy chỉnh của chúng tôi đã sẵn sàng. Bây giờ, chúng ta hãy nhập nó vào một chương trình.
main.hs
import Custom
main = do
print(showEven 4)
print(showBoolean True)
Mã của chúng tôi sẽ tạo ra kết quả sau:
True
1
Các showEven hàm trả về True, vì "4" là một số chẵn. CácshowBoolean hàm trả về "1" là hàm Boolean mà chúng ta đã truyền vào hàm là "True".
Tất cả các ví dụ mà chúng ta đã thảo luận cho đến nay đều có bản chất tĩnh. Trong chương này, chúng ta sẽ học cách giao tiếp linh hoạt với người dùng. Chúng ta sẽ học các kỹ thuật đầu vào và đầu ra khác nhau được sử dụng trong Haskell.
Tệp và Luồng
Cho đến nay, chúng tôi đã mã hóa cứng tất cả các đầu vào trong chính chương trình. Chúng tôi đã lấy đầu vào từ các biến tĩnh. Bây giờ, chúng ta hãy tìm hiểu cách đọc và ghi từ một tệp bên ngoài.
Hãy để chúng tôi tạo một tệp và đặt tên là "abc.txt". Tiếp theo, nhập các dòng sau vào tệp văn bản này: "Chào mừng bạn đến với Tutorialspoint. Tại đây, bạn sẽ nhận được nguồn tốt nhất để học Haskell."
Tiếp theo, chúng ta sẽ viết đoạn mã sau để hiển thị nội dung của tệp này trên bảng điều khiển. Ở đây, chúng tôi đang sử dụng hàm readFile () đọc một tệp cho đến khi nó tìm thấy một ký tự EOF.
main = do
let file = "abc.txt"
contents <- readFile file
putStrLn contents
Đoạn mã trên sẽ đọc tệp "abc.txt" dưới dạng một Chuỗi cho đến khi nó gặp bất kỳ ký tự Cuối tệp nào. Đoạn mã này sẽ tạo ra kết quả sau.
Welcome to Tutorialspoint
Here, you will get the best resource to learn Haskell.
Quan sát rằng bất cứ thứ gì nó đang in trên thiết bị đầu cuối đều được ghi trong tệp đó.
Đối số dòng lệnh
Haskell cũng cung cấp cơ sở để vận hành một tệp thông qua dấu nhắc lệnh. Hãy để chúng tôi quay lại thiết bị đầu cuối và nhập"ghci". Sau đó, nhập nhóm lệnh sau:
let file = "abc.txt"
writeFile file "I am just experimenting here."
readFile file
Ở đây, chúng tôi đã tạo một tệp văn bản có tên "abc.txt". Tiếp theo, chúng tôi đã chèn một câu lệnh vào tệp bằng lệnhwriteFile. Cuối cùng, chúng tôi đã sử dụng lệnhreadFileđể in nội dung của tệp trên bảng điều khiển. Mã của chúng tôi sẽ tạo ra kết quả sau:
I am just experimenting here.
Ngoại lệ
An exceptioncó thể được coi là một lỗi trong mã. Đó là một tình huống mà trình biên dịch không nhận được đầu ra mong đợi trong thời gian chạy. Giống như bất kỳ ngôn ngữ lập trình tốt nào khác, Haskell cung cấp một cách để thực hiện xử lý ngoại lệ.
Nếu bạn đã quen thuộc với Java, thì bạn có thể biết khối Try-Catch nơi chúng tôi thường gặp lỗi và bắt lỗi tương tự trong catchkhối. Trong Haskell, chúng ta cũng có chức năng tương tự để bắt lỗi thời gian chạy.
Định nghĩa hàm của trytrông giống như "try :: Exception e => IO a -> IO (Either ea)". Hãy xem đoạn mã ví dụ sau. Nó chỉ ra cách bạn có thể bắt được ngoại lệ "Chia cho 0".
import Control.Exception
main = do
result <- try (evaluate (5 `div` 0)) :: IO (Either SomeException Int)
case result of
Left ex -> putStrLn $ "Caught exception: " ++ show ex
Right val -> putStrLn $ "The answer was: " ++ show val
Trong ví dụ trên, chúng tôi đã sử dụng try chức năng của Control.Exceptionmô-đun, do đó chúng tôi đang bắt trước ngoại lệ. Đoạn mã phía trên sẽ mang lại kết quả bên dưới trong màn hình.
Caught exception: divide by zero
Functortrong 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ạ. Đó là một khái niệm cấp cao của việc thực hiện đ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.
A Functor là một lớp có sẵn với định nghĩa hàm 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, fmap()và trả về một hàm khác. Trong ví dụ trên,fmap() là một đại diện tổng quát của hàm map().
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ả hai map() và fmap()trên một danh sách cho một phép toán trừ. Bạn có thể quan sát 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? 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 xác định 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àApplicative Functor. Cơ sở ánh xạ này được thực hiện bởi lớp Loại ứng dụng được định nghĩa trongControlmô-đun. Lớp này chỉ cung cấp cho chúng ta hai phương thức để làm việc: một làpure và cái kia 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ể lập bản đồ 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 tôi đã triển khai các hàm ứng dụng trong lệnh 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 dưới dạng đầu ra của hàm. Đây là những gì mộtMonoid Là.
A Monoidlà một tập hợp các hàm và toán tử trong đó đầu ra độc lập với đầ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 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 "đa" 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 chức năng{(*),1} và {(+),0} là những ví dụ hoàn hảo về monoids.
Monadskhông có gì khác 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àmonadic rules.
Tất cả ba quy tắc này đều có thể áp dụng nghiêm ngặt đối với 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 một tuyên bố Monad là:
Left Identity Law - Sự returnhàm không thay đổi giá trị và nó sẽ không thay đổi bất cứ điều gì trong Đơn nguyên. Nó có thể được biểu thị là "return> => mf = mf".
Right Identity Law - Sự returnhàm không thay đổi giá trị và nó sẽ không thay đổi bất cứ điều gì trong Đơn nguyên. Nó có thể được biểu thị là "mf> => return = mf".
Associativity- Theo luật này, cả Functors và Monad instance đề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à, return nên có hành vi nhận dạng ở cả hai phía của bind nhà điều hành.
Chúng tôi đã sử dụng rất nhiều Đơn nguyên trong các ví dụ trước 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]
Zippers 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ư tree.
Hãy để chúng tôi xem xét một tree có 5 yếu tố [45,7,55,120,56]có thể được biểu diễn dưới dạng 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 duyệt 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ó. Đúng?
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ái cây có N các phần tử là một tập hợp của [(N-1),N]. Sau đó, chúng ta không cần phải vượt qua tất cả những điều không mong muốn(N-1)các yếu tố. 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 mộttree hoặc một file cấu trúc dữ liệu.
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.