Loại Haskell so với Trình tạo dữ liệu


124

Tôi đang học Haskell từ learningnyouahaskell.com . Tôi đang gặp sự cố khi hiểu các hàm tạo kiểu và các hàm tạo dữ liệu. Ví dụ: tôi không thực sự hiểu sự khác biệt giữa điều này:

data Car = Car { company :: String  
               , model :: String  
               , year :: Int  
               } deriving (Show) 

và điều này:

data Car a b c = Car { company :: a  
                     , model :: b  
                     , year :: c   
                     } deriving (Show)  

Tôi hiểu rằng cách đầu tiên chỉ đơn giản là sử dụng một hàm tạo ( Car) để tạo dữ liệu kiểu Car. Tôi không thực sự hiểu cái thứ hai.

Ngoài ra, làm thế nào để các kiểu dữ liệu được xác định như thế này:

data Color = Blue | Green | Red

phù hợp với tất cả những điều này?

Từ những gì tôi hiểu, ví dụ thứ ba ( Color) là một loại mà có thể ở ba trạng thái: Blue, Greenhoặc Red. Nhưng điều đó mâu thuẫn với cách tôi hiểu hai ví dụ đầu tiên: đó là kiểu Carchỉ có thể ở một trạng thái, Carcó thể lấy nhiều tham số khác nhau để xây dựng? Nếu vậy, làm thế nào để ví dụ thứ hai phù hợp?

Về cơ bản, tôi đang tìm kiếm lời giải thích thống nhất ba ví dụ / cấu trúc mã ở trên.


18
Ví dụ Xe hơi của bạn có thể hơi khó hiểu vì Carvừa là phương thức khởi tạo kiểu (ở bên trái =) vừa là phương thức tạo dữ liệu (ở bên phải). Trong ví dụ đầu tiên, hàm tạo Carkiểu không có đối số, trong ví dụ thứ hai, nó có ba. Trong cả hai ví dụ, phương thức Carkhởi tạo dữ liệu nhận ba đối số (nhưng loại của các đối số đó trong một trường hợp là cố định và trong trường hợp khác được tham số hóa).
Simon Shine

đầu tiên chỉ đơn giản là sử dụng một hàm tạo dữ liệu ( Car :: String -> String -> Int -> Car) để xây dựng dữ liệu kiểu Car. thứ hai chỉ đơn giản là sử dụng một hàm tạo dữ liệu ( Car :: a -> b -> c -> Car a b c) để xây dựng dữ liệu kiểu Car a b c.
Will Ness

Câu trả lời:


228

Trong một datakhai báo, một hàm tạo kiểu là thứ ở bên trái của dấu bằng. (Các) hàm tạo dữ liệu là những thứ ở bên phải của dấu bằng. Bạn sử dụng các hàm tạo kiểu trong đó một kiểu được mong đợi và bạn sử dụng các hàm tạo dữ liệu khi một giá trị được mong đợi.

Công cụ tạo dữ liệu

Để làm cho mọi thứ trở nên đơn giản, chúng ta có thể bắt đầu với một ví dụ về một loại biểu thị màu sắc.

data Colour = Red | Green | Blue

Ở đây, chúng ta có ba hàm tạo dữ liệu. Colourlà một kiểu và Greenlà một phương thức khởi tạo chứa giá trị của kiểu Colour. Tương tự, RedBlueđều là các hàm tạo xây dựng các giá trị kiểu Colour. Mặc dù vậy, chúng tôi có thể tưởng tượng ra việc đẩy nó lên!

data Colour = RGB Int Int Int

Chúng ta vẫn chỉ có kiểu Colour, nhưng RGBkhông phải là một giá trị - đó là một hàm lấy ba Ints và trả về một giá trị! RGBcó loại

RGB :: Int -> Int -> Int -> Colour

RGBlà một phương thức tạo dữ liệu là một hàm nhận một số giá trị làm đối số của nó, sau đó sử dụng những giá trị đó để tạo một giá trị mới. Nếu bạn đã thực hiện bất kỳ lập trình hướng đối tượng nào, bạn nên nhận ra điều này. Trong OOP, các hàm tạo cũng nhận một số giá trị làm đối số và trả về một giá trị mới!

Trong trường hợp này, nếu chúng ta áp dụng RGBcho ba giá trị, chúng ta sẽ nhận được một giá trị màu!

Prelude> RGB 12 92 27
#0c5c1b

