There is several ways in which type classes or cap...
# arrow-meta
r
There is several ways in which type classes or capabilities can be achieved. The type clases part is more about polymorphism than capabilities since it’s directly related to how they are used in haskell as constrains over types that bring in new methods. In Kotlin we have subtype polymorphism but there are other types such as ad-hoc (type classes, implicits) and row polymorphism (structural based on member shape or records) In the case of ad-hoc polymorphism it allows us to say a type we may not know has capabilities of such type class or interface. This can be accomplished through multiple ways. The most popular ones are implicits like Scala which Arrow meta supports via
@given
; in contrast to scala, given implicits in Kotlin follow coherence rules like sub-typing. This means that only authors of the type class or data type are allowed to provide the instance that proofs the conformance to the protocol and that resolution is not scoped but global. Beside this rule meta allows users to override any type locally if the instance is
internal
this allows you to change behaviours ad-hoc locally but won’t allow you to export a canonical one for others to consume and therefore it preserves coherence in the same way sub-typing does. A given style type class may look like this:
Copy code
interface Eq<A> {
  fun A.eq(other: A): Boolean
}

object StringEq : Eq<String> {
  override fun String.eq(other: String): Boolean = 
    this == other
}

fun <A> Eq<A>.areEq(a: A, b: A): Boolean = 
    a.eq(b)

fun main() {
  println(StringEq.run { areEq("", "") })
}
In meta we are taking this further by using the Curry howard correspondonce to simply stablish the relationship between types and you can do this like this if you want to use @Given
Copy code
@Given
object StringEq : Eq<String> {
  override fun String.eq(other: String): Boolean =
    this == other
}

fun <A> areEq(a: A, b: A, eq: EQ<A> = given()): Boolean =
  eq.run { a.eq(b) }

fun main() {
  println(areEq("", "")) //
}
you can require any typed value for which a provider instance is declared by using
given()
in any expression position not just constructors or fields. Alternatively you can just project the syntax directly by establishing a coercion or extension proof between the types you care:
Copy code
fun interface Eq<A> {
  fun A.eq(other: A): Boolean
}

/**
 * Proof projects all members of Eq<String> over values of String,
 * enables [eq] syntax and tell the type checker String is a subtype of Eq<String> for
 * all implicit expression replacement purposes
 */
@Coercion
fun String.eqProof(): Eq<String> = 
  Eq { this == it }

fun main() {
  println("".eq("")) // proof removes "".eqProof().eq("") 
}
👍 2
💯 2