Haskell: Typeclass implies other typeclass - haskell

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.

Related

Polymorphic Types: representing multiple types using the same type synonym

My objective is giving two data types the same synonym.
I've minimized my question to the following question:
{-# LANGUAGE KindSignatures, Rank2Types #-}
class Things (h :: * -> *) where
newtype Thing1 a = Thing1 a
newtype Thing2 a = Thing2 a
instance Things Thing1 where
instance Things Thing2 where
type OneOfTwo a = forall h. Things h => h a
foo :: OneOfTwo String -> String
foo (Thing1 name) = name
foo (Thing2 name) = name
My goal is to be able to make the type synonym oneOfTwo stand for either Thing1 and Thing2 (or even more if I want to). But when I do so, any pattern matching aside from the first one in foo will be seen as redundant:
Pattern match is redundant
|
15 | foo (Thing2 name) = name
|
I'm aware that I can re-write it as the following:
newtype Thing1 a = Thing1 a
newtype Thing2 a = Thing2 a
data OneOfTwo a = One (Thing1 a)
| Two (Thing2 a)
foo :: OneOfTwo String -> String
foo (One (Thing1 name)) = name
foo (Two (Thing2 name)) = name
But I want to avoid creating a new data type OneOfTwo.
Is there a workaround to make this happen without creating a new boxing data type?
You've tried to build an existential type
type OneOfTwoE a = ∃ h. Things h => h a
meaning “whoever uses a value v of type OneOfTwo a can be sure that there is some concrete type constructor h, which is an instance of Things, such that v has type h a”. But that's not what the type you wrote means; that is a universal type
type OneOfTwoA a = ∀ h. Things h => h a
meaning that for all type constructors h that the user of v might think of, v has type h a. I.e. the single value v has in general multiple different types!
That means you can actually write
fooA :: OneOfTwoA String -> String
fooA v = (case v of {Thing1 name -> name})
++ (case v of {Thing2 name -> name})
and both of the case matches will succeed, despite matching on different constructors! This works because in each of the case expressions, v is specialised to a different h instantiation, which is ok because the type is universal.
Haskell doesn't really have existential types. It does, as you've noted, have discriminated sum types. It also can simulated existential types with universally quantified data constructors, preferrably expressed with GADT syntax:
data OneOfTwoG a where
OneOfTwo :: ∀ h a . Things h => h a -> OneOfTwoG a
which makes it conceptually possible to have something like (this code doesn't work)
fooG :: OneOfTwoG String -> String
fooG (OneOfTwo (Thing1 name)) = name
fooG (OneOfTwo (Thing2 name)) = name
To actually get that functionality, you need to put the case distinction into the class instances, like
class Things (h :: * -> *) where
gname :: h a -> a
newtype Thing1 a = Thing1 a
newtype Thing2 a = Thing2 a
instance Things Thing1 where gname (Thing1 n) = n
instance Things Thing2 where gname (Thing2 n) = n
fooG :: OneOfTwoG String -> String
fooG (OneOfTwo ϑ) = gname ϑ
At the risk of pointing out the obvious, the whole purpose of type classes is to allow you to define polymorphic functions that can be applied to multiple types, so what you're supposed to do in this situation is write:
class Things h where
foo :: h String -> String
newtype Thing1 a = Thing1 a
newtype Thing2 a = Thing2 a
instance Things Thing1 where
foo (Thing1 name) = name
instance Things Thing2 where
foo (Thing2 name) = name
That is, when you want to represent multiple types with a single name, the tool you want to use is a type class, not a type synonym.

Haskell does not support the C++ overloading style in which functions with different types share a common name

I am learning Haskell. When I gone through following documentation.
https://www.haskell.org/tutorial/classes.html
It is mentioned that "Haskell does not support the C++ overloading style in which functions with different types share a common name." I am not getting this statement, I guess ad-hoc polymorphism (which is done by using type classes) is equivalent to method overloading in C++, Java. Can some body explain me is my understanding correct ?
class Equal a where
isEquals :: a -> a -> Bool
type Id = Int
type Name = String
data Employee = Engineer Id Name
data Student = Student Id Name
getEmpId (Engineer empId _) = empId
getStudId (Student studId _) = studId
instance Equal Employee where
isEquals emp1 emp2 = getEmpId emp1 == getEmpId emp2
instance Equal Student where
isEquals stud1 stud2 = getStudId stud1 == getStudId stud2
In the above snippet 'isEquals' function is applied to two different types Employee, Student which is equivalant of overloading in C++, Java. Is my understanding correct?
Partially, yes. However, keep in mind that signature of your isEquals is always a -> a. In C++ you could easily write:
int foo(int a, int b)
int foo(int a, char b)
int foo(char a, char b)
By using typeclasses you're only able to get first and third function, never the second.
UPDATE 1:
as noted in comments, you can achieve the second version by using MultiParamTypeClasses extension (if you're using GHC). Still, there is fourth version:
int foo(int a, int a, int a)
which has wrong arity if you use a typeclass, but is perfectly fine in C++.
Type classes in Haskell are just a fancy way of providing implicit arguments. Consider this code:
data Equal a = Equal {isEquals :: a -> a -> Bool}
type Id = Int
type Name = String
data Employee = Engineer Id Name
data Student = Student Id Name
getEmpId (Engineer empId _) = empId
getStudId (Student studId _) = studId
equalEmployee :: Equal Employee
equalEmployee = Equal {
isEquals = \emp1 emp2 -> getEmpId emp1 == getEmpId emp2
}
equalStudent :: Equal Student
equalStudent = Equal {
isEquals stud1 stud2 = getStudId stud1 == getStudId stud2
}
equalThree :: Equal a -> a -> a -> a -> Bool
equalThree e a1 a2 a3 = isEquals e a1 a2 && isEquals e a2 a3
What is the difference with your code? Well, if in your code we define
equalThree :: Equal a => a -> a -> a -> Bool
equalThree a1 a2 a3 = isEquals a1 a2 && isEquals a2 a3
The difference is that in your code (with classes) compiler finds a suitable class instance by itself, using given types. In my variant, required instances are provided explicitly. But that's the only difference; in fact, your variant would be converted to mine under the hood.
So, classes do not override anything. They are just a fancy syntax for data types and implicit arguments.
Not really. The only way to get the equivalent of C++ (function; Haskell has no methods!) overloading would be a type class
class SomeFunc a where
someFunc :: a
Now you can define someFunc at (more or less) any type you want. But you would have to declare it for every function you want to 'overload', whereas in C++ it's implicit simply from having two functions with the same name.
Haskell's type system is designed to provide something C++'s doesn't: standardized behavior (expressed as laws). So, for example, the standard Prelude defines
class Eq a where
(==), (/=) :: a -> a -> Bool
and now you know every == operator in the language takes two arguments, of the same type, and returns a boolean; by contrast, C++ is perfectly fine with something like
mystring operator (==)(mystring x, string y) {
return x + " == " + y;
}
that breaks all of those 'reasonable' rules. Beyond that, type classes come with laws, e.g.
x == x = True -- x total
x == y = y == x
x == y = True => y == z = True => x == z = True
which are additional restrictions on all instances. That's made a lot easier by the fact that every instance is an instance of this type class (defined in one place), which gives you a single place to define what those laws are. In C++, such 'laws' have more of the character of conventions. Furthermore, C++ is explicitly designed to allow things like using + for string concatenation, while Haskell is explicitly designed to dis-allow things like that (that's what 'Haskell does not supporth the C++ overloading style' probably means), so laws have to have more of the character of conventions in C++, which individual types can ignore if it's convenient for them.

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.

Basic Haskell: problems with a function

me again with another basic problem I have. I'm using ghci.
I (with help) created this working code:
newtype Name = Name String deriving (Show)
newtype Age = Age Int deriving (Show)
newtype Weight = Weight Int deriving (Show)
newtype Person = Person (Name, Age, Weight) deriving (Show)
isAdult :: Person -> Bool
isAdult (Person(_, Age a, _)) = a > 18
However problems occur when I tried making a more complex function updateWeight that allows the user to change a Person's weight from it's previous value. Can you point out where I have gone wrong?
updateWeight :: Person -> Int -> Person
updateWeight (Person(_,_,Weight w) b = (Person(_,_,w+b))
The problem is that you can't use the _ placeholder on the right hand side of an expression. You'll have to pass through the unchanged values. Also, you must wrap the result of w + b with a Weight again. This should work:
updateWeight :: Person -> Int -> Person
updateWeight (Person(n, a, Weight w) b = (Person(n, a, Weight (w + b)))
You can get rid of the boilerplate of passing through the unchanged values by using record syntax for the Person type.

Confused about custom data types in Haskell

The task: I am trying to create a custom data type and have it able to print to the console. I also want to be able to sort it using Haskell's natural ordering.
The issue: Write now, I can't get this code to compile. It throws the following error: No instance for (Show Person) arising from a use of 'print'.
What I have so far:
-- Omitted working selection-sort function
selection_sort_ord :: (Ord a) => [a] -> [a]
selection_sort_ord xs = selection_sort (<) xs
data Person = Person {
first_name :: String,
last_name :: String,
age :: Int }
main :: IO ()
main = print $ print_person (Person "Paul" "Bouchon" 21)
You need a Show instance to convert the type to a printable representation (a String). The easiest way to obtain one is to add
deriving Show
to the type definition.
data Person = Person {
first_name :: String,
last_name :: String,
age :: Int }
deriving (Eq, Ord, Show)
to get the most often needed instances.
If you want a different Ord instance, as suggested in the comments, instead of deriving that (keep deriving Eq and Show unless you want different behaviour for those), provide an instance like
instance Ord Person where
compare p1 p2 = case compare (age p1) (age p2) of
EQ -> case compare (last_name p1) (last_name p2) of
EQ -> compare (first_name p1) (first_name p2)
other -> other
unequal -> unequal
or use pattern matching in the definition of compare if you prefer,
compare (Person first1 last1 age1) (Person first2 last2 age2) =
case compare age1 age2 of
EQ -> case compare last1 last2 of
EQ -> compare first1 first2
other -> other
unequal -> unequal
That compares according to age first, then last name, and finally, if needed, first name.

Resources