Chúng tôi đã xây dựng một giá trị của kiểu Colourbằng cách áp dụng hàm tạo dữ liệu. Một phương thức khởi tạo dữ liệu hoặc chứa một giá trị giống như một biến sẽ hoặc lấy các giá trị khác làm đối số của nó và tạo ra một giá trị mới . Nếu bạn đã từng lập trình trước đó, thì khái niệm này hẳn không còn xa lạ với bạn.

Nghỉ giải lao

Nếu bạn muốn xây dựng một cây nhị phân để lưu trữ Strings, bạn có thể tưởng tượng làm điều gì đó như

data SBTree = Leaf String
            | Branch String SBTree SBTree

Những gì chúng ta thấy ở đây là một kiểu SBTreechứa hai hàm tạo dữ liệu. Nói cách khác, có hai hàm (cụ thể là LeafBranch) sẽ xây dựng các giá trị của SBTreekiểu. Nếu bạn không quen với cách hoạt động của cây nhị phân, chỉ cần tiếp tục ở đó. Bạn không thực sự cần biết cây nhị phân hoạt động như thế nào, chỉ biết rằng cây này lưu trữ Stringtheo một cách nào đó.

Chúng ta cũng thấy rằng cả hai hàm tạo dữ liệu đều có một Stringđối số - đây là Chuỗi mà chúng sẽ lưu trữ trong cây.

Nhưng! Điều gì sẽ xảy ra nếu chúng ta cũng muốn có thể lưu trữ Bool, chúng ta phải tạo một cây nhị phân mới. Nó có thể trông giống như sau:

data BBTree = Leaf Bool
            | Branch Bool BBTree BBTree

Nhập các hàm tạo

Cả hai SBTreeBBTree đều là các hàm tạo kiểu. Nhưng có một vấn đề rõ ràng. Bạn có thấy chúng giống nhau như thế nào không? Đó là dấu hiệu cho thấy bạn thực sự muốn có một tham số ở đâu đó.

Vì vậy, chúng tôi có thể làm điều này:

data BTree a = Leaf a
             | Branch a (BTree a) (BTree a)

Bây giờ chúng ta giới thiệu một biến kiểu a như là một tham số cho hàm tạo kiểu. Trong khai báo này, BTreeđã trở thành một hàm. Nó nhận một kiểu làm đối số của nó và nó trả về một kiểu mới .

Điều quan trọng ở đây để xem xét sự khác biệt giữa một là loại bê tông (ví dụ bao gồm Int, [Char]Maybe Bool) mà là một loại có thể được gán cho một giá trị trong chương trình của bạn, và một chức năng loại constructor mà bạn cần để nuôi một loại để có thể được được gán cho một giá trị. Một giá trị không bao giờ có thể thuộc loại "danh sách", vì nó cần phải là "danh sách của một cái gì đó ". Theo tinh thần tương tự, một giá trị không bao giờ có thể thuộc loại "cây nhị phân", bởi vì nó cần phải là "cây nhị phân lưu trữ một cái gì đó ".

Nếu chúng ta chuyển vào, chẳng hạn Boolnhư một đối số BTree, nó sẽ trả về kiểu BTree Bool, là cây nhị phân lưu trữ Bools. Thay thế mọi lần xuất hiện của biến kiểu abằng kiểu Boolvà bạn có thể tự mình thấy nó đúng như thế nào.

Nếu muốn, bạn có thể xem BTreedưới dạng một hàm với loại

BTree :: * -> *

Các loại có phần giống như các loại - *biểu thị một loại bê tông, vì vậy chúng tôi nói BTreelà từ một loại bê tông thành một loại bê tông.

Kết thúc

Hãy quay lại đây một chút và ghi lại những điểm tương đồng.

  • Hàm tạo dữ liệu là một "hàm" nhận 0 hoặc nhiều giá trị và trả lại cho bạn một giá trị mới.

  • Một constructor loại là một "chức năng" mà mất 0 hoặc nhiều loại và cung cấp cho bạn trở lại một kiểu mới.

Các hàm tạo dữ liệu với các tham số rất tuyệt nếu chúng ta muốn có những thay đổi nhỏ trong các giá trị của mình - chúng tôi đặt các biến thể đó vào các tham số và để người tạo ra giá trị quyết định họ sẽ đưa vào đối số nào. Theo nghĩa tương tự, hãy nhập các hàm tạo có tham số là tuyệt vời nếu chúng ta muốn có những biến thể nhỏ trong các loại của mình! Chúng tôi đặt những biến thể đó làm tham số và để người tạo kiểu quyết định những đối số mà họ sẽ đưa vào.

