Getting values from Data in Haskell - haskell

type Name = String
type Age = age
data Person = P Name Age derieving (eq)
type People = [Person]
smiths = [P "John" 21, P "Willy" 26]
How to get Johns age? Is there any function like (smiths!!0).age? Or is it only possible with pattern matching?

You can perform pattern matching:
(\(P _ a) -> a) (smiths !! 0)
But it might be better to define Person with record syntax:
data Person = P {
name :: Name
, age :: Age
} deriving Eq
Then Haskell will automatically construct a "getter" age :: Person -> Age, and then you can access this with:
age (smiths !! 0)

Related

How to use haskell types?

Really simple question, i can't for the life of me google the info :(
I have the following types:
type NI = Int
type Age = Int
type Balance = Int
type Person = (NI, Age, Balance)
How do I create a function that returns true if a person's age is over 65? I've tried:
retired :: Person -> Bool
retired p = p >= 65
It doesn't work obviously, i realise that even when i tried it. I'm stumped on something so simple.
as Person is a tuple you should pattern-match it like this:
retired (_,age,_) = age >= 65
and it should work
type X = Y defines a type alias. In this example, X is defined to be the same as Y. Therefore, retired :: Person -> Bool is the same as retired :: (Int, Int, Int) -> Bool.
Then we can ask the question: how do we obtain a person's age? Well, Person is the same as (Int, Int, Int), and we know the second element of the tuple is the person's age. Therefore, an equivalent question is: how do we obtain the second element of a 3-tuple?
This is done in Haskell by deconstructing the tuple, which is the opposite of constructing the tuple. An example of constructing a tuple is x = (1,2,3). To deconstruct a tuple, we use a similar notation but flip the sides: (a,b,c) = x. This is the same as (a,b,c) = (1,2,3), and the same as the three assignments a = 1; b = 2; c = 3.
Haskell allows you to use this deconstruction notation for function arguments. Therefore, retired p = e could be written retired (ni, age, balance) = e, recalling that Person is a 3-tuple. Now it is straight-forward that e should be age >= 65.
To elaborate further, retired (ni, age, balance) = e is equivalent to retired p = let (ni, age, balance) = p in e. This is useful to know because then function application is clearer. retired x is let (ni, age, balance) = x in [x/p]e where [x/p]e means "substitute x for p in e".
Another approach is to define a data type using record notation.
data Person = Person { ni :: Int, age :: Int, balance :: Int }
This defines a new type called Person, and is not the same as the 3-tuple of type (Int, Int, Int). Additionally, this defines three projection functions ni :: Person -> Int, age :: Person -> Int, and balance :: Person -> Int.
Now if we implement retired :: Person -> Bool we can do so as retired p = age p >= 65, or in point-free form retired = (>= 65) . age.
To reconnect with the first approach, can you also use deconstruction? Absolutely. A Person is constructed as x = Person m n p, and so similarly can be deconstructed as Person a b c = x. Therefore, another definition for retired is retired (Person n a b) = a >= 65.

Can constructor be captured and reused with different set of parameters?

