In Rust I can do this: ```struct Circle { radi...
# functional
k
In Rust I can do this:
Copy code
struct Circle {
    radius: u32,
}

impl Circle {
    fn new(radius: u32) -> Circle {
        Circle { radius }
    }
}

trait Shape {
    fn area(&self) -> f64;
    fn perimeter(&self) -> f64;
}

impl Shape for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius as f64).powi(2)
    }

    fn perimeter(&self) -> f64 {
        2.0 * std::f64::consts::PI * (self.radius as f64)
    }
}

fn pretty_print<T: Shape>(shape: T) {
    println!("Area: {}", shape.area());
    println!("Perimeter: {}", shape.perimeter());
}

fn main() {
    let circle = Circle::new(1);

    pretty_print(circle);
}
So the trait is like a Kotlin Interface. And now my function pretty_print can take any type(struct) that implements Shape. This seems like a very powerful feature to me as it means I can extend types. How can I achieve the same thing in Kotlin?
So what I want is basically extension interfaces I guess. I read that something similar can be done with decorators?, but I couldn't quite understand how
In Rust it can also be extended to external types using the newtype pattern
I think this is solved in Scala with Typeclasses? but I have never done any Scala.
e
Copy code
import kotlin.math.*

interface Shape {
    fun area(): Double
    fun perimeter(): Double
}

class Circle(val radius: Int) : Shape {
    override fun area(): Double = PI * radius.toDouble().pow(2)
    override fun perimeter(): Double = 2 * PI * radius
}

fun prettyPrint(shape: Shape) {
    println("Area: ${shape.area()}")
    println("Perimeter: ${shape.perimeter()}")
}

fun main() {
    val circle = Circle(1)
    prettyPrint(circle)
}
s
I think what OP is asking for here is a way to extension-implement interfaces for external/existing classes
k
Yes @Starr that was what I was thinking about. I see that multiple receivers aim to solve this?
s
That matches my understanding as well. I admit that I haven't used context receivers yet, so I can't provide a code sample, but there might be an example in one of the links I sent.
e
Using the current context receivers prototype it looks like this:
Copy code
import kotlin.math.*

interface Shape<T> {
    fun T.area(): Double
    fun T.perimeter(): Double
}

class Circle(val radius: Int) 

object CircleShapeImpl : Shape<Circle> {
    override fun Circle.area(): Double = PI * radius.toDouble().pow(2)
    override fun Circle.perimeter(): Double = 2 * PI * radius
}

context(Shape<T>)
fun <T> prettyPrint(shape: T) {
    println("Area: ${shape.area()}")
    println("Perimeter: ${shape.perimeter()}")
}

fun main() {
    val circle = Circle(1)
    with(CircleShapeImpl) {
        prettyPrint(circle)
    }
}
Unlike Rust, there are no restrictions on where the different declarations are coming from.
Shape
and
Circle
can be declared in separate modules and
ShapeCircleImpl
can be in yet another module that depends on them both.
k
I was just playing around with this 🙂
Copy code
interface Shape<T> {
    fun perimeter(shape: T): Double
    fun area(shape: T): Double
}

context(Shape<T>)
fun <T>prettyPrint(shape : T) = "Perimeter: ${perimeter(shape)} Area: ${area(shape)})})}"

object SquareShape : Shape<Square> {
    override fun perimeter(shape: Square): Double = shape.side * 4.0
    override fun area(shape: Square): Double = shape.side * shape.side.toDouble()
}

object CircleShape : Shape<Circle> {
    override fun perimeter(shape: Circle): Double = 2 * Math.PI * shape.radius
    override fun area(shape: Circle): Double = Math.PI * shape.radius * shape.radius
}


data class Square(val side: Int)
data class Rectangle(val width: Int, val height: Int)
data class Cube(val side: Int)
data class Cuboid(val width: Int, val height: Int, val depth: Int)
data class Circle(val radius: Int)


fun main() {

    val square = Square(10)
    val circle = Circle(10)
    val rectangle = Rectangle(10, 20)


    with(SquareShape) {
        println(prettyPrint(square))
    }

    with(CircleShape) {
        println(prettyPrint(circle))
    }

    println(prettyPrint(rectangle)) // Will not compile until we add a RectangleShape context
}
I haven't written any Scala, but this is similar to scala implicits right? The fact that this can be used across separate modules is really good. Really nice stuff from the Kotlin team 🙂 Is it possible in the future that the compiler can infer the context so you don't have to explicitly define it at the edge?
e
In the current prototype you have to always explicitly bring the contexts into scope. We might introduce some improvements in the future. For now, we are focused on the core features of context management and propagation.
Also note, that you should not simply copy the way typeclasses are defined in Scala to Kotlin. Kotlin has a concept of receivers and the "type-classes" like
Shape
look and work nicer in Kotlin if you declare their functions with the receiver
T
instead of
(shape: T)
parameter:
Copy code
interface Shape<T> {
    fun T.area(): Double
    fun T.perimeter(): Double
}
k
Yes that looks a lot nicer 🙂