Một trường hợp nghiên cứu

Như phần mở rộng nhà ở đây, chúng ta có thể xem xét Maybe aloại. Định nghĩa của nó là

data Maybe a = Nothing
             | Just a

Đây, Maybelà một hàm tạo kiểu trả về một kiểu cụ thể. Justlà một hàm tạo dữ liệu trả về một giá trị. Nothinglà một hàm tạo dữ liệu có chứa một giá trị. Nếu chúng ta nhìn vào loại Just, chúng ta thấy rằng

Just :: a -> Maybe a

Nói cách khác, Justnhận một giá trị kiểu avà trả về giá trị kiểu Maybe a. Nếu chúng ta nhìn vào loại Maybe, chúng ta thấy rằng

Maybe :: * -> *

Nói cách khác, Maybelấy một kiểu cụ thể và trả về một kiểu cụ thể.

Một lần nữa! Sự khác biệt giữa một loại bê tông và một hàm xây dựng loại. Bạn không thể tạo danh sách các Maybes - nếu bạn cố gắng thực thi

[] :: [Maybe]

bạn sẽ gặp lỗi. Tuy nhiên, bạn có thể tạo một danh sách Maybe Inthoặc Maybe a. Đó là bởi vì Maybelà một hàm tạo kiểu, nhưng một danh sách cần phải chứa các giá trị của một kiểu cụ thể. Maybe IntMaybe alà các kiểu cụ thể (hoặc nếu bạn muốn, các lệnh gọi hàm tạo kiểu trả về kiểu cụ thể.)


2
Trong ví dụ đầu tiên của bạn, cả RED GREEN và BLUE đều là các hàm tạo không có đối số.
OllieB

3
Tuyên bố rằng trong data Colour = Red | Green | Blue"chúng tôi không có bất kỳ hàm tạo nào" là hoàn toàn sai. Các hàm tạo kiểu và hàm tạo dữ liệu không cần nhận đối số, xem ví dụ: haskell.org/haskellwiki/Constructor chỉ ra rằng data Tree a = Tip | Node a (Tree a) (Tree a)"có hai hàm tạo dữ liệu, Mẹo và Nút".
Frerich Raabe

1
@CMCDragonkai Bạn hoàn toàn chính xác! Loại là "loại của các loại." Cách tiếp cận phổ biến để nối các khái niệm kiểu và giá trị được gọi là kiểu gõ phụ thuộc . Idris là một ngôn ngữ được đánh máy phụ thuộc lấy cảm hứng từ Haskell. Với các phần mở rộng GHC phù hợp, bạn cũng có thể tiến gần hơn đến việc nhập phụ thuộc trong Haskell. (Một số người đã bị đùa rằng "nghiên cứu Haskell là về để tìm hiểu làm thế nào gần với các loại phụ thuộc chúng ta có thể có được mà không cần phải loại phụ thuộc.")
kqr

1
@CMCDragonkai Thực ra không thể có khai báo dữ liệu trống trong Haskell tiêu chuẩn. Nhưng có một phần mở rộng GHC ( -XEmptyDataDecls) cho phép bạn làm điều đó. Vì, như bạn nói, không có giá trị nào với kiểu đó, f :: Int -> Zví dụ , một hàm có thể không bao giờ trả về (vì nó sẽ trả về cái gì?) Tuy nhiên, chúng có thể hữu ích khi bạn muốn các kiểu nhưng không thực sự quan tâm đến giá trị .
kqr

1
Thực sự là không thể? Tôi vừa thử trong GHC, và nó chạy mà không có lỗi. Tôi không phải tải bất kỳ phần mở rộng GHC nào, chỉ cần GHC vani. Sau đó tôi có thể viết :k Zvà nó chỉ cho tôi một ngôi sao.
CMCDragonkai

42

Haskell có các kiểu dữ liệu đại số mà rất ít ngôn ngữ khác có. Đây có lẽ là điều khiến bạn bối rối.

Trong các ngôn ngữ khác, bạn thường có thể tạo "bản ghi", "cấu trúc" hoặc tương tự, có một loạt các trường được đặt tên chứa nhiều loại dữ liệu khác nhau. Bạn cũng có thể đôi khi làm cho một "liệt kê", trong đó có một bộ (nhỏ) của các giá trị cố định có thể (ví dụ như, bạn Red, GreenBlue).

