From 65312fcdfa42349739297d1271f72cef40f35abd Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Mon, 8 May 2023 19:58:55 +0100 Subject: [PATCH 01/17] Initial/transformation chains example --- proposals/self-types.md | 101 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 proposals/self-types.md diff --git a/proposals/self-types.md b/proposals/self-types.md new file mode 100644 index 000000000..710924fb4 --- /dev/null +++ b/proposals/self-types.md @@ -0,0 +1,101 @@ +# Self types + +* **Type**: Design proposal +* **Authors**: Maksim Grankin +* **Status**: Prototype implemented +* **Issue**: [KT-6494](https://youtrack.jetbrains.com/issue/KT-6494) + + +A **Self type** is type that refers to the receiver type. + +```kotlin +open class A { + fun foo(): Self { + return this; + } +} + +class B : A { + +} + +val x: B = B().foo(); // foo return type is B +``` + +## Motivation + +Self types can be implemented by programmer with boilerplate code using recursive generics and some additional unchecked casts. +They can be used in multiple useful and popular patterns, so there is a reason and community need to make it a language feature. + +## Usage examples + +One of the most common example of Self types application is [abstract builder pattern](https://medium.com/@hazraarka072/fluent-builder-and-powering-it-up-with-recursive-generics-in-java-483005a85fcd). However, in Kotlin builders are usually implemented via extension recievers.Although, if we want to have transformation chain of immutable object/data, Self types are really usefull. + +### Transformation chains + +Using transformation chains we can implement *Lazy* containers. *Lazy* container is container that contains computation for some *real* container. This allows to create a sequence of changes to container without computing it only when needed. + +```kotlin + +abstract class Lazy>(val computation: () -> T) { + protected abstract fun create(computation: () -> T): Self +} + +abstract class LazyContainer>(computation: () -> T) : + Lazy(computation) { + fun applyFunction(f: (T) -> T): Self = create { f(computation()) } +} + +class LazyList(computation: () -> List) : LazyContainer, LazyList>(computation) { + override fun create(computation: () -> List): LazyList = LazyList(computation) + fun add(elem: T): LazyList = create { computation() + elem } +} + +class LazySet(computation: () -> Set) : LazyContainer, LazySet>(computation) { + override fun create(computation: () -> Set): LazySet = LazySet(computation) + fun add(elem: T): LazySet = create { computation() + elem } +} +val list = LazyList { listOf(1, 2, 3) } + .applyFunction { l -> l.subList(1, 2) } + .add(15) + .computation() +val set = LazySet { setOf(1, 2, 3) } + .applyFunction { s -> s.map { it + 1 }.toSet() } + .add(3) + .computation() +``` + +With **Self type** feature the same code would look much easier to read. + +```kotlin +import kotlin.Self + +@Self +abstract class Lazy(val computation: () -> T) { + protected abstract fun create(computation: () -> T): Self +} + +@Self +abstract class LazyContainer(computation: () -> T) : + Lazy(computation) { + fun applyFunction(f: (T) -> T): Self = create { f(computation()) } +} + +class LazyList(computation: () -> List) : LazyContainer, LazyList>(computation) { + override fun create(computation: () -> List): LazyList = LazyList(computation) + fun add(elem: T): LazyList = create { computation() + elem } +} + +class LazySet(computation: () -> Set) : LazyContainer, LazySet>(computation) { + override fun create(computation: () -> Set): LazySet = LazySet(computation) + fun add(elem: T): LazySet = create { computation() + elem } +} +val list = LazyList { listOf(1, 2, 3) } + .applyFunction { l -> l.subList(1, 2) } + .add(15) + .computation() +val set = LazySet { setOf(1, 2, 3) } + .applyFunction { s -> s.map { it + 1 }.toSet() } + .add(3) + .computation() +``` From b1940f237794fa69b84729ae40e3026f1efaa6d1 Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Mon, 8 May 2023 20:06:05 +0100 Subject: [PATCH 02/17] Fix tabulation for lazy containers example --- proposals/self-types.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/proposals/self-types.md b/proposals/self-types.md index 710924fb4..b52482895 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -56,13 +56,13 @@ class LazySet(computation: () -> Set) : LazyContainer, LazySet>( fun add(elem: T): LazySet = create { computation() + elem } } val list = LazyList { listOf(1, 2, 3) } - .applyFunction { l -> l.subList(1, 2) } - .add(15) - .computation() + .applyFunction { l -> l.subList(1, 2) } + .add(15) + .computation() val set = LazySet { setOf(1, 2, 3) } - .applyFunction { s -> s.map { it + 1 }.toSet() } - .add(3) - .computation() + .applyFunction { s -> s.map { it + 1 }.toSet() } + .add(3) + .computation() ``` With **Self type** feature the same code would look much easier to read. @@ -91,11 +91,12 @@ class LazySet(computation: () -> Set) : LazyContainer, LazySet>( fun add(elem: T): LazySet = create { computation() + elem } } val list = LazyList { listOf(1, 2, 3) } - .applyFunction { l -> l.subList(1, 2) } - .add(15) - .computation() + .applyFunction { l -> l.subList(1, 2) } + .add(15) + .computation() val set = LazySet { setOf(1, 2, 3) } - .applyFunction { s -> s.map { it + 1 }.toSet() } - .add(3) - .computation() + .applyFunction { s -> s.map { it + 1 }.toSet() } + .add(3) + .computation() ``` + From 35642bf52bd382d5807c63fcf863ee3b38295311 Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Mon, 29 May 2023 13:13:00 +0100 Subject: [PATCH 03/17] Add Observers --- proposals/self-types.md | 148 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/proposals/self-types.md b/proposals/self-types.md index b52482895..828a2e5bb 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -100,3 +100,151 @@ val set = LazySet { setOf(1, 2, 3) } .computation() ``` +### Observer pattern + +Observer pattern can also be implemented using **Self types**. + +```kotlin +abstract class AbstractObservable> { + private val observers: MutableList<(Self) -> Unit> = mutableListOf() + + fun observe(observer: (Self) -> Unit) { + observers += observer + } + + @Suppress("UNCHECKED_CAST") + protected fun notification() { + observers.forEach { observer -> + observer(this as Self) + } + } +} + +class User(val name: String) : AbstractObservable() { + val friends: MutableList = mutableListOf() + + var status: String? = null + set(value) { + field = value + notification() + } +} + +class Company(val name: String) : AbstractObservable() { + val potentialEmployees: MutableList = mutableListOf() + + var hiring: Boolean = false + set(value) { + field = value + notification() + } +} + +val user1 = User("Maxim").apply { + observe { + if (it.status != null) { + it.friends.forEach { friend -> + println("Sending message to friend {${friend.name}} about new status: ${it.status}") + } + } + } +} + +val company: Company = Company("ITMO University").apply { + observe { + it.potentialEmployees.forEach { potentialEmployee -> + println( + "Sending notification to potential employee " + + "{${potentialEmployee.name}} that company hiring status is:" + + " ${if (it.hiring) "Hiring" else "Freeze"}." + ) + } + } +} + +company.potentialEmployees.add(user1) +company.potentialEmployees.add(user2) + +company.hiring = true +val user2 = User("Ivan") +user1.friends.add(user2) + +user1.status = "Looking for a new job" +``` + +With **Self type** feature the same code would look much easier to read and **do not contain unchecked casts** + +```kotlin +import kotlin.Self + +@Self +abstract class AbstractObservable { + private val observers: MutableList<(Self) -> Unit> = mutableListOf() + + fun observe(observer: (Self) -> Unit) { + observers += observer + } + + protected fun notification() { + observers.forEach { observer -> + observer(this) + } + } +} + +class User(val name: String) : AbstractObservable() { + val friends: MutableList = mutableListOf() + + var status: String? = null + set(value) { + field = value + notification() + } +} + +class Company(val name: String) : AbstractObservable() { + val potentialEmployees: MutableList = mutableListOf() + + var hiring: Boolean = false + set(value) { + field = value + notification() + } +} + +val user1 = User("Maxim").apply { + observe { + if (it.status != null) { + it.friends.forEach { friend -> + println("Sending message to friend {${friend.name}} about new status: ${it.status}") + } + } + } +} + +val company: Company = Company("ITMO University").apply { + observe { + it.potentialEmployees.forEach { potentialEmployee -> + println( + "Sending notification to potential employee " + + "{${potentialEmployee.name}} that company hiring status is:" + + " ${if (it.hiring) "Hiring" else "Freeze"}." + ) + } + } +} + +company.potentialEmployees.add(user1) +company.potentialEmployees.add(user2) + +company.hiring = true + +// stdout: Sending notification to potential employee Maxim that company hiring status is: Hiring +// stdout: Sending notification to potential employee Ivan that company hiring status is: Hiring + +val user2 = User("Ivan") +user1.friends.add(user2) + +user1.status = "Looking for a new job" +// stdout: Sending message to friend Ivan about new status: Looking for a new job +``` \ No newline at end of file From 866efcda7258b6ac3f40600448d936a20264b218 Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Mon, 29 May 2023 14:45:25 +0100 Subject: [PATCH 04/17] fix space --- proposals/self-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/self-types.md b/proposals/self-types.md index 828a2e5bb..0882bd67b 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -29,7 +29,7 @@ They can be used in multiple useful and popular patterns, so there is a reason a ## Usage examples -One of the most common example of Self types application is [abstract builder pattern](https://medium.com/@hazraarka072/fluent-builder-and-powering-it-up-with-recursive-generics-in-java-483005a85fcd). However, in Kotlin builders are usually implemented via extension recievers.Although, if we want to have transformation chain of immutable object/data, Self types are really usefull. +One of the most common example of Self types application is [abstract builder pattern](https://medium.com/@hazraarka072/fluent-builder-and-powering-it-up-with-recursive-generics-in-java-483005a85fcd). However, in Kotlin builders are usually implemented via extension recievers. Although, if we want to have transformation chain of immutable object/data, Self types are really usefull. ### Transformation chains From 2290975f0f58a0b61d926ebed5f4ee253547082a Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Mon, 29 May 2023 18:00:14 +0100 Subject: [PATCH 05/17] Add recursive containers --- proposals/self-types.md | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/proposals/self-types.md b/proposals/self-types.md index 0882bd67b..e9726b377 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -247,4 +247,47 @@ user1.friends.add(user2) user1.status = "Looking for a new job" // stdout: Sending message to friend Ivan about new status: Looking for a new job +``` + +### Recursive containers + +```kotlin +abstract class AbstractNode>(val children: List) + +class Node>(children: List = emptyList()) : AbstractNode(children) { + fun doTheBest() = println(42) +} + +val betterTree = Node>(listOf( + Node>(), + Node>(listOf(Node>())) +)) + +betterTree.children + .flatMap { it.children } + .forEach { it.doTheBest() } +``` + +With **Self type** feature the same code would look much easier to read. + + +```kotlin +import kotlin.Self + +@Self +abstract class AbstractNode(val children: List) + +@Self +class Node(children: List = emptyList()) : AbstractNode(children) { + fun doTheBest() = println(1) +} + +val betterTree = Node(listOf( + Node(), + Node(listOf(Node())) +)) + +betterTree.children + .flatMap { it.children } + .forEach { it.doTheBest() } ``` \ No newline at end of file From 5caf1d3a83677f06496b821ab5905a8dce765ea3 Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Tue, 30 May 2023 22:24:02 +0100 Subject: [PATCH 06/17] Abstract factory --- proposals/self-types.md | 50 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/proposals/self-types.md b/proposals/self-types.md index e9726b377..f6fb8a832 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -290,4 +290,54 @@ val betterTree = Node(listOf( betterTree.children .flatMap { it.children } .forEach { it.doTheBest() } +``` + +### Abstract factory + + +```kotlin +abstract class Element(val factory: Factory) + +interface Factory> { + fun create(): Element +} + +abstract class SpecificFactory> : Factory { + abstract fun doSpecific() +} + +class ConcreteFactory : SpecificFactory() { + override fun create(): Element = object : Element(this) {} + override fun doSpecific() = println("Soo concrete!") +} + +fun > test(element: Element) { + element.factory.doSpecific() +} +``` + +With **Self type** feature the same code would look much easier to read. + + +```kotlin +abstract class Element(val factory: Factory) + +@Self +interface Factory> { + fun create(): Element +} + +@Self +abstract class SpecificFactory : Factory { + abstract fun doSpecific() +} + +class ConcreteFactory : SpecificFactory() { + override fun create(): Element = object : Element(this) {} + override fun doSpecific() = println("Soo concrete!") +} + +fun > test(element: Element) { + element.factory.doSpecific() +} ``` \ No newline at end of file From 86ec1cd52198f671d70fa30ed66bc7dbb094384a Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Tue, 30 May 2023 22:42:14 +0100 Subject: [PATCH 07/17] Initial design --- proposals/self-types.md | 53 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/proposals/self-types.md b/proposals/self-types.md index f6fb8a832..ff5103a5f 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -340,4 +340,55 @@ class ConcreteFactory : SpecificFactory() { fun > test(element: Element) { element.factory.doSpecific() } -``` \ No newline at end of file +``` + +## Design + +Self-type behaves exactly same as covariant recursive generic type parameter that is bounded with bound. + +```kotlin +fun interface Foo> { + +} +``` + +Can be rewriten as: + +```kotlin +@Self +fun interface Foo { + +} +``` + +### `Self` bound + +`Self` type is bounded to the nearest receiver. + +```kotlin +@Self +class Foo { + @Self + class Bar { + fun x(): Self // Self from Bar + } + + fun y(): Self // Self from Foo +} +``` + +With this design decision it is imposible to access `Self` type of `Foo` inside `Bar`. It is considered to be not very popular situation, so user can create their own recursive generic for `Foo` or `Bar`. + +### `this` assingment + +Only `this` that refers to function receiver is assignable to the self-type with the correspoding bound. + +```kotlin +@Self +abstract class A { + fun self(): Self = this + + fun other(a: A): Self = a.self() // Do not compile as we cannot return Self of an other object +} +``` + From 810070602276ff16c948fbcfb5d4a7be166d76a1 Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Wed, 31 May 2023 21:42:04 +0100 Subject: [PATCH 08/17] Add more to design --- proposals/self-types.md | 70 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/proposals/self-types.md b/proposals/self-types.md index ff5103a5f..4debdc069 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -347,7 +347,7 @@ fun > test(element: Element) { Self-type behaves exactly same as covariant recursive generic type parameter that is bounded with bound. ```kotlin -fun interface Foo> { +interface Foo> { } ``` @@ -355,12 +355,26 @@ fun interface Foo> { Can be rewriten as: ```kotlin +import kotlin.Self + @Self -fun interface Foo { +interface Foo { } ``` +Special type parameter for type `Self` would be in the end of type parameters list. + +```kotlin +import kotlin.Self + +@Self +interface Foo {} + +// This is not real representation inside compiler, but enough to understand for user. +interface Foo> {} +``` + ### `Self` bound `Self` type is bounded to the nearest receiver. @@ -392,3 +406,55 @@ abstract class A { } ``` +### Covariance + +As type `Self` is `out` it can be used only in covariant position. + +```kotlin +import kotlin.Self + +@Self +interface Foo { + fun foo(): Self // compiles + fun bar(s: Self) // compile error +} +``` + +Input generic position: + +```kotlin +import kotlin.Self + +@Self +interface Foo { + fun foo(f: Bar): Self // compiles +} +``` + +Super type argument position: + +```kotlin +import kotlin.Self + +@Self +interface Foo {} + +@Self +interface Bar : Foo {} +``` + +### New instance of class with self-type + +Type argument for `Self` type will be set implicitly, but also can be set explicitly by user. + +```kotlin +import kotlin.Self + +@Self +class Foo { + ... +} + +val fooImplicit = Foo() +val fooExplicit = Foo>() +``` \ No newline at end of file From 05e50d2f1758d152ee4ca6be61ab3f68ece2e5d2 Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Wed, 31 May 2023 21:45:21 +0100 Subject: [PATCH 09/17] Assignment --- proposals/self-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/self-types.md b/proposals/self-types.md index 4debdc069..083ed0478 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -393,7 +393,7 @@ class Foo { With this design decision it is imposible to access `Self` type of `Foo` inside `Bar`. It is considered to be not very popular situation, so user can create their own recursive generic for `Foo` or `Bar`. -### `this` assingment +### `this` assignment Only `this` that refers to function receiver is assignable to the self-type with the correspoding bound. From 2996129111d6f10225100461c1004a5bc3be677f Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Sat, 3 Jun 2023 10:35:10 +0100 Subject: [PATCH 10/17] return generic position --- proposals/self-types.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/proposals/self-types.md b/proposals/self-types.md index 083ed0478..c08c2f439 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -19,7 +19,7 @@ class B : A { } -val x: B = B().foo(); // foo return type is B +val x: B = B().foo() // foo return type is B ``` ## Motivation @@ -431,6 +431,22 @@ interface Foo { } ``` +Return generic position: + +```kotlin +import kotlin.Self + +@Self +interface A { + +} + +@Self +interface B { + fun a(): A +} + +``` Super type argument position: ```kotlin From 923aad5739269dbc6b45541c7b82e6538eb947df Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Sat, 3 Jun 2023 11:06:35 +0100 Subject: [PATCH 11/17] Add specification --- proposals/self-types.md | 52 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/proposals/self-types.md b/proposals/self-types.md index c08c2f439..ee245386e 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -473,4 +473,54 @@ class Foo { val fooImplicit = Foo() val fooExplicit = Foo>() -``` \ No newline at end of file +``` + +## Self-types specification + +We will use `{T}.Self` to describe type `Self` for type `T`. + +```kotlin +@Self +interface A {} +``` + +So type `Self` for interface `A` would be `A.Self`. + +### Safe-values: + +There are two types of values that can be typed as `Self` for type `A`. + +1. `this` +2. `A` constructor call. + + +1. `B <: A => B.Self <: A.Self` to support override for methods with type `Self` in the return position. +2. `B <: A => B.Self <: A` so we can use `this` as the value for type `A`. +3. `Nothing <: A.Self` and `A.Self <: Any`. +4. `B !<: A.Self` if `B` does not fit rules (1) and (3). + +Rule (4) guarantees that only values considered safe may have self-type. + +Common supertype: + +* `CST(B.Self, A) ~ CST(B, A)` + +### Safe-positions + +`Self` type is the same as the covariant recursive generic parameter, but with some additional implicit casts. + +Self-type [positions](https://kotlinlang.org/spec/declarations.html#type-parameter-variance): + +* If `A` is a dispatch receiver then `A.Self` can be used only in covariant positions. +* If `A` is an extension receiver then `A.Self` can be used in all positions. + +Self-type [capturing](https://kotlinlang.org/spec/type-system.html#type-capturing): + +* If `A` is a dispatch receiver then `A.Self` behaves as a covariant type argument: + * For a covariant type parameter `out F` captured type `K <: A.Self`; + * For invariant or contravariant type parameter `K` is ill-formed type. +* If `A` is an extension receiver then `A.Self` behaves as invariant type argument. + +### Safe-calls + +Self-type behaves as the same covariant recursive generic type parameter. So, self-type materializes to the eceirver type. Value will be validated on the declaration-site by safe-values rules. \ No newline at end of file From a072c5248856661982473bd200b5f545f4c496cc Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Sat, 3 Jun 2023 12:37:05 +0100 Subject: [PATCH 12/17] Add initial swift examples --- proposals/self-types.md | 72 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/proposals/self-types.md b/proposals/self-types.md index ee245386e..750324c14 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -523,4 +523,74 @@ Self-type [capturing](https://kotlinlang.org/spec/type-system.html#type-capturin ### Safe-calls -Self-type behaves as the same covariant recursive generic type parameter. So, self-type materializes to the eceirver type. Value will be validated on the declaration-site by safe-values rules. \ No newline at end of file +Self-type behaves as the same covariant recursive generic type parameter. So, self-type materializes to the eceirver type. Value will be validated on the declaration-site by safe-values rules. + + +## Other languages experience + +### Swift + +* [Self type documentation](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/types/#Self-Type) +* [Associated types](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/#Associated-Types) + +Swift protocols are like Rust traits (limited type classes). Protocols can be conformed (implemented) by classes. A class can inherit one another. + +```swift +protocol Protoc { + func className() -> Self +} + +class Superclass : Protoc { + func className() -> Self { return self } +} + +class Subclass: Superclass { } + +let a = Superclass() +print(type(of: a.className())) // Prints "Superclass" + +let b = Subclass() +print(type(of: b.className())) // Prints "Subclass" + +let c: Superclass = Subclass() +print(type(of: c.className())) // Prints "Subclass" + +let d: Protoc = Subclass() +print(type(of: d.className())) // Prints "Subclass" + +``` + +Swift prohibits using a new object where self is expected: + +```swift +final class Foo { + /* + error: cannot convert return expression of type 'Foo' to return type 'Self' + func f() -> Self { return Foo() } + ^~~~~ + as! Self + */ + func f() -> Self { return Foo() } +} +``` + +In non-return positions Self is available only in protocols and class declaration should use itself instead of Self: + +```swift +/* +error: covariant 'Self' or 'Self?' can only appear as the type of a property, subscript or method result; did you mean 'A'? +*/ +class A { + func f(s: Self) -> Self { return s } +} + +protocol P { + func f(s: Self) -> Self +} + +class B: P { + func f(x: B) -> Self { return self } +} + +// todo: more examples +``` \ No newline at end of file From 0daa190d8198a8535616ac816f4fdd024f334e4a Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Sat, 3 Jun 2023 12:40:24 +0100 Subject: [PATCH 13/17] Fix example --- proposals/self-types.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/proposals/self-types.md b/proposals/self-types.md index 750324c14..c409d362e 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -15,11 +15,14 @@ open class A { } } -class B : A { - +class B : A() { + fun b() { + println("Inside B") + } } -val x: B = B().foo() // foo return type is B +val x = B().foo() // foo return type is B +x.b() // stdout: Inside B ``` ## Motivation From e798e2231458c0db4a60085c9356376f120a45dd Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Sun, 4 Jun 2023 13:24:37 +0100 Subject: [PATCH 14/17] Finish swift examples --- proposals/self-types.md | 55 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/proposals/self-types.md b/proposals/self-types.md index c409d362e..a3616e7ad 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -592,8 +592,59 @@ protocol P { } class B: P { - func f(x: B) -> Self { return self } + func f(s: B) -> Self { return self } } -// todo: more examples +protocol ArrayP { + func f() -> Array + func g(arr: Array) -> Array +} + +class D: ArrayP { + // error: covariant 'Self' or 'Self?' can only appear in the top level of method result type + func f() -> Array { return Array() } + + // error: method 'f()' in non-final class 'D' must return 'Self' to conform to protocol 'ArrayP' + func f() -> Array { return Array() } + + // error: method 'g(arr:)' in non-final class 'D' must return 'Self' to conform to protocol 'ArrayP' + func g(arr: Array) -> Array { return Array() } +} + +final class E: ArrayP { + func f() -> Array { return Array() } + func g(arr: Array) -> Array { arr } +} +``` + +### Associated types + +Accociated types can be used to achive similar behavior to self-types, but for one-level hierarchy: + +```swift +protocol Protoc { + associatedtype S + func f() -> S + func g(s: S) + func arr() -> Array +} + +class A: Protoc { + typealias S = A + func f() -> A { return self } + func g(s: A) { s.specific() } + func arr() -> Array { return [self] } + func specific() {} +} + +class B: Protoc { + override func f() -> B { return self } + override func g(x: B) {} + override func arr() -> Array { return [self, self] } +} + +func test(_ a: some S, _ b: some S) { + a.g(s: a.f()) + a.g(s: b.f()) +} ``` \ No newline at end of file From ca60bcab40d3527f3be94d42de0bbeec01cc74c8 Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Sun, 4 Jun 2023 13:36:50 +0100 Subject: [PATCH 15/17] Add Rust and TS examples --- proposals/self-types.md | 67 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/proposals/self-types.md b/proposals/self-types.md index a3616e7ad..902e97398 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -647,4 +647,69 @@ func test(_ a: some S, _ b: some S) { a.g(s: a.f()) a.g(s: b.f()) } -``` \ No newline at end of file +``` + +## TypeScript + +[Documentation](https://www.typescriptlang.org/docs/handbook/2/classes.html#this-types) + +```typescript +class A { + foo(): this { + return this; + } +} + +class B extends A { + bar(): this { + return this; + } +} + +var b: B = new B(); +var x: B = b.foo().bar(); +``` + +Type `this` was added in 1.7 version of TypeScript and some older code became non-compatible. Example: + +```typescript +class Foo { + foo() { + var x = this; + x = new Foo(); + } +} +``` + + +In TypeScript >= 1.7 this code would fail with compilation error: `Type 'C' is not assignable to type 'this'. 'C' is assignable to the constraint of type 'this', but 'this' could be instantiated with a different subtype of constraint 'C'`. + +This is the problem that we shouldn't have in Kotlin and that is why we use annotation `@Self` in this proposal. + +## Rust + +[Documentation](https://doc.rust-lang.org/reference/paths.html#self-1) + +Obviously, `Rust` do not have inheritance and self-types implemented via macrosses, but example may be useful to understand how self-types may be used `Rust` engineer. + +```rust +trait T { + type Item; + fn new() -> Self; + fn f(&self) -> Self::Item; +} +``` + +Type `Self` in `Rust` also allows user to have a reference on any type alias within scope of receiver. + +Type `Self` is reserved keyword, so some constructions give compilation error: + +```rust +trait T { + type Self; +} +``` + +`expected identifier, found keyword `Self` expected identifier, found keyword`. + +Type `Self` is also covariant. From 7c439cf871d37a7dff274ecd9f7c7e96928198f9 Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Sun, 4 Jun 2023 13:41:50 +0100 Subject: [PATCH 16/17] Add Manifold --- proposals/self-types.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/proposals/self-types.md b/proposals/self-types.md index 902e97398..d3f68570b 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -713,3 +713,32 @@ trait T { `expected identifier, found keyword `Self` expected identifier, found keyword`. Type `Self` is also covariant. + +## Manifold + +There is `Java` library that supports `Self` types. + +[Documentation](https://github.com/manifold-systems/manifold/tree/3f4134bcc74ba38bef35a490a03884b5540e6488/manifold-deps-parent/manifold-ext#the-self-type-with-self) + +> The Self type provides a way to statically express the "type of this" and is most useful in situations where a method return type or parameter type in a base type reflects a subtype. + +```java +public class Node { + private List children; + + public List<@Self Node> getChildren() { + return children; + } + + public void addChild(@Self Node child) { + checkAssignable(this, child); + children.add(child); + } +} + +public class MyNode extends Node { + ... +} +``` + +In `Manifold` annotation `@Self` can be used for type arguments and functions. \ No newline at end of file From 8a9c4631aef95b10a958ab2c70ba44b8a1e1d094 Mon Sep 17 00:00:00 2001 From: Maxim Grankin Date: Sun, 4 Jun 2023 13:42:57 +0100 Subject: [PATCH 17/17] Add Python --- proposals/self-types.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/proposals/self-types.md b/proposals/self-types.md index d3f68570b..5118e4d5f 100644 --- a/proposals/self-types.md +++ b/proposals/self-types.md @@ -741,4 +741,9 @@ public class MyNode extends Node { } ``` -In `Manifold` annotation `@Self` can be used for type arguments and functions. \ No newline at end of file +In `Manifold` annotation `@Self` can be used for type arguments and functions. + +## Python +[Documentation](https://peps.python.org/pep-0673/) + +`Python` also provides type `Self`, but we will not cover it due to weak `Python` type system. \ No newline at end of file