Files
hpr-knowledge-base/hpr_transcripts/hpr2878.txt

172 lines
13 KiB
Plaintext
Raw Normal View History

Episode: 2878
Title: HPR2878: Type classes in Haskell
Source: https://hub.hackerpublicradio.org/ccdn.php?filename=/eps/hpr2878/hpr2878.mp3
Transcribed: 2025-10-24 12:39:29
---
This is HBR episode 2008-178 entitled, Type Classes in Haskell, and in part on the series,
Haskell, it is hosted by Tuku Toroto, and in about 19 minutes long, and Karina Cleanflag,
the summer is, Tuku Toroto explains what Type Classes are, and how to use them.
This episode of HBR is brought to you by an honesthost.com.
At 15% discount on all shared hosting with the offer code, HBR15, that's HBR15.
Better web hosting that's honest and fair, at An Honesthost.com.
Hello, you're listening to the Hacker Public Radio, and this is Tuku Toroto.
This episode is about Type Classes in Haskell.
I feel like I'm doing these episodes in a bit of a wrong order, but I guess it's better
late than now, so the Type Classes are the Haskell's value of doing
ad hoc polymorphism or overloading, so you can define a set of functions that can operate
on a more than one specific web host either.
For example, Equality is one of one of these things that is done with the Type Classes.
In Haskell days, no default Equality, it has to be or it always has to be defined.
In some other languages, the Equality is defined by default as a comparison of the memory
location.
If you're comparing two pieces of data and they are in the same location as the memory,
so they are actually the identity content, then it's the Equality.
In those languages, you can, of course, define your own Equality operator or Equality operator
that actually looks into the data instead of just comparing the location of the data,
but not in the Haskell.
The Haskell you have to, there's no default Equality, it has to be defined, and it's
never about the memory location, so there's two parts in this piece.
The first is the Type Class definition, which defines the common interface, defines what
operations are available to any data that has instance of this Type Class.
And then the other part is the actual instance definition, which defines the implementation
for that template or interface, and that's for a specific type of data.
So basically, the Type Class definition is generic, it usually has a slot or empty space
with the data codes and the actual definition puts your own data in that specific slot.
So for example, the Equality, the Type Class definition is written as class, EQ, A,
where Equality operator is type of A to A to Bool, and not Equality operator is type
of A to A to Bool, and then the standard definition, X, not E equals Y, is not X equals
Y. So in the EQ Type Class, there's two operators, EQ and not EQ, both of those data.
Two pieces of data of the same type and return Bool, indicating if these are same or not the same.
And then the default definition for the not EQ, which is just the inverse of the EQ,
it is written as a not X equals Y. So it uses the Equality comparison and the inverse, the inverse
that is solved. And the important part, well, all of this is important, I guess, but the
interesting part here is that it starts with a class, EQ, EQ is the name of the type class,
A, where, and A is a law case, it's a type parameter, and that A is used in the operators.
For example, the Equality operator is type of A to A to Bool. So whatever you are, whatever,
you are making instance of this, that type is the A, and that A is used in the Equality
comparison. It's a bit confusing, it probably sounds a bit confusing with my explanation,
but if you have a look at the show notes, it's probably clear. So you can read this definition
as a class EQ, A, that has two functions with following signatures and implementation.
In other words, given two A, this function determinants are the EQ or not, that's the call return type.
Not EQ is defined in terms of EQ, so it's enough to define one, and you get another final property.
But you can totally still define both of them, if you are so inclined, if you can,
if you have some clever way of optimizing the not EQ operator, for example.
So let's continue with our example. We could have our own data type called size,
which is just an enumeration, so it's a data, size equals small by medium by plots.
So our size can have three values, small medium and large, and to define instance of the EQ for this,
we just write instance, EQ, size, there. So now we are putting size in place of A,
and then we have to write equal operator, and I'm not going to write it in a sort of a case by case.
So small equals small is true, medium equals medium is true, large equals large is true,
and then underscore equals underscore is false.
So what I'm saying here is that if given two small values,
this will return as a true, the equality comparison, same when you are given two medium or two large,
and in every other case, we are going to return false.
And the not equal operator, we are not defining, we are using the standard definition to use the inverse of the equality.
So with this definition, now we could compare sizes, we could write small equals small,
and we could get true or large, not equal slots, and get false.
Of course, writing these things by hand can be with details, and you feel like you are repeating yourself.
So has can derive these false.
So we can have the same data definition, just data says, equals small by medium by large,
deriving, so freed, ego.
So under it, our other five classes, so is used to turn our data type into the string,
and read is used to turn the string into our data pipe.
And equal is the one we defined manually here, but we can tell the hash code to derive, equal for us.
It can derive that in quite many occasions.
You can have pretty complex data and hash code, and still do that.
There's some limitations there, and I'm not going to explore right now,
because I probably will remember all of them.
But in simple case like this, we don't have to define anything.
We can just tell hash code to derive it, and it will write it correctly for us.
There can of course be hierarchy.
Between those classes, obviously, is probably a wrong word to use here.
But there can be hierarchy between those classes.
For example, there's a type class or that is used to compare the ordering of items.
And that is written as a class, equal a, set arrow, or a, where.
This is the definition.
So this is right as a class or a, where a has instance of equal with pile of functions as follows.
And there's a pile of functions.
There's a compare, that is a to a to ordering, then there's a less than a to a to rule,
greater or equal than a to a to rule greater than a to a to a to rule less than or equal a to a to rule max a to a to a to mean a to a to a.
So whenever you have an order in your code, something something that's an order instance,
you can, you have access to this, and you of course have, and you have access to the equal equality,
because or spill in them.
You can define ordering without having the equality.
So, and it's enough to implement either the compare or the less or equal.
There has to be defined rest of the functions in terms of this to one of these two.
So for example, our size, that it would be instance or size, where small less or equal to underscore is true,
meaning small is always smaller or equal to everything.
Medium less than or equal to small falls medium less or equal to underscore true, meaning that small is,
meaning that medium is not smaller or equal than small, but for everything else, it's either smaller or equal.
And then large less than equal large is true, large less than equal to underscore falls,
meaning that large is always less or equal to large, and it's not less or equal to anything else.
And with that definition, we can compare our sizes, sorry, order than we can say.
For example, medium is greater or equal to small falls for us.
And there's lots and lots of classes in the standard library.
For example, num for numerical variations, integral for integer numbers, floating for floating numbers.
You can totally have your own data type, and then you can define integral instance for that,
and you get some numerical variations for integer numbers.
There is a show that I mentioned already, turning data into strings and read for turning strings into the data.
Enum is interesting, it's for sequentially ordered types that can be enumerated, for example, enumerations, you can say.
And pounded is for things with upper and lower bound.
For example, our size is pretty good candidate for having a both enum and pounded, because you can say which is the lower bound,
that will be the small, and upper bound is large, all the values are in between these.
And enum is for enumerating them, you can, for example, say, give me all the values from small to large,
and then it will give small, medium and large for you.
Or you can even say that if you combine enum and pounded, you can say that give me all the values and starting from the main bound, the lowest bound.
And you don't have to specify the upper bound.
And it will automatically start from the small and go to the enumerate them in order to the largest.
And if you later on add, if you later on add in your data, new value after the large, extra large, then it will automatically work in the other places of your program,
you are asking all the values starting from the lower bound, it will produce you with the small, medium, large, extra large.
So you can write really generic code when you start using the type classes.
I have convoluted, made up example here, because check that takes a, it is a type of check, or a, show a fat arrow, a to a to string.
So it takes anything that has instance for the ordering and showing.
So you can, so you can compare the ordering, I mean, you can compare the, yeah, you can compare the ordering, which one is bigger than which one, and you can turn the data into string.
There's nothing, no, no other limitations here.
And it's written as a check AB is case compare AB of LT arrow.
So a, a quotation is smaller than quotation show B.
So if you compare these two values that are given to the function and A is less than B,
then it's going to produce a string with A is smaller than B, and it's going to use the show, show to turn A into the string.
So if it's a number, if the number will be turned into string, if it's our size, then we are going to get a text that represents our data.
And then there's two more cases, GT arrow, show a plus is greater than plus show B.
So if the A is greater than B, we are going to get a string A is greater than B, the A and D string representation of our data.
And last one is equal arrow, show A plus, and show B plus, are equal.
So what is in that?
So it takes two parameters that are of same type.
Both types have ORT and show instances.
That's only thing we know about the data.
Or this for ordering, so is the show is returning data into string.
That is very useful displaying it.
And it produce a string telling the result of comparison.
So if we return to our size data, we can write up, check medium small, and we are going to get a string, medium is greater than small.
If we write check small large, we are going to get a string, small is smaller than large.
We can write check 73, these are indexes, 7 is greater than 3, that's a string.
We can even write check square bracket 1, 2, square bracket, square bracket 1, 1, 1, square bracket.
And we are going to get a string, square bracket 1, 2, square bracket is greater than square bracket 1, 1, 1, square bracket.
So it does a comparison of the, what that was, it does a comparison to a list.
And the comparison of the list works in a way that it compares the first element.
And if they are saying it compares the next element, the next element, and the next element until it finds a first one where the comparison is different and equal.
And that is what defines, that is the, which one of the list is greater?
Yeah, greater.
So our simple check function, that does not know anything about the data that it can, it handles except that they can be ordered and they can be turned into strings, then handle very different parts of data.
And there are very many extensions to the five classes to add more behavior.
And there are somewhat more complicated to use.
But there are really interesting options.
For example, it is possible to define a five class that takes two type parameters, A and D, for example.
So you can have much more complex behavior, but that power comes with a course to course.
The code is more complex and you have to take into account things that you normally would have to take into account.
But those are, I like sometimes just reading what kind of extensions they are, for example, for the five classes.
That's about what I have to say about these things now.
I hope this clears up a lot of things that I have been talking about earlier.
This I really should have done this bit earlier.
I didn't, originally I didn't intend to teach that much of Haskelfeld in the podcast service, but that's how it seemed to have turned.
So thanks for listening.
Questions and comments and all kinds of feedback is always welcome.
The best way to catch me nowadays is either by the email or at the fediverse, where I'm a developer at Mastertons.oso.
Thank you.
Heck, I probably radio was founded by the digital dog pound and the infonomicon computer club, and it's part of the binary revolution at binrev.com.
If you have comments on today's show, please email the host directly, leave a comment on the website or record a follow-up episode yourself.
Unless otherwise status, today's show is released on the creative comments, attribution, share a light, 3.0 license.
Thank you.