Trong Haskell, bạn có thể kết hợp cả hai điều này cùng một lúc. Kỳ lạ, nhưng có thật!

Tại sao nó được gọi là "đại số"? Chà, lũ mọt sách nói về "loại tổng" và "loại sản phẩm". Ví dụ:

data Eg1 = One Int | Two String

Một Eg1giá trị về cơ bản một số nguyên hoặc một chuỗi. Vì vậy, tập hợp tất cả các Eg1giá trị có thể có là "tổng" của tập hợp tất cả các giá trị nguyên có thể có và tất cả các giá trị chuỗi có thể có. Do đó, nerds được coi Eg1là một "loại tổng". Mặt khác:

data Eg2 = Pair Int String

Mọi Eg2giá trị bao gồm cả số nguyên và chuỗi. Vì vậy, tập hợp tất cả các Eg2giá trị có thể có là tích Descartes của tập hợp tất cả các số nguyên và tập hợp tất cả các chuỗi. Hai tập hợp được "nhân" với nhau, vì vậy đây là một "loại sản phẩm".

Các kiểu đại số của Haskell là tổng các kiểu sản phẩm . Bạn cung cấp một hàm tạo nhiều trường để tạo một loại sản phẩm và bạn có nhiều hàm tạo để tạo thành một tổng (của các sản phẩm).

Ví dụ về lý do tại sao điều đó có thể hữu ích, giả sử bạn có thứ gì đó xuất dữ liệu dưới dạng XML hoặc JSON và nó cần một bản ghi cấu hình - nhưng rõ ràng, cài đặt cấu hình cho XML và cho JSON hoàn toàn khác nhau. Vì vậy, bạn có thể làm một cái gì đó như sau:

data Config = XML_Config {...} | JSON_Config {...}

(Rõ ràng là với một số trường phù hợp trong đó.) Bạn không thể làm những thứ như thế này bằng các ngôn ngữ lập trình thông thường, đó là lý do tại sao hầu hết mọi người không quen với nó.


4
tuyệt quá! Wikipedia cho biết: "Chúng có thể ... được xây dựng bằng hầu hết mọi ngôn ngữ" . :) Trong ví dụ C / ++, đó là unions, với một kỷ luật thẻ. :)
Will Ness

5
Ừ, nhưng mỗi khi tôi nhắc đến union, mọi người lại nhìn tôi như "thằng quái nào lại dùng cái đó ??" ;-)
MathOrchid

1
Tôi đã thấy rất nhiều unionđược sử dụng trong sự nghiệp C của tôi. Xin đừng làm cho nó nghe có vẻ không cần thiết vì đó không phải là trường hợp.
truthadjustr 24/09/2018

26

Bắt đầu với trường hợp đơn giản nhất:

data Color = Blue | Green | Red

Điều này xác định một "phương thức tạo kiểu" Colorkhông nhận đối số - và nó có ba "phương thức tạo dữ liệu" Blue, GreenRed. Không có hàm tạo dữ liệu nào nhận bất kỳ đối số nào. Điều này có nghĩa rằng có ba loại Color: Blue, GreenRed .

Một phương thức khởi tạo dữ liệu được sử dụng khi bạn cần tạo một giá trị nào đó. Giống:

myFavoriteColor :: Color
myFavoriteColor = Green

tạo ra một giá trị myFavoriteColorbằng cách sử dụng phương thức Greenkhởi tạo dữ liệu - và myFavoriteColorsẽ thuộc loại Colorvì đó là loại giá trị do phương thức khởi tạo dữ liệu tạo ra.

Hàm tạo kiểu được sử dụng khi bạn cần tạo một kiểu sắp xếp nào đó. Điều này thường xảy ra khi viết chữ ký:

isFavoriteColor :: Color -> Bool

Trong trường hợp này, bạn đang gọi hàm tạo Colorkiểu (không có đối số).

Vẫn còn với tôi?

Bây giờ, hãy tưởng tượng bạn không chỉ muốn tạo các giá trị đỏ / xanh lá cây / xanh lam mà bạn còn muốn chỉ định "cường độ". Giống như, một giá trị từ 0 đến 256. Bạn có thể làm điều đó bằng cách thêm một đối số vào từng hàm tạo dữ liệu, vì vậy bạn kết thúc với:

data Color = Blue Int | Green Int | Red Int

