Saturday, 30 April 2016

Haskell: Custom types using record syntax


In my previous posts, I explained about custom types, pattern matching against custom types. Let me briefly explain with an example.

BooksUtil.hs
type ISBN = String
type Title = String
type Authors = [String]

data BookInfo = Book ISBN Title Authors 

getIsbn :: BookInfo -> ISBN
getIsbn (Book isbn _ _) = isbn

getTitle :: BookInfo -> Title
getTitle (Book _ title _) = title

getAuthors :: BookInfo -> Authors
getAuthors (Book _ _ authors) = authors

*Main> :load BookUtil.hs
[1 of 1] Compiling Main             ( BookUtil.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> let bk1 = Book "123SVC" "Java Tutorial" ["Krishna", "Hari"]
*Main> 
*Main> getIsbn bk1
"123SVC"
*Main> 
*Main> getTitle bk1
"Java Tutorial"
*Main> 
*Main> getAuthors bk1
["Krishna","Hari"]
*Main> 


Observe BookUtil.hs, i had written three functions getIsbn, getTitle, getAuthors to extract ISBN, Title, Authors information respectively. Observe the above code, we are repeating the same logic in all the three functions. We can remove this kind of boilerplate code by using the record syntax notation.

We can redefine our custom type BookInfo using record syntax like below.
data BookInfo = Book {
    isbn :: ISBN,
    title :: Title,
    authors :: Authors 
} deriving (Show)


Following statement creates a variable of type Book.
bk1 = Book "123SVC" "Java Tutorial" ["Krishna", "Hari"]

'isbn bk1' return ISBN number of book bk1.
'title bk1' return the title of book bk1.
'authors bk1' return the authors of book bk1.

Following is the complete working application.

BookUtil.hs
type ISBN = String
type Title = String
type Authors = [String]

data BookInfo = Book {
    isbn :: ISBN,
    title :: Title,
    authors :: Authors 
} deriving (Show)


Above definition automatically generate the following accessor functions for us.
isbn :: BookInfo -> ISBN
title :: BookInfo -> Title
authors  :: BookInfo -> Authors

*Main> :load BookUtil.hs
[1 of 1] Compiling Main             ( BookUtil.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> let bk1 = Book "123SVC" "Java Tutorial" ["Krishna", "Hari"]
*Main> 
*Main> bk1
Book {isbn = "123SVC", title = "Java Tutorial", authors = ["Krishna","Hari"]}
*Main> 
*Main> isbn bk1
"123SVC"
*Main> 
*Main> title bk1
"Java Tutorial"
*Main> 
*Main> authors bk1
["Krishna","Hari"]
*Main> 


You can also create an instance of Book like below.
bk1 = Book{title="Java Tutorial", authors=["Hari", "Krishna"], isbn="ISBN12345"}


One advantage of above notation is, we no need to remember the order of fields in the custom data type.
*Main> let bk1 = Book{title="Java Tutorial", authors=["Hari", "Krishna"], isbn="ISBN12345"}
*Main> 
*Main> title bk1
"Java Tutorial"
*Main> 
*Main> isbn bk1
"ISBN12345"
*Main> 
*Main> authors bk1
["Hari","Krishna"]
*Main>


When you define a type signature using the record syntax, it changes the way that type values are printed.
*Main> let bk1 = Book{title="Java Tutorial", authors=["Hari", "Krishna"], isbn="ISBN12345"}
*Main> bk1
Book {isbn = "ISBN12345", title = "Java Tutorial", authors = ["Hari","Krishna"]}


One more advantage of record notation is, you can update the fields of a type conveniently.
bk1 = Book{title="Java Tutorial", authors=["Hari", "Krishna"], isbn="ISBN12345"}

bk2 = bk1 {title = "Haskell Tutorial"}


bk2 has the title "Haskell Tutorial", and maintain remaining properties same as bk1.
*Main> let bk1 = Book{title="Java Tutorial", authors=["Hari", "Krishna"], isbn="ISBN12345"}
*Main> 
*Main> let bk2 = bk1 {title = "Haskell Tutorial"}
*Main> bk2
Book {isbn = "ISBN12345", title = "Haskell Tutorial", authors = ["Hari","Krishna"]}


So, in general, to update the field x in a datatype y to z, you write y { x = z }.

You can perform updates on multiple fields at a time by separating them using commas.

*Main> let bk3 = bk1 {title = "Python Tutorial", isbn = "ISBN3245"}
*Main> bk3
Book {isbn = "ISBN3245", title = "Python Tutorial", authors = ["Hari","Krishna"]}


You can apply pattern matching against named fields in record notations.

getInfo (Book {isbn = is, title = ti}) = (is, ti)

Above function matches isbn field to the variable is, title field to the variable ti.

BookUtil.hs

type ISBN = String
type Title = String
type Authors = [String]

data BookInfo = Book {
    isbn :: ISBN,
    title :: Title,
    authors :: Authors 
} deriving (Show)

getInfo (Book {isbn = is, title = ti}) = (is, ti)

*Main> :load BookUtil.hs
[1 of 1] Compiling Main             ( BookUtil.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> let bk1 = Book{title="Java Tutorial", authors=["Hari", "Krishna"], isbn="ISBN12345"}
*Main> getInfo bk1
("ISBN12345","Java Tutorial")







Previous                                                 Next                                                 Home

No comments:

Post a Comment