If we have a type Person defined like:
--datatype in record syntax
data Person = Male { firstName :: String, lastName :: String } |
Female { firstName :: String, lastName :: String }
Can this:
flipNames :: Person -> Person
flipNames p#(Male{}) = Male (lastName p) (firstName p)
flipNames p#(Female{}) = Female (lastName p) (firstName p)
be written as one definition of flipNames? Can we somehow capture constructor used, and reuse it with different parametes?
Something like:
flipNames (constructor fname lname) = c lname fname
Although Ganesh has answered your exact question, I want to say that your problem simply indicates an incorrect approach to design of datatypes.
The following approach is much more flexible and removes your problem as such:
data Person = Person { gender :: Gender, firstName :: String, lastName :: String }
data Gender = Male | Female
flipNames (Person gender firstName lastName) = Person gender lastName firstName
The rule behind this is pretty simple: whenever you see yourself creating multiple constructors with the same fields, just use a single constructor and introduce another field with an enum type, as in the code above.
You won't lose any pattern matching capabilities, as the patterns can be like Person Male firstName lastName, and you'll be able to make the Gender type derive Enum and Bounded which will surely help you with types which aren't as trivial. E.g.:
data Gender = Male | Female deriving (Enum, Bounded)
allGenders :: [Gender]
allGenders = enumFrom minBound
maidenName :: Person -> Maybe String
maidenName (Person Female _ z) = Just z
maidenName _ = Nothing
In this particular case, you can do it like this:
flipNames :: Person -> Person
flipNames p = p { firstName = lastName p , lastName = firstName p }
However this only works because the record selectors for Male and Female are the same. There's no general abstraction that captures constructors without their arguments.
To add another option, you could do something similar with phantom types. Note that you're wanting to do this because your Person constructors are redundant, they only exist to differentiate male and female. You could lift this distinction into the type system and let the type inferencing take care of the Male/Female portion.
{-# LANGUAGE FlexibleInstances #-}
data Person a = Person { first :: String, last :: String }
deriving (Show, Eq)
data Male
data Female
flipName :: Person a -> Person a
flipName (Person f l) = Person l f
main = do
let m = Person "John" "Doe" :: Person Male
f = Person "Jane" "Doe" :: Person Female
print m
print f
print (flipName m)
print (flipName f)
print (gender f)
print (gender m)
class Gender a where
gender :: a -> String
instance Gender (Person Male) where
gender _ = "Male"
instance Gender (Person Female) where
gender _ = "Female"
With this in the file person.hs you get this output:
╰─➤ runhaskell person.hs
Person {first = "John", last = "Doe"}
Person {first = "Jane", last = "Doe"}
Person {first = "Doe", last = "John"}
Person {first = "Doe", last = "Jane"}
"Female"
"Male"
The downside of doing this is that you may not want to carry around the extra type parameter. However, the upside is that you could now define typeclass instances with different implementations based on Male and Female types. Although to do the latter requires the FlexibleInstances extension.
Yes, you can do similar (but not identical) using view patterns!
{-# LANGUAGE ViewPatterns #-}
data Person = Male { firstName :: String, lastName :: String }
| Female { firstName :: String, lastName :: String }
| NewBorn { birthdate :: String }
| Child { firstName :: String, lastName :: String }
| Teenager { firstName :: String, lastName :: String }
isAdult :: Person -> Bool
isAdult (Male {}) = True
isAdult (Female {}) = True
isAdult _ = False
flipNames :: Person -> Person
flipNames p#(isAdult -> True) = p{firstName=lastName p, lastName=firstName p}
flipNames p#(isAdult -> False) = p
You can't match a variable against a constructor like that because patterns are not first-class citizens in Haskell. You can have code that's specific to your function, but not something general.
If you're interested in ideas like that, take a look at the research language bondi which does support matching constructors like this. It actually opens up an interesting new vein of expressiveness. In practice, it makes writing code that's generic over the exact structure of the algebraic data type much easier.

basic Haskell : searching through multiple lists for the same elements

I've been battling with this problem for a while. I'm trying to create an "organisation" which is a list of Gyms. These gyms are a list of People. Each person has an ID number, an age and an amount of credit.
I want the FindID function to search through the organisation to search through the list of Gyms to find the users with the inputted ID and then return the total of their credit. However, I feel I'm overcomplcating the problem and I am really struggling now.
newtype ID = ID Int deriving (Show)
newtype Age = Age Int deriving (Show)
newtype Credit = Credit Int deriving (Show)
newtype Person = Person (ID, Age, Weight) deriving (Show)
type Gym = [Person]
type Organisation = [Gym]
getAge :: Person -> Int
getAge (Person(a,Age b,c)) = b
getID :: Person -> Int
getID (Person(ID a,b,c)) = a
getCredit :: Person -> Int
getCredit (Person(a,b,Credit c)) = c
p = Person ( ID 123, Age 65, Credit 12000)
q = Person ( ID 321, Age 64, Credit 0)
e = Person ( ID 453, Age 30, Credit 3000)
r = Person ( ID 123, Age 65, Credit 2310)
s = Person ( ID 364, Age 32, Credit 32340)
t = Person ( ID 123, Age 65, Credit 1300)
org1 = [p,q,e]
org2 = [r,s,t]
hasPerson :: Gym->Int-> Bool
hasPerson gym' id' = not (null(filter hasperson' gym') )
where
hasperson' person' = getID person' == id'
findID:: ID -> Organisation -> Int
findID id' org = total
where
IsInGym org' = hasPerson ( org' id' )
validGym = filter (IsInGym) org'
total = sum ( map getCredit validGym)
First, I would recommend using a record to represent your person, unless you have a particular reason to assign a new type to each field:
type ID = Int
type Age = Int
type Credit = Int
data Person = Person
{ personId :: ID
, personAge :: Age
, personCredit :: Credit
} deriving (Eq, Show)
type Gym = [Person]
type Organization = [Gym]
Next, you can use map to convert a Gym into [Int] with personId, then you can use the built-in elem to check if the ID given appears in that list.
hasPerson :: Gym -> ID -> Bool
hasPerson gym pId = pId `elem` map personId gym
Now, for the findID function, I would suggest renaming it to something like organizationCredit, and I would make a simpler function called gymCredit to calculate it for a single gym:
gymCredit :: ID -> Gym -> Credit
gymCredit pId gym = sum $ map personCredit $ filter (\p -> personId p == pId) gym
organizationCredit :: ID -> Organization -> Credit
organizationCredit pId org = sum $ map (gymCredit pId) org
Alternatively, you could declare your functions as
gymCredit :: Person -> Gym -> Credit
gymCredit person gym = sum $ map personCredit $ filter (\p -> personId p == pId) gym
where pId = personId person
organizationCredit :: Person -> Organization -> Credit
organizationCredit person org = sum $ map (gymCredit person) org
EDIT: To stick with your old types, you just have to define a few extra functions yourself, then put them in your code where you need to
newtype ID = ID Int deriving (Eq, Show)
newtype Age = Age Int deriving (Eq, Show)
newtype Credit = Credit Int deriving (Eq, Show)
newtype Person = Person (ID, Age, Credit) deriving (Eq, Show)
type Gym = [Person]
type Organisation = [Gym]
personId :: Person -> ID
personId (Person (i, a, c)) = i
personAge :: Person -> Age
personAge (Person (i, a, c)) = a
personCredit :: Person -> Credit
personCredit (Person (i, a, c)) = c
idVal :: ID -> Int
idVal (ID x) = x
ageVal :: Age -> Int
ageVal (Age x) = x
creditVal :: Credit -> Int
creditVal (Credit x) = x
gymCredit :: Person -> Gym -> Credit
gymCredit person gym = Credit $ sum $ map (creditVal . personCredit) $ filter (\p -> personId p == pId) gym
where pId = personId person
organisationCredit :: Person -> Organisation -> Credit
organisationCredit person org = Credit $ sum $ map (creditVal . gymCredit person) org
It is important to note that I've added Eq to the list of derived typeclasses for each newtype. Without it, you wouldn't be able to directly compare two IDs, you'd have to extract the values first. Another important typeclass to derive is Ord, which lets you use the <, >, <=, and >= operators, as well as a whole bunch of list functions like sort.

Basic haskell : Defining types

sorry to bother you.
I'm new to haskell and trying to define a new type Person.
I'm using the GHCI compiler.
I'm running the file new.hs which includes:
Name = String
Age = Int
Weight = Int
Person = (Name, Age, Weight)
but I get not in scope errors. Can anyone help?
Jeremy D helped me with this and I solved that but how can I add a function such as:
isAdult :: Person -> Bool
George = ("George", 20, 80)
isAdult George = if Age >=18
try with:
type Name = String
type Age = Int
type Weigth = Int
type Person = (Name, Age, Weigth)
For a simple introduction, look here
To answer your second question, here is what I did:
newtype Name = Name String deriving (Show)
newtype Age = Age Int deriving (Show)
newtype Weigth = Weight Int deriving (Show)
newtype Person = Person (Name, Age, Weigth) deriving (Show)
isAdult :: Person -> Bool
isAdult (Person(_, Age a, _)) = a > 18
When executing it:
*Main> let p = Person(Name "Jeremy", Age 18, Weight 199)
*Main> p
Person (Name "Jeremy", Age 18, Weight 199)
*Main> isAdult p
False
*Main> let p = Person(Name "Jeremy", Age 20, Weight 199)
*Main> isAdult p
True
*Main>

Haskell: Typeclass implies other typeclass

Is it possible to have a typeclass imply another typeclass in Haskell? For example let's say there is a bunch of "things" that can be ordered by an "attribute":
data Person = Person { name :: String, age :: Int }
Person p1 <= Person p1 = (age p1) <= (age p2)
To avoid repetition one could define a "orderable by key" type class
class OrdByKey o where
orderKey :: (Ord r) => o -> r
x <= y = (orderKey x) <= (orderKey y)
Then the instance declaration for Person could look like this
instance OrdByKey Person where
orderKey Person p = age p
Now this does obviously not work for multiple reasons. I wonder if it's possible at all?
As you have specified it, the OrdByKey class can only have one instance
per type, when it sounds like you would like to be able to declare an instance
for each field in your record type.
To accomplish that, you will have to put the field type into the class
definition as well. This lets you do something like the following:
{-# LANGUAGE MultiParamTypeClasses #-}
data Person = Person { name :: String, age :: Int }
class (Ord r) => OrdByKey o r where
orderKey :: o -> r
instance OrdByKey Person Int where
orderKey p = age p
x <=? y = (orderKey x :: Int) <= (orderKey y :: Int)
However, you can only have one instance per field type, so if your
Person type looks like
data Person = Person { name :: String, age :: Int, ssn :: String}
you will not be able to have a version to compare on both the name and
the ssn fields. You could get around this by wrapping each field in a
newtype so each field has a unique type. So your Person type would look
like
data Person = Person { name :: Name, age :: Age, ssn :: SSN}
That would lead to a lot of newtypes floating around though.
The real downside of this is the need to specify the return type for the
orderKey function. I would recommend using the on function from
Data.Function to write the appropriate comparison functions. I think a
function like
compareByKey :: (Ord b) => (a -> b) -> a -> a -> Bool
compareByKey = on (<=)
generalizes your idea of "can be compared by some key". You just have to give
it the function that extracts that key, which would be exactly the accessor
functions for your Person type, in this case.
I can't think of an instance where the OrdByKey class would be useful and trying to overload the <= with multiple versions for the same type seems like it would be down right
confusing in practice.
You could do this:
instance Ord Person where
compare p1 p2 = compare (age p1) (age p2)
Now the standard <= operator will work on Persons and compare their ages.

Resources