Kristian Nedrevold
11/09/2022, 8:24 PMstruct 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?Kristian Nedrevold
11/09/2022, 8:26 PMKristian Nedrevold
11/09/2022, 9:00 PMKristian Nedrevold
11/09/2022, 9:01 PMelizarov
11/10/2022, 2:12 AMimport 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)
}
Starr
11/10/2022, 7:55 AMStarr
11/10/2022, 7:56 AMKristian Nedrevold
11/10/2022, 9:31 AMStarr
11/10/2022, 9:40 AMelizarov
11/14/2022, 6:17 PMimport 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.Kristian Nedrevold
11/14/2022, 6:24 PMinterface 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?elizarov
11/14/2022, 6:33 PMelizarov
11/14/2022, 6:37 PMShape
look and work nicer in Kotlin if you declare their functions with the receiver T
instead of (shape: T)
parameter:
interface Shape<T> {
fun T.area(): Double
fun T.perimeter(): Double
}
Kristian Nedrevold
11/14/2022, 6:48 PM