Can I force the use of keywords only when initializing a record in Haskell?
data Person
= Person
{
name :: String,
idnum :: String
}
deriving( Show )
main = putStrLn $ show $ Person "oren" "9200"
-- I want to only allow such initializations:
-- main = putStrLn $ show $ Person { name = "moish", idnum = "7400" }
(This is especially useful when two fields have the same type)
As far as I know, no.
Consider maybe the following solution?
newtype Name = Name String
newtype Idnum = Idnum String
data Person = Person { name :: Name, idnum :: Idnum }
Another possibility, worse in my opinion, is this:
module A (Person, name, idnum) where
data Person = Person
{ _name :: String
, _idnum :: String
}
name :: String -> (Person -> Person)
name n p = p { _name = n }
idnum :: String -> (Person -> Person)
idnum n p = p { _idnum = n }
emptyPerson :: Person
emptyPerson = Person "" ""
# in another module
module B
import A (Person, name, idnum)
myPerson = name "myname" . idnum "myidnum" $ emptyPerson
In this case there's no guarantee that both name and idnum get a value.
The fact that Person can always be used as a 'plain function' may turn out to be very useful. If you have, for instance, getName :: IO String and getIdnum :: IO String, then combining these to form a getPerson :: IO Person is concise: getPerson = Person <$> getName <*> getIdnum. This is only possible because we don't use record syntax here!
Related
I try printing a value (of the showable Person type) and then changing the return type from IO () to IO Person.
import qualified Data.Text as T
data Person = Person
{ firstName :: T.Text
, lastName :: T.Text
} deriving Show
writePerson :: Person -> IO Person
writePerson p = const p <$> print p
Expected Result:
Person {firstName = "Maria", lastName = "do Rosario"}
Actual Result:
Person {firstName = "Maria", lastName = "do Rosario"}
Person {firstName = "Maria", lastName = "do Rosario"}
You are running this in ghci. The first line is the output of the call to print. The second line is the interpreter showing the return value of the call to writePerson. They are identical because you pass p as the argument to both const and print.
I am new to Haskell, I wrote this small script but I am having this read: no parse Exception, anyone could help me?
thanks
import System.Environment
import Data.List
data ContactInfo = Contact { name :: String
, surname :: String
, mobile :: String
} deriving (Read, Show)
fromDt :: [String] -> ContactInfo
fromDt ds = read $ "Contact " ++ (Data.List.concat $ Data.List.intersperse " " $ Data.List.map show ds)
main = do
let val = fromDt ["John","Smith","223 455 2703"]
putStrLn ("Name: " ++ name val ++ ", " ++ surname val)
Using read is horrible for that task, simply use the constructor Contact.
fromDt :: [String] -> ContactInfo
fromDt [n,s,m] = Contact n s m
Note that you will still get an error if the list you pass to fromDt is not 3 cells long. I would simply avoid defining this fragile function and use the constructor Contact directly wherever you would call fromDt.
When you define a data type using record syntax, the derived read instance requires the full record syntax - i.e. you must pass a string like
ContactInfo { name = "...", surname = "...", mobile = "..." }
to read to obtain a ContactInfo value. A string like:
ContactInfo "..." "..." "..."
will result in a no parse exception. Here is a quick demo:
data ABC = ABC { a :: Int, b :: Int, c :: Int }
deriving (Show, Read)
test1 :: ABC -- throws no parse exception
test1 = read "ABC 1 2 3"
test2 :: ABC -- works
test2 = read "ABC { a = 1, b = 2, c = 3 }"
I have the data type:
data Person = Person {
person_id :: Int,
person_firstname :: String,
person_surname :: String,
person_address :: Address
}
I would like to change let say the value of person_firstname which in haskell means copying everything else. Is there an easier way to do this than:
person'' = Person (person_id person') newName (person_surname person') (person_address person')
Record update:
person' = person { person_firstname = newName }
Note that it works on any expression:
somebody = (Person 0 "Nobody" "Nothingson" "123 Fake St.")
{ person_firstname = "Somebody"
, person_surname = "Somethingson"
}
The RecordWildCards extension can also save you some typing, but I recommend it only for short definitions where the names can’t get away from you:
incrementId person#Person{..} = person { person_id = person_id + 1 }
Another way of doing it is by using lenses from the lens package:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data Person = Person {
_person_id :: Int,
_person_firstname :: String,
_person_surname :: String,
_person_address :: String
}
makeLenses ''Person
examplePerson = Person 7 "aaa" "bbb" "ccc"
modifiedPerson = set person_firstname "zzz" examplePerson
main :: IO ()
main = do
putStrLn $ view person_firstname modifiedPerson
Lenses have the advantage that they are easily composable; they come in handy when you have nested data structures.
I am trying to serialize a Contacts type but I am stuck at defining put and get?
import Control.Monad
import Data.Binary
type Name = String
type Address = String
data Contacts = Contacts [(Name, Address)] deriving (Show)
instance Binary Contacts where
put (Contacts [(n,a)]) = do ...
get = do ...
main :: IO ()
main = do
let c = Contacts [("gert","home")]
let e = encode c
let d = decode e
print d
Yes, you are stuck defining put and get. Does that answer your question?
type Name = String
type Address = String
data Contacts = Contacts [(Name, Address)] deriving (Show)
instance Binary Contacts
put (Contacts [(n,a)]) = do ...
get = do ...
Since there are already instances:
instance (Binary a) => Binary [a]
instance (Binary a, Binary b) => Binary (a,b)
instance Binary Char
You should just be able to trivially lift the underlying put and get routines:
instance Binary Contacts where
put (Contacts set) = put set
get = fmap Contacts get
So when you put contacts you just tell it to put the list of pairs of strings. When you want to deserialize the contacts you just get the underlying list and use the Contacts constructor.
Adding more simple examples to prevent other noobs from suffering like me :)
{-# LANGUAGE RecordWildCards #-}
import Data.Binary
type Name = String
type Address = String
type Phone = String
data Contacts = Contacts [(Name, Address)] deriving (Show)
instance Binary Contacts where
put (Contacts set) = put set
get = fmap Contacts get
data Contact = Contact { name :: Name, address :: Address, phone :: Phone } deriving (Show)
instance Binary Contact where
put Contact{..} = do put name; put address; put phone
get = do name <- get; address <- get; phone <- get; return Contact{..}
main :: IO ()
main = do
let c = Contacts [("gert","home"),("gert2","home2")]
let e = encode c
print e
let d = decode e
print (d:: Contacts)
let c' = Contact{name="gert",address="home",phone="test"}
let e' = encode c'
print e'
let d' = decode e'
print (d':: Contact)
I'm reading values from in from a console using readLn.
I'd like to write a function:
requestValue :: String -> IO a
requestValue s = do
putStrLn $ "Please enter a new value for " ++ s
readLn
I'd then be able to do, for example,
changeAge :: Person -> IO Person
changeAge p = do
age' <- requestValue "age"
return $ p { age = age'}
changeName :: Person -> IO Person
changeName p = do
name' <- requestValue "name"
return $ p { name = name'}
The problem I have is that the read instance of String seems to require the string to be in quotes. I don't want to have to enter "Fred" in the console to change name when I really only want to type in Fred.
Is there an easy way to do this that keeps requestValue polymorphic?
Since you want to add your own custom read behavior for user names, the way to do that is to actually write a new instance for readings names. To do that we can create a new type for names:
import Control.Arrow (first)
newtype Name = Name { unName :: String }
deriving (Eq, Ord, Show)
and write a custom read for it:
instance Read Name where
readsPrec n = map (first Name) . readsPrec n . quote
where quote s = '"' : s ++ ['"']
this is the same as the read instance for strings, but we first quote the string, after reading it in.
Now you can modify your Person type to use Name instead of String:
data Person = Person { age :: Int
, name :: Name } deriving Show
and we're in business:
*Main> changeName (Person 31 (Name "dons"))
Please enter a new value for name
Don
Person {age = 31, name = Name {unName = "Don"}}
You want getLine, not readLn.