Skip to main content

Bài 02 - Kiểu dữ liệu, Khai báo và Đa hình

TÓM TẮT
  • Giới thiệu thực tế về Kiểu (types)
  • Khai báo của Hàm
  • Làm việc các Hàm
    • Biến trong Haskell
    • Hàm trung tố và tiền tố
  • Các kiểu dữ liệu phổ biến
  • Giá trị đa hình và biến kiểu
  • Làm việc Danh sách!
Video bài giảng
Chúng tôi đang dịch thuyết minh bài giảng sang tiếng Việt

Giới thiệu thực tế về Kiểu

Ký hiệu ::

Một Kiểu là một nhãn mà mọi biểu thức đều có và hạn chế việc sử dụng nó.

Chúng ta sử dụng dấu hai chấm :: để hiển thị hoặc chỉ định Kiểu biểu thức. Ví dụ, mã

myexpression :: MyType 

cho chúng ta biết rằng biểu thức myexpression có Kiểu MyType.

Các Kiểu thường dùng

Có Kiểu tiêu chuẩn thường được sử dụng trong Haskell:

  • Int và Integer cho số nguyên.
  • Float và Double cho số thực dấu phẩy động.
  • Bool cho True và False.
  • Char cho ký tự.
  • String cho chuỗi văn bản.

Làm thế nào để kiểm tra Kiểu?

Lệnh :type (hoặc viết tắt :t) trong GHCI, theo sau là bất kỳ biểu thức hợp lệ nào, cho chúng ta biết Kiểu của nó.

:type True

:type False

:t (3 < 5)

:t 'A'

:t "Hello world!"

Khai báo của một Hàm

Ký hiệu dấu hai chấm :: phải được đọc đơn giản là "thuộc Kiểu" và biểu thị khai báo Kiểu. Hãy giải thích khai báo kiểu là gì bằng ví dụ sau. Trong Haskell, một hàm bình phương đối số của nó được định nghĩa là:

square :: Int -> Int
square v = v * v

Dòng đầu tiên chứa khai báo, dòng thứ hai chứa định nghĩa của hàm square.

  • Khai báo của một Hàm là một thông báo cho toàn thế giới rằng Hàm đó tồn tại, đây là tên của nó và đây là các kiểu mà nó hoạt động.
  • Định nghĩa của hàm là thông tin về Hàm chính xác của hàm này.

trong khai báo

square :: Int -> Int

hai phần được ngăn cách nhau bởi dấu hai chấm thành

  • Tên của Hàm nằm phía bên trái và
  • Kiểu của Hàm nằm phía bên phải.

Tất cả dữ liệu trong chương trình Haskell thuộc một Kiểu cụ thể (particular type). Và vì hàm hoạt động với dữ liệu nên khai báo của nó chứa Kiểu đầu vào và đầu ra của nó, được phân tách bằng các mũi tên -> .

Khai báo của hàm bình phương square cho chúng ta biết rằng nó chấp nhận một đối số duy nhất kiểu Int và trả về một giá trị cùng kiểu Int.

Nếu có nhiều hơn một đối số, khai báo sẽ được rút ra một cách đơn giản. Ví dụ, prod khai báo hàm trả về tích của hai đối số nguyên, có thể trông giống như sau:

prod :: Int -> Int -> Int
prod x y = x * y

Nó có hai đối số kiểu Int và đầu ra của nó cũng có kiểu Int.

Trong định nghĩa của một hàm, dấu đẳng thức = phân tách mã thành hai phần.

  • Phần đầu là mã ở bên trái = và bao gồm tên của hàm và tên đối số (tên, không phải kiểu!), được phân tách bằng dấu cách.
  • Nội dung là một mã ở bên phải = thể hiện bản chất của Hàm, nội dung của nó.

Khai báo của hàm cho chúng ta biết điều gì?

Haskell là một ngôn ngữ lập trình Hàm và mỗi chương trình bao gồm các Hàm. Mỗi hàm nhận một số tham số cố định của một số Kiểu và trả về một giá trị cũng có một Kiểu. Chẳng hạn, một Hàm

not :: Bool -> Bool

