Hi! I've asked this question about the different i...
# arrow-meta
c
Hi! I've asked this question about the different implementation/use-cases of typeclasses in Kotlin, and Mr. Raja redirected me to here to discuss those. Please tell me if I'm not making sense, it's a quite new concept to me ^^
r
@CLOVIS LOL this the first time someone calls me Mr not an airport, you can just call me Raul ☺️
I'll try to describe a bit more about type classes and how we solve this issue in meta as soon as I get to my computer. You may start thinking in type classes as just abstract interfaces which bring capabilities to an object. This capabilities are proven by an instance that proves the given object can implement the type class behavior. Ideally they come with laws but the same encoding can serve other purposes people frequently use for architecture of applications.
c
Sorry, I never know how to address people 😅 So that vision would be closer to what type proofs are doing? Do you have an example on when that can be useful? I completely see the benefits of typeclasses for polymorphism (stuff like replacing
Comparable/Comparator
by a single interface—which I believe is called
Order
?), to have a more streamlined dependency injection system, and I guess that finding a default implementation at compile-time is very convenient because it allows the user not to have to find one by themselves (which is probably nice since this way of writing code does seem like it would use way more interfaces/typeclasses than what we're used to), but I don't see a very big use-case, and your wording makes me think there's one 🤔 Also, my current understanding of typeclasses is that they conceptually are normal interfaces that define behavior, and that the ‘special' thing is that they can be implemented externally (which is coherent with KEEP-87 using a specific keyword for implementation but not for declaration). I guess there's something deeper in there?
I see, that sounds really good. The only thing is that it requires to define a new interface? Let's say we have a regular Java behavior interface, like
Comparable
.
Copy code
// This interface already exists
interface Comparable<T> {
  fun compareTo(other: T): Int
}
Copy code
// But for typeclasses we need this one (sorry for the naming, that probably doesn't follow your conventions)
interface ExternalComparable<T> {
  fun T.compareTo(other: T): Int
}
Let's say now that we have an arbitrary class
data class Car(...whatever...)
In regular Kotlin, we would write
data class Car(...) : Comparable<Car>
Except that we can't do that if we don't control Car, etc. Typeclasses allow to solve that:
data class Car(...whatever...)
Copy code
@Given
object CarComparator : ExternalComparable<Car> {
  override fun Car.compareTo(other: Car): Int
}
If I understand your comments correctly, the Given annotation allows to tell the compiler “this is the default implementation of
ExternalComparable
for `Car`s, let me use it implicitly” (and that only works in your own scope to not break everything). Now, if I understand correctly, the Arrow Meta compiler let's you write
Copy code
val c = Car(...)
val d = Car(...)
val r = c.compareTo(d)
Here, no need to specify that the implementation we use is
CarComparator
, the compiler finds it by itself (assuming we're in the right scope). If all of this is correct, I have a few questions: • What if we want to use another implementation than the
Given
one? I think it was mentioned in the wiki that typeclasses were useful to have multiple implementations of interfaces (eg. to sort based on different criterion in different cases). • If I'm not wrong, this solution allows to call implemented functions fine, but I don't think it satisfies
is
relationships:
Copy code
val c = Car(...)
if (c is Comparable)
  ...
It seems to me like that would be useful, and that the only solution would be to have the compiler be able to generate the ‘extension interface' syntax from any regular interface, so it could do the link itself?
r
it does not need to define a new interface, you can simply project with a proof Comparable over any type and also coerce it but it will not satisfy
is
checks because that is a runtime check which implies a subtype relationship:
Copy code
@Coercion fun Car.comparable(): Comparable<Car> = ...
val car1: Car = Car()
val c: Comparable<Car> = car1 //ok by coercion
val result = car1 > car2 //> from Comparable available by extension
Same applies to Comparator.
What if we want to use another implementation than the 
Given
 one?
when used as value argument in functions or constructors you can always pass the instance manually or provide an internal override if it’s not in value argument position so it applies locally to the whole module
type classes don’t need to expose their members as extension functions, that is just one of the possible encodings
interface fun with SAM interfaces is also another I’m currrently investigating
c
I understand why @Coercion can be cleaner than actually defining is, the problem I see is that it doesn't inter-operate with the standard library, which always expects implementation to be defined in the object itself (apart from
filter
and
sort
)
r
It does interoperate, the difference is that without the plugin you need to call the proof first explicitly to obtain the value and with the plugin that call is injected automatically
Until adhoc polymorphism is solved across Kotlin without plugins other plugins use this approach for places where otherwise injection would have worked automatically. See serialization plugin for example.
The proof system is among the examples we sent and some of the compiler team at Jetbrains is looking as example use cases to solve some of this problems in Kotlin. Adhoc polymorphism in Kotlin can still show up in different ways like multiple receivers, proofs and others which tackle the scoping and DI problems differently