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

If we have a Person type defined as:

--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) 

is written as one definition of flipNames ? Can we somehow use the constructor and use it with different parameters? Something like:

 flipNames (constructor fname lname) = c lname fname 
+7
constructor programming-languages pattern-matching haskell
source share
5 answers

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 same. There is no general abstraction that captures constructors without their arguments.

+5
source share

Although Ganesh answered your exact question, I want to say that your problem simply indicates the wrong approach to the development of data types.

The following approach is much more flexible and fixes 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 quite simple: whenever you see that you are creating several constructors with the same fields, just use one constructor and enter another field with the enumeration type, as in the code above.

You will not lose the ability to match patterns, since patterns can be similar to Person Male firstName lastName , and you can draw conclusions such as Gender Enum and Bounded , which will undoubtedly help you with types that are not so trivial. For example:.

 data Gender = Male | Female deriving (Enum, Bounded) allGenders :: [Gender] allGenders = enumFrom minBound maidenName :: Person -> Maybe String maidenName (Person Female _ z) = Just z maidenName _ = Nothing 
+11
source share

To add another option, you can do something similar with phantom types. Note that you want to do this because your Person constructors are redundant, they exist only to distinguish between men and women. You can raise this distinction into the type system and let the inferencing type take care of the Male / Female .

 {-# LANGUAGE FlexibleInstances #-} data Person a = Person { first :: String, last :: String } deriving (Show, Eq) data Male data Female flipName :: Person a -> Person a flipName (Person fl) = Person lf 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 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 disadvantage of this is that you cannot carry an extra type parameter. However, the growth potential is that now you can define instances of typeclass with various implementations based on the types Male and Female . Although the implementation of the latter requires the extension FlexibleInstances .

+3
source share

Yes, you can do similar (but not identical) using viewing templates!

 {-# 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 
+2
source share

You cannot map a variable to such a constructor because templates are not first-class citizens in Haskell. You may have code specific to your function, but not generic.

If you are interested in such ideas, look at the bondi research language , which supports related constructors like this one. It really opens up an interesting new expression. In practice, it greatly simplifies writing code that generates a more accurate structure such as algebraic data.

+1
source share

All Articles