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

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.

Related

Getting values from Data in 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)

Is there any point in using the constructor after construction when there is only one possible type

Considering
data Person = Person String Int
let p = Person "Jim" 23
let ofLegalAge :: Person -> Bool
ofLegalAge (_ _ age) -- (instead of (Person _ age))
| age >= 18 = True
| age < 18 = False
Is there any point in ever using the Enum part of the sum type Person over the type of the sum type itself?
You can't add any variants anyway.
Your question, with a few terminology tweaks:
Is there any point in ever [specifying the constructor] Person [...]? You can't add any variants anyway.
In this case, given that there is just one constructor and that you have specified the type of ofLegalAge's argument, specifying the constructor indeed is, in principle, redundant. In practice, though, there is no syntax for ommiting constructors in such a way. One alternative, which works very well for single-constructor types, is using record syntax:
data Person = Person
{ name :: String
, age :: Int
}
ofLegalAge :: Person -> Bool
ofLegalAge person
| age person >= 18 = True
| age person < 18 = False
Here, age is a Person -> Int function that gives access to the relevant field.
Tangential note: your function can be written more simply as...
ofLegalAge :: Person -> Bool
ofLegalAge person
| age person >= 18 = True
| otherwise = False
... and even more simply as:
ofLegalAge :: Person -> Bool
ofLegalAge person = age person >= 18

Beginning Haskell: Calling a simple function, matching a data structure

I am a little embarrassed to be asking such a banal question on here, but I am trying to follow the recent 'Beginning Haskell' book (Apress) as an introduction to Haskell, but the code does not work. I found the source code online, which was identical to my own, and that doesn't work either.
data Client = GovOrg String
| Company String Integer Person String
| Individual Person Bool
deriving Show
data Person = Person String String Gender
deriving Show
data Gender = Male | Female | Unknown
deriving Show
clientName :: Client -> String
clientName (GovOrg name) = name
clientName (Company name _ _ _) = name
clientName (Individual (Person fName lName _) _) = fName ++ " " ++ lName
When I attempt to call the function with
clientName (GovOrg "NASA")
It returns "NASA".
But when I try to call it with:
clientName (Company "Virgin")
Or:
clientName (Individual "Adam" "Smith") -- (or any other permutations of this function call)
The result is Type mismatch, and:
Probable cause: `Company' is applied to too few arguments
As you might be able to tell, I have a difficult time with the syntax at this stage, but I'm sure I would have a better time of it if I could get it to work in the first place. Is there something wrong with how I call the function from the interpreter?
That's because you are not passing up the entire data. Try this:
λ> clientName (Company "Virgin" 3 (Person "fname" "lname" Male) "hello")
"Virgin"
λ> clientName (Individual (Person "Adam" "Smith" Male) True)
"Adam Smith"
Both Company and Individual are data constructors and you can inspect their type also:
λ> :t Individual
Individual :: Person -> Bool -> Client
So, for Construcing Individual you should pass Person and Bool type to it. Something like this builds up a Client type:
λ> let a = Individual (Person "Adam" "Roy" Male) True
λ> :t a
a :: Client
The problem is that you try to make a Company with Virgin as its only parameter. The parameters you actually need for a Company are a String and an Integer and a Person and another String.
clientName (Company "Virgin" 123 (Person "Three" "Fx" Unknown) "someString")
will work.
Both Company and Individual have data constructors that take more than 1 argument. Specifically Company also takes an Integer, a Person and a String; Individual also takes a Bool.
Not only that, but Individual takes a Person as first argument.
You should, for example, call:
clientName (Individual (Person "Adam" "Smith" Male) True)
Live demo

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