nhận một tham số kiểu Bool và trả về phủ định của nó, lại là kiểu Bool.

Nhìn vào mũi tên ngoài cùng bên phải -> trong khai báo, chúng ta hiểu rằng

  • Mọi thứ ở bên trái của nó là Kiểu đối số, cũng có thể được phân tách bằng các mũi tên ->,
  • Mọi thứ ở bên phải là một Kiểu giá trị được tính toán.

Làm việc các Hàm

Biến trong Haskell (Tên/định nghĩa)

Hãy xem Hàm này:

name = "Bob"

Nếu chúng ta không có tham số, chúng ta có một hàm luôn trả về cùng một giá trị—a String —, không có vấn đề gì!

Vì vậy, chúng ta có một biểu thức Kiểu:

name :: String
name = "Bob"

Loại hàm không nhận tham số thường được gọi là Định nghĩa hoặc Tên.

Mặc dù vậy, bạn cũng có thể gọi nó là biến, vì đây là thứ mà trong hầu hết các ngôn ngữ lập trình được gọi là biến. Nhưng, "biến" không phải lúc nào cũng có nghĩa giống nhau.

Bởi vì chúng ta không thể thay đổi giá trị của một định nghĩa (biểu thức ở bên phải của = luôn cho cùng một kết quả) name và "Bob" về cơ bản là giống nhau. Và chúng ta có thể sử dụng chúng thay thế cho nhau.

Khi nói về lập trình nói chung, một biến giống như một chiếc hộp chứa một giá trị. Và tên của biến được viết ở bên cạnh hộp. Bạn có thể đặt các giá trị bên trong hộp và—trong hầu hết các ngôn ngữ lập trình—sau này bạn có thể đổi ý và thay thế giá trị bên trong hộp.

-- THIS IS NOT VALID HASKELL!!!
x = 3
7 + x -- 10
x = 5
7 + x -- 12

OOOOOOOOhhhhhh nhưng nó không phải với Haskell, không, không, không! Khi bạn nói với Haskell điều đó x có nghĩa là 3, nó sẽ có nghĩa là 3 mãi mãi!

Về mặt kỹ thuật:

Các biến của Haskell là bất biến (immutable).

Khái niệm biến của Haskell là khác nhau. Haskell có các biến, nhưng theo nghĩa toán học. Theo nghĩa là khi chúng ta nói:

x = 3
city = "Paris"
letter = 'a'
it'sTrue = True

Chúng ta đang tuyên bố rằng thuật ngữ bên trái của dấu = có thể hoán đổi cho nhau với thuật ngữ ở bên phải của =.

Và điều này cũng áp dụng cho các tham số của hàm:

volumeOfACylinder r h = pi * r^2 * h 

Trong trường hợp này, một khi chúng ta truyền giá trị cho các tham số của volumeOfACylinder, chúng ta không thể thay đổi chúng bên trong phần thân của hàm. Chúng ta có thể sử dụng lại hàm và truyền các tham số khác nhau, nhưng chúng ta không thể thay đổi chúng sau khi truyền chúng.

Ký hiệu tiền tố và tiền tố

Bạn có thể áp dụng (sử dụng) các hàm theo hai ký hiệu khác nhau: ký hiệu tiền tố và tiền tố.

Tiền tố (Prefix)

Hãy xem xét một biểu thức:

prod x y = x * y
prod 4 5

prod được sử dụng ở dạng tiền tố , tức là trước các đối số của nó .

Trung tố (Infix)

Hãy xem xét một biểu thức:

1 + 2

Dấu + thực sự là một Hàm! Và được viết ở dạng trung tố, tức là ở giữa các đối số của nó .

Các hàm dành cho dạng trung tố của ứng dụng được gọi là toán tử (operators).

Và làm cách nào để biết một hàm là trung tố hay tiền tố? Tốt…

Các hàm được xác định chỉ bằng các ký hiệu sẽ tự động được đặt thành các hàm trung tố, nếu không chúng là các hàm tiền tố.

Nhưng bạn vẫn có thể sử dụng hàm trung tố làm hàm tiền tố và ngược lại.

Trung tố (Infix) thành Tiền tố (Prefix) và ngược lại

