Would it be feasible to be able to implement entir...
# language-proposals
n
Would it be feasible to be able to implement entire types via extensions? For example:
Copy code
interface Flippable<T> {
	fun flip(): T
}

extend String as Flippable<String> {
	// Implements all of the Flippable properties/methods missing from the base class
	fun flip(): String {
		...
	}
}
Then you could treat a
String
as an instance of
Flippable
1
w
Why don’t you use
fun String.asFlippable(): Flippable<String>
?
b
@wcaokaze because this way is a much better Dev Ex. Later on when you take the instance out of whatever requires a
Flippable
, with the proposed way you continue using the string. With your way you're stuck with an opaque type
👍 2
w
true
also comment from Kotlin Team member https://stackoverflow.com/a/39252347/420412
t
That's a type class and afaik there's already a proposal for these (keep 87 iirc)
b
@gildor that's similar, but it's also ducktyping. It's a Hallmark of JavaScript that I'd like to keep out of Kotlin
This proposal is similar to the way Swift lets you declare
protocol
(their word for
interface
) conformance after a type has been declared. It's a very underrated and powerful language feature that basically changed the way I think about programming in general. This lets the compiler more easily do very complex reasoning on types. For instance I've been wanting to create an interface of my own that I can later say all the number types (
Int
,
Number
,
BigInteger
, etc) conform to so I can strongly differentiate between integer types and real number types without typechecking against all known classes (both tedious and fragile), which would let me do things that are absolutely impossible right now (like promise integer output given integer input without restricting myself to the caller's chosen bitwidth) because there's no way to express to the compiler that what I'm doing is guaranteed to succeed, even if it is. I would love to see this added, and would love to help work on it too. I just can't promise time because I've been so busy with moving apartments that I can't even give my collection literals proposal enough time :(
👍 1
d
I would also like to see this. It solves some of the issues that arise from final classes, and it useful for gluing libraries together. However I suspect there are a lot of complications and edge cases with this feature on the JVM with Java - Kotlin interaction.
1
👍🏼 2
c
t
k
In your example case, how do you foresee this working?
Copy code
val str = "foo"
if (str is Flippable<*>) { ... }
Is that condition true or false?
t
in case of type classes, that check doesn't really make sense.
str
isn't a
Flippable<*>
, it has a
Flippable<String>
(implementation). An important distinction, since there may be different sensible implementations depending on the type class and you avoid the issue of redeclaration.
2
b
is
should be changed to detect any implemented conformance, whether at the original declaration or via an extension later on. The exact location of the implementation should not matter to the API user. I wouldn't have to say
if (str is Flippable<*> || str has Flippable<*>)
everywhere I check for interface conformance. That's just dumb
👍 1
👎 2
g
It is not how type classes work, you cannot implicitly extend class with another class. And you don’t need this, because your function require type class on invocation
k
I'm very much against modifying the behavior of
is
to also take magical implicit implementations into account, personally
n
I think
is
would have to return true there, it's the only logical behavior
If the object can be treated as the type, it should be true
👍🏼 1
b
@gildor I agree. This should be strictly for late implementation of interfaces. @kevinmost the current behavior should say exactly as it is today. However, if this feature of late implementation were added to the language,
is
should become smart enough to also check for that. No backwards breaking, no forwards breaking.
t
If you are checking for type with
is
I presume you would also cast to that type. How is the compiler supposed to decide which methods to call, since there may be multiple declarations of extension methods that implement the interface. It has to be decided at compile time what methods to call
n
Doesn't the compiler already make those decisions with extension methods?
b
@tschuchort just like everywhere else in Kotlin, if you try to define/implement/extend the same thing twice, that's a compiler error. As @nwh said, this wouldn't magically add some ambiguity that extension methods avoid
☝🏻 1
g
You can extend class with extension function any amount of times, no problem there, user always explicitly import required extension. But proposal to actually implicitly extend some class
@nwh yes, and this decision: always call class members instead of extensions in case of name conflict, you cannot overrides one method from class using extension function
b
@gildor yeah, that’s what I meant. If they’re in the same import-space they can’t be the same. That is to say, the same file or the same package.
g
@benleggiero I don’t understand than how this suppose to work, when you extend one class with another as with extension
b
I leverage this feature of Swift at work, by the way, in unit tests. I made all primitive numbers, and related math structures like rectangles and points, conform to “ApproximatelyEquatable”, so that I can assert that two things are equal within a given tolerance, without caring what type they are and letting me only write one
assertApproximatelyEqual
function. Bunches less boilerplate.
O(1)
instead of
O(n)
g
I just think that everyone talks about different feature, somehow related (some sort of ad-hoc polymorphism), but with own usage syntax and restrictions
b
@gildor Imagine this:
Copy code
// In library A:
class Foo {
    fun clone(): Foo { ... } // very okay
}

extension Foo: Comparable<Foo> { // very okay
    override fun compareTo(other: Foo): Int { ... }
}


// In library B:
extension Foo: Comparable<Foo> // error: Foo already conforms to Comparable

extension Foo: Cloneable // okay; all requirements already met

extension Foo: CharSequence { // okay
    override val length: Int { ... }
    override fun get(index: Int): Char { ... }
    override fun subSequence(startIndex: Int, endIndex: Int): Foo { ... }
}
@gildor Isn’t that what this channel is for? Getting everyone’s varying ideas and opinions before making a formal proposal? Also, I don’t think people want this to make multiple-inheritance a thing, just spread out the already-okay multiple-interface-conformance and allow conformance to be done by the API user as well, instead of only by the API writer
g
Hm, I don’t think that this is okay, library don’t know about each other, so you can easily broke build with introduction of one more extension, you don’t know about those extensions and libraries don’t know about each other
I think everyone talks about different problem and use cases. Let’s discuss some particular one
For example original message mentioned Flippable interface
this is how you can achieve it in Kotlin with something like type class (without direct support from language, you have to specify instance explicitly) https://try.kotlinlang.org/#/UserProjects/2m97mgjb1tk1odam1c4n5nsen6/816b6g8mtrb51hnukogijj2ndc
b
@gildor I should have said that Library A doesn’t know about Library B, but Library B includes Library A as a dependency.
g
what if you have 2 libraries included to your build
b
@gildor that link just shows me an empty main method and nothing else
g
I think such implicit strategy is against Kotlin philosophy. And in general introduce a lot of problems even without compatibility with Java and JVM
@benleggiero Sorry, fixed the link
b
@gildor so like this?
Copy code
// In library C, which includes A as a dependency but does not know about B:
extension Foo: CharSequence { // okay
    override val length: Int { ... }
    override fun get(index: Int): Char { ... }
    override fun subSequence(startIndex: Int, endIndex: Int): Foo { ... }
}


// In App D, which includes both A, B, and C:
import library.a.Foo // okay: basic functionality
import library.b.Foo // okay: extended functionality by B
import library.c.Foo // error: both B and C declare Foo's implementation of CharSequence
@gildor that link is… close, but it shows that unnecessary boilerplate is needed to achieve something that could be simplified.
How is this against the Kotlin philosophy?
g
no, explicit import IMO is completely fine
Yes, I completely agree this is boilerplate, and Type Classes proposal is exactly about it, use similar approach with some syntactic sugar. But many parts of proposal is not ready, how to pass instances, how lookup should work etc
b
perhaps not ready for a KEEP, but I think it’s a great idea worthy of being in the language
g
I’m talking that Type Classes proposal Keep is not ready
I would happy to have support of some sort of ad-hoc polymorphism in Kotlin, I just worry about implicit import and lookup, we have example of Scala where it’s one of big pain points
b
I’m not a fan of KEEP #87's idea of how to do ad-hoc polymorphism either. I think there’s a much more elegant syntax for what it’s trying to achieve. And I also think it’s not that hard to think of better syntax and resolutions than Scala 😛
g
What kind syntax? Are you talking about extensions-like syntax? I think it’s not a big problem, much bigger how to find instance of particular type class (lookup) and how to pass it to a function
In case of scala is not about syntax, it’s about general idea of implicit params
it’s trade-off between “less code” and “be more explicit”
I’m actually really worry about this proposal, looks like it goes too deep and discussion about higher kinder types and other not related stuff just distracted from the original goal: type classes
r
The syntax in the original comment would satisfy coherent type classes and it’s defined as a type class. That is the instance that brings implicit syntax to all
String
with the contract of being contrained by
Flippable
. What is missing for type classes is to define how one can depend in such syntax since String is not a subtype of Flippable and
String: Flippable
may not be a good bounds expression. How does one write a function or class that depends on there being a
Flippable<T>
in the scope such as that the syntax is activated? Is it via import? or are they coherent in the sense that the compiler can guarantee a single instance? Are there any override capabilities for users to modify the behavior libraries provide as instances?
@gildor Also KEEP-87 won’t introduce higher kinds, we are just going to get syntax activation and will send a different one for HKs down the road.
g
@raulraja Yes, sure, I understand this, I actually find your proposals practical and not overcomplicated, I'm talking in general about discussion. Or maye I just stupid and it looks too complicated for me 🙈 My main concern is implicits lookup, but I should think about implementation that you proposed and want to implement. Do you plan to update proposal with some main points of discussion and your plans about prototype?
r
I was hoping @claire would update it since it was her original idea and once she does there would be no implicit resolution in terms of imports since they can be looked up only in 3 places: - Type class definition package or companion - Instance definition package or companion - Look for
internal
override in call site definition package or companion
With those rules there is no need to import implicit instances and we avoid the confusion that Scala has in terms of where implicit values and instances may come from
Also it’s originally restricted to type classes with one type argument so you can’t place implicit values that are not type class instances in the scope.
For those of you interested the proposal has been updated to support coherent type classes https://github.com/47deg/KEEP/pull/6
👍 3