Bây giờ, mỗi một trong ba hàm tạo dữ liệu nhận một đối số kiểu Int. Hàm tạo kiểu ( Color) vẫn không nhận bất kỳ đối số nào. Vì vậy, màu yêu thích của tôi là màu xanh lá cây đậm, tôi có thể viết

    myFavoriteColor :: Color
    myFavoriteColor = Green 50

Và một lần nữa, nó gọi hàm tạo Greendữ liệu và tôi nhận được một giá trị kiểu Color.

Hãy tưởng tượng nếu bạn không muốn ra lệnh cho mọi người thể hiện cường độ của màu sắc như thế nào. Một số có thể muốn một giá trị số như chúng tôi vừa làm. Những người khác có thể ổn chỉ với một boolean cho biết "sáng" hoặc "không sáng lắm". Giải pháp cho điều này là không mã hóa cứng Inttrong các hàm tạo dữ liệu mà sử dụng một biến kiểu:

data Color a = Blue a | Green a | Red a

Bây giờ, hàm tạo kiểu của chúng ta nhận một đối số (kiểu khác mà chúng ta vừa gọi a!) Và tất cả các hàm tạo dữ liệu sẽ nhận một đối số (một giá trị!) Của kiểu đó a. Vì vậy, bạn có thể có

myFavoriteColor :: Color Bool
myFavoriteColor = Green False

hoặc là

myFavoriteColor :: Color Int
myFavoriteColor = Green 50

Lưu ý cách chúng ta gọi hàm tạo Colorkiểu với một đối số (kiểu khác) để nhận kiểu "hiệu quả" sẽ được trả về bởi các hàm tạo dữ liệu. Điều này chạm đến khái niệm về các loại mà bạn có thể muốn đọc qua một hoặc hai tách cà phê.

Bây giờ chúng ta đã tìm ra hàm tạo dữ liệu và hàm tạo kiểu là gì và cách các hàm tạo dữ liệu có thể nhận các giá trị khác làm đối số và các hàm tạo kiểu có thể nhận các kiểu khác làm đối số. HTH.


Tôi không chắc mình là bạn với khái niệm của bạn về phương thức tạo dữ liệu nullary. Tôi biết đó là một cách phổ biến để nói về hằng số trong Haskell, nhưng nó đã không được chứng minh là không chính xác một vài lần phải không?
kqr

@kqr: Một hàm tạo dữ liệu có thể là nullary, nhưng sau đó nó không còn là một hàm nữa. Một hàm là một cái gì đó nhận một đối số và mang lại một giá trị, tức là một cái gì đó có ->trong chữ ký.
Frerich Raabe

Một giá trị có thể trỏ đến nhiều loại không? Hay mỗi giá trị chỉ được liên kết với 1 loại và đó là nó?
CMCDragonkai

1
@jrg Có một số trùng lặp, nhưng không phải do các hàm tạo kiểu cụ thể mà do các biến kiểu, ví dụ: ain data Color a = Red a. alà một trình giữ chỗ cho một kiểu tùy ý. Mặc dù vậy, bạn có thể có những hàm tương tự trong các hàm đơn giản, ví dụ như một hàm kiểu (a, b) -> anhận một bộ giá trị của hai giá trị (kiểu ab) và cho ra giá trị đầu tiên. Đây là một hàm "chung chung" ở chỗ nó không chỉ định kiểu của các phần tử tuple - nó chỉ xác định rằng hàm mang lại một giá trị cùng kiểu với phần tử tuple đầu tiên.
Frerich Raabe

1
+1 Now, our type constructor takes one argument (another type which we just call a!) and all of the data constructors will take one argument (a value!) of that type a.Điều này rất hữu ích.
Jonas

5

Như những người khác đã chỉ ra, tính đa hình không có ích gì ở đây. Hãy xem một ví dụ khác mà bạn có thể đã quen thuộc:

Maybe a = Just a | Nothing

Kiểu này có hai hàm tạo dữ liệu. Nothinghơi nhàm chán, nó không chứa bất kỳ dữ liệu hữu ích nào. Mặt khác, Justchứa một giá trị a- bất kỳ loại nào acó thể có. Hãy viết một hàm sử dụng kiểu này, ví dụ: lấy phần đầu của Intdanh sách, nếu có (tôi hy vọng bạn đồng ý rằng điều này hữu ích hơn việc tạo ra một lỗi):

