it seems like instances are types (like ShirtSize) which you
can apply type-class functions on (e.g the (==) function from MyEq)
Absolutely correct.
In Haskell a type is a defined structure of data. Every value that exists in Haskell code has a defined type. And a type can be made an instance of a class, which means... actually, hold that thought. I want to talk about functions.
Functions have type signatures, defining which type(s) they can be used on. If a function is defined to work on a particular type, then the function can be used on any value that has that type. If a function is defined to work on a particular class, then it can be used on any value of any of the types that are instances of that class.
When you define a class you describe some minimal set of functions (eg ==
in your example) which have to be implemented for all the types that want to be instances of that class. The class defines names and signatures for those functions, and that definition means those names and signatures are fixed. They will be the same for every instance of the class.
But the implementations aren't fixed by the class. They can be different for different types. We make a type into an instance of a class by writing an instance statement, in which we can define how those functions will work. If the class provides a default implementation of a function, then the different instance types can override the default and have their own definitions. And if there is no default, then the instance types must have their own definitions.
Now you have a minimal set of functions which can be called with any value of any of the types. And you can write more functions that work by calling those functions, and build up from there.
The idea is really useful, but IMHO the terminology is awful. Saying that these types are instances of a class makes it sound as if they're subtypes or child types, inheriting properties from parent types. But it's not like that at all. Being an instance of a class is like being a member of a club. Lots of different, unrelated types can all be instances of the same class. And one type can be an instance of lots of different unrelated classes, all at the same time.
In Rust, they have the same idea, but with the word 'trait' instead of 'class'. Instead of saying "this type is an instance of that class", they would say "this type implements that trait". I think that gets the idea across much better.