But then I realized that (a, Api a) should be able to completely hide completely, since no one cares, and then Api turns into a regular record of data types, not functions.
Right! The use of such an existential type, which will be performed by a simple data type, is called existential style contouring (see also FAQ ). However, this is not directly related to laziness; you can just as easily represent each field as () -> Result in a strict language. Of course, it would not be so nice to use.
The advantage of a type class is implicit, type-oriented permission: you can simply use the operations directly, as if they were monomorphic to the specific types that you are using, and it works fine, without the need for a separate name for each type for which you want to perform an operation. For example, why class classes are valuable, imagine that Num was implemented as a record; you have to go around the implementation record for each type everywhere. Worse, there is no place to put fromInteger :: (Num a) => Integer -> a , since it is polymorphic as a result and generally does not accept a value of type a !
Another example is something like Ord. You cannot represent Ord as a record, because any Ord instance must work with the very values that are eliminated by this record-type-type conversion. Having Ord as a type-type also allows us to write code that is common to any type of value that can be ordered, which is very valuable, and in this regard, using class types is somewhat similar to OOP interfaces.
However, when there are no real relevant values that they speak of, or all of them come from “outside” to act as an internal state (for example, existential), the type-type simply adds an unnecessary template and much less “first” class. Functions and data types are the real units of abstraction in Haskell; Type classes are just convenience.
So, is it enough (or maybe) enough to realize that it does not need to create an Api value at all, since all we need is a call to myf2 by the value of MyData?
The GHC is likely to create an intermediate data type, but you should not worry too much; The existential language also contains a dictionary of type class instances, which is exactly the same as your Api type. The advantage of this recording interface is not performance, but simplicity, clarity, and composability (it is easy to convert all fields of a record to create a “modified” implementation, but you cannot have a function, class).