maybeHead :: [Int] -> Maybe Int
maybeHead [] = Nothing
maybeHead (x:_) = Just x

> maybeHead [1,2,3]    -- Just 1
> maybeHead []         -- None

Vì vậy, trong trường hợp anày là một Int, nhưng nó sẽ hoạt động tốt cho bất kỳ loại nào khác. Trên thực tế, bạn có thể làm cho hàm của chúng tôi hoạt động cho mọi loại danh sách (ngay cả khi không thay đổi cách triển khai):

maybeHead :: [t] -> Maybe t
maybeHead [] = Nothing
maybeHead (x:_) = Just x

Mặt khác, bạn có thể viết các hàm chỉ chấp nhận một loại nhất định Maybe, ví dụ:

doubleMaybe :: Maybe Int -> Maybe Int
doubleMaybe Just x = Just (2*x)
doubleMaybe Nothing= Nothing

Vì vậy, câu chuyện dài ngắn, với tính đa hình, bạn cung cấp cho loại của riêng mình sự linh hoạt để làm việc với các giá trị của các loại khác nhau.

Trong ví dụ của bạn, bạn có thể quyết định tại một số điểm Stringkhông đủ để xác định công ty, nhưng công ty cần có loại riêng Company(chứa dữ liệu bổ sung như quốc gia, địa chỉ, tài khoản trở lại, v.v.). Việc triển khai đầu tiên của bạn Carsẽ cần phải thay đổi để sử dụng Companythay vì Stringcho giá trị đầu tiên của nó. Cách triển khai thứ hai của bạn vẫn ổn, bạn sử dụng nó như cũ Car Company String Intvà nó sẽ hoạt động như trước (tất nhiên các chức năng truy cập dữ liệu công ty cần được thay đổi).


Bạn có thể sử dụng các hàm tạo kiểu trong ngữ cảnh dữ liệu của một khai báo dữ liệu khác không? Một cái gì đó như thế data Color = Blue ; data Bright = Colornào? Tôi đã thử nó trong ghci và có vẻ như Màu trong hàm tạo kiểu không liên quan gì đến hàm tạo dữ liệu Màu trong định nghĩa Sáng. Chỉ có 2 hàm tạo Màu, một là Dữ liệu và một là Kiểu.
CMCDragonkai

@CMCDragonkai Tôi không nghĩ rằng bạn có thể làm được điều này và tôi thậm chí không chắc bạn muốn đạt được điều gì với điều này. Bạn có thể "bọc" một kiểu hiện có bằng cách sử dụng datahoặc newtype(ví dụ data Bright = Bright Color), hoặc bạn có thể sử dụng typeđể xác định một từ đồng nghĩa (ví dụ type Bright = Color).
Landei

5

Cái thứ hai có khái niệm "đa hình" trong đó.

a b cthể thuộc bất kỳ loại nào. Ví dụ, acó thể là một [String], bcó thể được [Int]ccó thể được [Char].

Trong khi loại đầu tiên là cố định: công ty là một String, mô hình là một Stringvà năm là Int.

Ví dụ về Xe có thể không cho thấy tầm quan trọng của việc sử dụng tính đa hình. Nhưng hãy tưởng tượng dữ liệu của bạn thuộc loại danh sách. Một danh sách có thể chứa String, Char, Int ...Trong những trường hợp đó, bạn sẽ cần cách thứ hai để xác định dữ liệu của mình.

Đối với cách thứ ba, tôi không nghĩ rằng nó cần phải phù hợp với loại trước đó. Đó chỉ là một cách khác để xác định dữ liệu trong Haskell.

Đây là ý kiến ​​khiêm tốn của tôi với tư cách là một người mới bắt đầu.

Btw: Hãy chắc chắn rằng bạn rèn luyện trí não tốt và cảm thấy thoải mái khi làm việc này. Nó là chìa khóa để hiểu Monad sau này.


1

Đó là về loại : Trong trường hợp đầu tiên, bạn đặt các loại String(cho công ty và kiểu máy) và Intcho năm. Trong trường hợp thứ hai, của bạn chung chung hơn. a, bccó thể là các kiểu giống như trong ví dụ đầu tiên, hoặc một cái gì đó hoàn toàn khác. Ví dụ: có thể hữu ích nếu cung cấp năm dưới dạng chuỗi thay vì số nguyên. Và nếu bạn muốn, bạn thậm chí có thể sử dụng Colorloại của mình .

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.