Chúng ta sử dụng dấu ngoặc đơn xung quanh hàm trung tố để sử dụng nó làm hàm tiền tố:

(+) 1 2

Để kiểm tra Kiểu hàm trung tố, chúng ta cũng phải bao quanh tên giữa dấu ngoặc đơn:

:t (+)

Tôi chắc rằng bạn đã nhận thấy rằng khai báo Kiểu `+` trông khác với những khai báo trước đó. Đó là bởi vì nó sử dụng các kiểu đa hình và các lớp kiểu. Hôm nay chúng ta sẽ học về các kiểu đa hình và về các lớp kiểu trong các bài học sau. Hiện tại, đừng lo lắng về nó quá nhiều.

Chúng ta sử dụng backticks ` xung quanh hàm tiền tố để sử dụng nó làm hàm trung tố:

4 `prod`  5

Các Kiểu dữ liệu phổ biến

Các Kiểu số nguyên: Int Integer

  • Integer là một Kiểu chính xác tùy ý: Nó sẽ chứa bất kỳ số nguyên nào—dù lớn đến đâu—cho đến giới hạn bộ nhớ máy của bạn.

Điều này có nghĩa là bạn sẽ không bao giờ bị tràn số học, nhưng điều đó cũng có nghĩa là số học của bạn tương đối chậm.

  • Int mặt khác, có các giá trị nhất định nằm trong phạm vi $±2^{63}$ (đối với CPU 64 bit) .

Điều này giới hạn các giá trị Int có thể giữ, nhưng nó làm cho nó hiệu quả hơn.

Hãy xem điều này trong thực tế:

2^62 :: Int -- All good

2^64 :: Int -- Oh no!

2^127 :: Integer -- All good again

Nhưng còn số thực thì sao? Số có chữ số thập phân? Đó là lý do tại sao chúng ta có Float và Double.

Các Kiểu số dấu phẩy động: Float Double

Float là một dấu phẩy động thực với độ chính xác đơn (32 bit), trong khi Double là một dấu phẩy động thực với độ chính xác gấp đôi (64 bit).

Hãy xem điều gì sẽ xảy ra nếu chúng ta muốn hiển thị 20 chữ số đầu tiên của số pi (π) ở cả hai Kiểu:

3.14159265358979323846 :: Float

3.14159265358979323846 :: Double

Bạn có thể nói rằng Double waaaaaaay chính xác hơn Float.

Về mặt lý thuyết, các lý do về thời điểm sử dụng cái này hay cái kia hơi giống với Int, và Integer các trường hợp. Double có độ chính xác gấp đôi, nhưng chiếm nhiều bộ nhớ hơn vì nó sử dụng gấp đôi số bit để biểu diễn số.

NHƯNG!

Đề xuất dựa trên việc sử dụng trong thế giới thực:
  • Ngay cả khi bạn không đặc biệt quan tâm đến các giá trị chính xác, hãy sử dụng Double. Hiếm khi có bất lợi về tốc độ trong các máy tính hiện đại và với Double, bạn ít có khả năng tự bắn vào chân mình với các lỗi làm tròn.
  • Nếu bạn đang ở trong tính huống mà số tiền chính xác là rất quan trọng (ví dụ: tài chính và kế toán), bạn nên sử dụng Kiểu dữ liệu Rational hoặc Decimal. Bởi vì chúng tránh được lỗi làm tròn. Chúng ta sẽ đề cập đến chúng trong các bài học trong tương lai.

Kiểu Boolean Bool

Kiểu boolean Bool chỉ chứa hai giá trị: True và False.

Các số, ký tự và chuỗi có thể được so sánh bằng cách sử dụng các toán tử so sánh thông thường để tạo ra một Bool giá trị: $$==,~/=,~<=,~>=,~<,~~>$$

5 /= 0 -- True

3 >= 0 -- True

7.2 < 6.1 -- False

pi > 3.14 -- True

Ngoài ra còn có các toán tử &&AND ) và ||OR ) cho phép chúng ta kết hợp các giá trị:

  • Toán && tử (AND) trả về True nếu cả boolean bên trái và bên phải của nó là True.

  • Toán || tử (OR) trả về True nếu một trong số chúng là True.

    :t (&&) :t (||)

    True && False True || False

Loại ký tự Char

Char là kiểu chúng ta dùng để biểu diễn một ký tự Unicode .

Chuẩn Unicode (Unicode) là một bộ quy tắc áp đặt cách xử lý và thể hiện văn bản. Nó cần thiết vì máy tính suy nghĩ bằng các con số (số một và số không) và chúng ta phải cùng nhau quyết định số nào đại diện cho ký tự nào.

Nó thực sự phức tạp hơn một chút (xem: Mã hóa ký tự). Nhưng với mục đích của chúng ta, chúng ta chỉ muốn biết rằng chúng ta có thể sử dụng hầu hết mọi ký hiệu mà chúng ta cần bằng cách sử dụng các ký tự Unicode. Chữ cái, số và hơn 140 nghìn ký hiệu.

Chúng ta viết các giá trị kiểu Char (ký tự Unicode) giữa các dấu nháy đơn. Như thế này:

'a'
'@'
'7'

Lưu ý rằng nếu bạn viết một số được bao quanh bởi dấu nháy đơn (như trong biểu thức cuối cùng), Haskell sẽ không coi đó là một số. Nó sẽ coi nó như bất kỳ ký tự nào khác. Vì vậy, bạn không thể làm toán với '7' (có dấu nháy), nhưng bạn có thể làm toán với 7 (không có dấu nháy).

Quan trọng: Bạn chỉ có thể viết một ký tự tại một thời điểm! Một cái gì đó như 'hi' không hợp lệ với Char!

Vì vậy, làm thế nào bạn có thể viết câu đầy đủ? Tôi sẽ nói với bạn. Nhưng trước đó, chúng ta phải tìm hiểu về danh sách.

Danh sách (List)

Trong Haskell, danh sách là một cấu trúc dữ liệu đồng nhất .

Đây chỉ là một cách thú vị để nói rằng chúng là các danh sách lưu trữ các phần tử cùng Kiểu. Vì vậy, chúng ta có thể có một danh sách Int hoặc một danh sách Char nhưng chúng ta không thể có một danh sách hỗn hợp.

  • Danh sách được biểu thị bằng dấu ngoặc vuông [1,5,3,-4,0] và các giá trị trong danh sách được phân tách bằng dấu phẩy .
  • Kiểu của danh sách được thể hiện dưới dạng Kiểu phần tử mà nó chứa, được bao quanh bởi dấu ngoặc vuông. Một danh sách Kiểu [Int] chứa số lượng Kiểu Int. Một danh sách kiểu [Char] chứa các phần tử kiểu Char.
:t ['a', 'b', 'c', 'd']

:t [True,False, 3 > 2, 'a' == 'b']

String

Chuỗi đại diện cho danh sách các ký tự. Bạn có thể sử dụng Kiểu String để viết tin nhắn, giá trị chữ và số, ký hiệu, v.v. Không giống như CharString được đặt trong dấu ngoặc kép như sau:

"Hellooooooo!"

Điều đó có nghĩa là hai giá trị này giống nhau!:

['H','i','!'] == "Hi!"

Và đó cũng String là [Char] cùng một Kiểu! Cụ thể hơn, String là cú pháp ngắn (syntactic sugar - cú pháp được thiết kế để làm cho mọi thứ dễ đọc hoặc dễ diễn đạt hơn) cho [Char]! Vì vậy, bạn có thể sử dụng chúng thay thế cho nhau!

Những gì bạn không thể sử dụng thay thế cho nhau trong Haskell là dấu ngoặc đơn và dấu ngoặc kép. String (được viết trong dấu ngoặc kép) là danh sách Char (được viết bằng dấu ngoặc đơn). Đây là không giống nhau!:

:t "A"
:t 'A'

Mọi lập trình viên đều biết rằng danh sách cực kỳ hữu ích. Nhưng nếu bạn muốn kết hợp các giá trị thuộc Kiểu khác nhau thì sao? Đó là khi Tuples hữu ích!

Tuples (Bộ dữ liệu)

Tuples là cấu trúc được sử dụng để lưu trữ các phần tử không đồng nhất dưới dạng một giá trị.

Chúng ta biểu diễn các Tuples bằng cách bắt đầu bằng dấu ngoặc đơn mở, viết tất cả các phần tử được phân tách bằng dấu phẩy và kết thúc bằng dấu ngoặc đơn đóng. Đây là một ví dụ về một tuple có 3 phần tử:

('a', 3, True)

Nghe có vẻ giống danh sách, nhưng có hai điểm khác biệt chính:

  • Tuples có thể lưu trữ các phần tử thuộc Kiểu khác nhau: Như bạn có thể thấy trong ví dụ trước, các Tuples có thể lưu trữ các phần tử thuộc Kiểu khác nhau, trong khi danh sách thì không thể.
  • Tuples có kích thước cố định: Bạn có thể tăng kích thước danh sách bằng cách nối hoặc các phương tiện khác, nhưng bạn không thể tăng hoặc giảm kích thước của Tuples. Khi bạn chỉ ra rằng một bộ có N phần tử, nó sẽ luôn có N phần tử.

Và những khác biệt chính đó được phản ánh trong Kiểu của tuple.

Loại tuple phụ thuộc vào:

  • Các Kiểu phần tử của nó.
  • Thứ tự của các phần tử.
  • Số lượng của các phần tử.

Ví dụ:

:t ('a', True)

:t (True, 'a')

:t (True, 'a', 'b')

:t (True)

Như bạn có thể thấy, ('a', True) :: (Char, Bool)(True, 'a') :: (Bool, Char) và ('a', True, True) :: (Char, Bool, Bool) tất cả đều có các kiểu khác nhau. Theo như trình biên dịch biết, ba Tuples đó khác nhau giữa chúng Float và Char.

Bạn có nhận thấy rằng nếu bạn cố gắng tạo một bộ phần tử đơn lẻ, GHCi chỉ trả về phần tử đó không? (Hai biểu thức cuối cùng của khối mã trước đó.) Đó là bởi vì không có bộ một phần tử! Có một Tuples một phần tử sẽ không cung cấp thêm giá trị nào. Vì vậy, Haskell bỏ qua Tuples và chỉ đánh giá phần tử.

Giá trị đa hình và biến kiểu

Điều tuyệt vời về Kiểu là chúng bảo vệ chúng ta khỏi chính chúng ta! Nếu chúng ta nói rằng một hàm lấy đầu vào là Kiểu [Char], Haskell sẽ kiểm tra xem chúng ta có đáp ứng yêu cầu đó mỗi khi chúng ta sử dụng hàm đó không. Nếu chúng ta vượt qua a Double, trình biên dịch sẽ yêu cầu chúng ta sửa lỗi đó!

Nhưng bây giờ chúng ta có một vấn đề! Hãy tưởng tượng rằng chúng ta tạo prod hàm:

prod :: Int -> Int -> Int
prod x y = x * y

Nó hoạt động hoàn hảo cho các giá trị của Kiểu Int. Nhưng nếu chúng ta cần nó cho các giá trị kiểu thì sao Dobule ? Chúng ta biết rằng nó sẽ hoạt động vì chúng vẫn là số và công thức sẽ cung cấp câu trả lời chính xác.

Chúng ta có thể tạo một hàm mới thực hiện tương tự nhưng chỉ định một Kiểu khác:

prodForDubles :: Double -> Double -> Double
prodForDoubles x y = x * y

Về mặt kỹ thuật, nó hoạt động. Nhưng bây giờ, những gì về Float và Integer Kiểu? Nếu chúng ta cần tạo các hàm trùng lặp cho mọi trường hợp, điều này sẽ nhanh chóng trở nên không bền vững!

Các Kiểu đa hình để giải cứu!

Đa hình có nghĩa là một cái gì đó có nhiều hình thức. Và một giá trị đa hình là một giá trị có thể có nhiều Kiểu. (Ví dụ: 4 có thể là IntIntegerFloat,…)

Ví dụ, hãy tưởng tượng rằng chúng ta muốn tạo một hàm nhận một bộ có hai giá trị (còn gọi là cặp) và trả về giá trị đầu tiên. Như thế này:

first (x,y) = x

Nó nên có Kiểu nào? Tôi không đặc biệt quan tâm đến Kiểu phần tử vì tôi không làm gì với chúng! Tôi không làm số học, những thứ liên quan đến văn bản, hay bất cứ thứ gì! Hơn nữa, tôi vừa lấy lại phần tử đầu tiên, và thế là xong!

Trong những trường hợp này, chúng ta chỉ định một khai báo với các biến Kiểu!

first :: (a, b) -> a
first (x,y) = x

first ('a', "hi!")

Khai báo đó viết: " first Hàm nhận một cặp Kiểu (a, b) và trả về một giá trị Kiểu a."

Quan trọng: 

Các Kiểu cụ thể (tức là, CharBoolInt) bắt đầu bằng chữ in hoa. Nhưng Kiểu đa hình bắt đầu bằng chữ thường. Chúng ta có thể sử dụng tên dài hơn cho Kiểu đa hình, nhưng thông thường là sử dụng các chữ cái đơn lẻ (ví dụ: , ab,c) .

Hàm " first " chúng ta vừa tạo này thực sự đi kèm với Haskell, nhưng nó được đặt tên là fst! Và nó đi kèm với đối tác của nó : snd!:

:t fst
:t snd

fst (1,2)
snd (1,2)

a và b là các biến kiểu, nghĩa là chúng có thể thuộc bất kỳ kiểu nào. Và bất kể Kiểu nào, giá trị được trả về first phải cùng Kiểu với phần tử đầu tiên của cặp (vì cả hai đều thuộc Kiểu a).

Bằng cách sử dụng các biến kiểu, chúng ta có thể sử dụng first hàm với các cặp kiểu bất kỳ (giá trị đa hình)!

Lưu ý rằng a và b cả hai CÓ THỂ thuộc bất kỳ Kiểu nào VÀ Kiểu khác nhau. Nhưng chúng KHÔNG PHẢI như vậy. Bạn có thể sử dụng first trên một bộ với các giá trị cùng Kiểu: ('a','b') :: (Char, Char).

Một ví dụ khác về hàm đa hình là hàm head và tail.

Bạn có thể sử dụng head để lấy phần tử đầu tiên của danh sách và tail lấy tất cả các phần tử của danh sách ngoại trừ phần tử đầu tiên.

list = [1,2,3,4]
list

:t head
head list

:t tail
tail list

Chúng ta không quan tâm đến Kiểu cụ thể. Chúng ta chỉ trích xuất một phần tử. Vì vậy, tham số là một danh sách đa hình (một danh sách thuộc bất kỳ Kiểu nào, hãy gọi nó là [a]). Và kết quả phải là một phần tử cùng Kiểu với các phần tử trong danh sách. Đó là lý do tại sao nó phải như vậy a.

Bây giờ chúng ta đã quen thuộc với tất cả Kiểu này, hãy vui vẻ với các danh sách! (Chúng ta sẽ giải trí với các Tuples sau khi học cách khớp mẫu. Như vậy sẽ thú vị hơn.)

Làm việc với Danh sách!

Mỗi phần tử có một chỉ mục được xác định bởi vị trí của nó trong danh sách — bắt đầu từ 0 (không).

Chúng ta sử dụng !! toán tử để truy cập một phần tử cụ thể trong danh sách bằng cách sử dụng chỉ mục của nó:

:t (!!)
"abc" !! 1
[12,13,16,18] !! 3

Tuples không có chỉ số, vì vậy người ta không thể dễ dàng trích xuất các phần tử của Tuples như thế này. Tuy nhiên, chúng ta có thể sử dụng fst và snd cho các cặp và khớp mẫu cho các bộ dài hơn. (Xem bài học về khớp mẫu để biết cách thực hiện.)

Danh sách có thể được xác định bởi một phạm vi:

[3..22]

Và chúng ta cũng có thể chỉ định một bước giữa các mục của phạm vi:

[3,5..22]
['a','c'..'z']

Kết quả của biểu thức đầu tiên sẽ chứa tất cả các phần tử bắt đầu từ 3 một bước 2 = 5 - 3 không vượt quá 22 (nếu phần tử cuối cùng không khớp với mẫu bước đã xác định, nó sẽ bị Kiểu khỏi kết quả).

Kết quả của biểu thức thứ hai sẽ chứa mọi chữ cái viết thường khác trong bảng chữ cái.

Điều quan trọng cần lưu ý là bạn chỉ có thể chỉ định kích thước một bước!

Nếu số gia là âm, các phần tử sẽ được liệt kê theo thứ tự giảm dần:

[17,14..3]

Bạn cũng có thể sử dụng các phạm vi để tạo danh sách vô hạn bằng cách không chỉ định giới hạn trên.

  • [1..] là danh sách vô hạn $[1,2,3,4,5,…]$.
  • [1,3..] là danh sách vô hạn $[1,3,5,7,9,…]$.

Bây giờ, nếu chúng ta chỉ đánh giá danh sách một mình, chương trình sẽ chạy mãi mãi (hoặc cho đến khi nó gặp sự cố). Vì vậy, danh sách vô hạn thường được sử dụng như một phần của biểu thức.

Chúng ta cũng có hàm take trả về một danh sách chứa n các phần tử đầu tiên trong danh sách (có thể là vô hạn) l.

:t take

take 3 ['x'..]

take 20 [1,3..]

take 7 [5,3..]

Chúng ta sử dụng toán tử cons (ký hiệu là dấu hai chấm :) để thêm vào trước một phần tử:

:t (:)
2 : [3,4,5]

Và chúng ta sử dụng toán tử nối ++ để đặt hai danh sách lại với nhau:

:t (++)
[1,3,7,9] ++ [3,3,1]

Lưu ý rằng đó ++ là một hàm nhận hai danh sách và : là một hàm nhận một phần tử và một danh sách.

Cảnh báo 

Việc sử dụng lặp lại ++ toán tử trong các danh sách dài (thậm chí nếu bạn thêm một danh sách đơn lẻ vào một danh sách, chẳng hạn: [1,2,3] ++ [4]), buộc Haskell phải duyệt qua toàn bộ danh sách ở phía bên trái của ++. Do đó, việc đặt thứ gì đó vào cuối danh sách dài năm mươi triệu mục sẽ mất một lúc! Tuy nhiên, đặt một cái gì đó ở đầu danh sách bằng toán tử cons : là ngay lập tức!

Trong số nhiều Hàm hữu ích khác được xác định cho danh sách, chúng ta đề cập ngắn gọn như sau:

  • length lấy một danh sách và trả về độ dài của nó;
  • null kiểm tra xem danh sách có trống không;
  • sum lấy một danh sách các số và trả về tổng của chúng;
  • elem lấy một phần tử x và một danh sách các phần tử l cùng Kiểu và kiểm tra xem nó có phải x là một phần tử của danh sách hay không l.
length [2,4,5,6,7]

null [2]

sum [-1,0,1,6,-5,-1]

5 `elem` [6,3,5,7,5]

Đó là về danh sách cho bây giờ. Nhưng chúng ta sẽ tiếp tục tìm hiểu thêm về chúng trong suốt khóa học!

Nối và ngắt văn bản

Có những trường hợp bạn muốn làm gì với danh sách của mình cụ thể là một thứ liên quan đến văn bản. Haskell có các Hàm cụ thể cho điều đó.

Ví dụ:

  • words :: String -> [String] chia String nhỏ một danh sách các từ, được phân định bằng khoảng trắng.
  • unwords :: [String] -> String là phép toán nghịch đảo với từ. Nó nối các từ với khoảng trắng ngăn cách.
  • lines :: String -> [String] chia đối số thành một danh sách các dòng, với các ký tự dòng mới ( \n) đóng vai trò là dấu phân cách.
  • unlines :: [String] -> String tạo một String từ một mảng các chuỗi, nối thêm các ký tự dòng mới ( \n) giữa các chuỗi ban đầu.
words "To be or not to be?"

lines "How are you doing? \n I'm fine, how about you?"

Nguồn bài viết tại đây


Picture