You’re probably right, it is more about extending rather than modifying.
How about some sugar (borrowing from Swift) like this?
extend class String : IMonoid {
fun zero() = ""
fun append(other: String) = this + other
}
You’re probably right, it is more about extending rather than modifying.
How about some sugar (borrowing from Swift) like this?
extend class String : IMonoid {
fun zero() = ""
fun append(other: String) = this + other
}
If you go this way, then you will have to repeat the entire definition of IMonoid
for each class you want to add to this type class.
I think IMonoid
should be defined once and then its name can be reused whenever a new class wants to be part of that family.
I’m not married to my suggestion of using override
for this, it was just the first idea I had with the constraint of not adding a new keyword, I’m sure we can come up with other syntaxes.
I saw the extend class as just some added sugar to remove the need for repeating the Type.function() syntax, rather than explicitly implementing the interface. I envisioned it being the equivalent to your initial example.
In the past, JetBrains has been strongly against the idea of implicit conversions (which this essentially amounts to - importing the extension would implicitly extend the type of the object).
Ages ago a conversion operator was proposed allowing types to be converted. I kind of liked the idea, but think its been dropped along the way. See: https://youtrack.jetbrains.com/issue/KT-1752#comment=27-318887
I guess your proposal avoids the “boxing” overhead that conversions would introduce however.
the “Nice” programming language had an Idea for how to handle this and still work with the Java Type system, Generics.
http://nice.sourceforge.net/manual.html#abstractInterfaces
Abstract Interface are special Interfaces which can only be used as to close a generic argument (like < A : AbstractInterface > ) , you cannot have handle to the type, except in a generic context, it appears to comes down to a compiler trick, like a macro, maybe similar Kotlin’s reified generics, or Go’s interfaces
Nice! (pun semi-intended)
Seems quite similar to what I proposed here: Kotlin and the Expression Problem
But you’re going to have to do that anyway, even with your original suggestion, correct? Or do you mean classes that have a subset of the interface already implemented?
Right, not sure what I was thinking when I wrote that…
Hello,
Here is another (draft) approach to typeclass inspired by scala implicit resolution (but limited to typeclasses use case)
`
//Typeclass interface definition
interface Monoid<T> {
fun zero(): T
fun add(a1: T, a2: T): T
}
//String instance
val stringMonoid = object : Monoid<String> {
override fun zero() = ""
override fun add(a1: String, a2: String) = a1 + a2
}
//Int instance
val intMonoid = object : Monoid<Int> {
override fun zero() = 0
override fun add(a1: Int, a2: Int) = a1 + a2
}
//A generic function working on all type T, where it exists a Monoid<T>
//Such instance is callable in the function body as M
fun <T with Monoid as M> multiply(a: T, n: Int) {
var t: T = M.zero()
for (i in 1 to n) {
M.add(a)
}
return t
}
fun main(args: Array<String>) {
println(multiply("hello", 5)) // should print "hellohellohellohellohello"
println(multiply(4, 5)) // should print 20
println(multiply(45.3, 5)) // should not compile because no Monoid<Double> is defined
}
`
I think the important part of such suggestions is the resolution of the implicit, which is the Monoid
interface in this case. What are your thoughts on that?
intMonoid1
and intMonoid2
?anyMonoid
and a stringMonoid
?To me such resolution of implicit classes should in fact be very very explicit. No compiler burden, but simple straightforward rules that are easy to understand and (relatively) easy to implement.
There are also some other important questions, questions like:
What may be a solution would be to allow the following to work (and be statically resolved)
inline fun <reified T> add(v1:T, v2:T) = when (T) {
Int -> v1 + v2
BigDecimal -> v1.add(v2)
}
This code should not require an else clause, but a compile error instead. The non-applicable cases should also be elided/omitted from the compilation result as they are statically unreachable. Usage of T.class
with the same eliding would be ok as well.
I’d like to throw my $0.02 in also - personally I love the idea of “Extension Types” as well. I do a lot of work with JavaMail, which pre-dates the AutoCloseable/try-with-resources functionality. Even though some of the classes in there have explicit close
methods, I still have to add a finally block to manually close them. It’d be awesome to just do something like (using @CodeAnxiety’s sample syntax):
extend class Store : AutoCloseable {
fun close() {
(this as Store).close() // well, some kind of way to refer to the base method instead of trying to recurse.
}
}
createEmailStore().use {
// blah blah blah
}
On one hand, I can see this being consider an implicit conversion - it basically is. However, I don’t think it’s terribly different from extension methods. Those are kinda-sorta implicit conversions - you’re converting from a type that doesn’t have the functionality you want into one that does. These would still be a decorator for the original object, just one that implements a specified interface instead of one that just adds a couple of methods.
The only possible gotcha I can see off hand would be if the extended class already possesses (some) methods with the same signature; how would those be resolved? I imagine whatever resolution method is in place for extension methods would work as well, but maybe not.
One other possibility I remember from my C# days was that you had the ability to explicitly define an implementation for an interface in the case of naming conflicts - you could have AutoCloseable.close()
and FrontDoor.close()
. Depending on the context the object was used in, it would call which ever one was appropriate; if the object was referenced as a FrontDoor
instance, it would call the FrontDoor.close()
implementation. If you omitted the type qualifier and only had a single implementation, it would just call that regardless of the context.
I really like @cedricbeust 's proposal (apart from the syntax). It would give us simple type classes for almost no additional complexity or performance cost. In Kotlin they could appear as regular interfaces. The only problem would be that you couldn’t use those interfaces in Java. But at least you don’t have to learn a new concept on the Java side to interact with those methods. I think this is a fair trade off.
What does the Kotlin team think of it? I couldn’t find any responses in this thread.
The idea is indeed very similar to implicit conversions, for which we’ve made an explicit decision not to include them in the language.
Could you clarify, what does it mean “now” and “from this point on” in terms of compiler scopes? I.e. if I declare such an extension for String
, where would I be able to use String
as a Monoid
?
Can I use List<String>
where List<Monoid>
is expected? If yes, how the conversion is performed?
I haven’t really thought about this in details but my first idea would be to use a similar mechanism as extension functions: you would need to import the Monoid
type, obviously, and also the compilation unit that defines the override
. In effect, the compiler could either emit a function that performs the conversion (and I understand this is something goes against Kotlin’s focus on avoiding implicit conversions) or it could emit a new synthetic class with the required attributes. The difficulty for the compiler is to remember that a String
is also a Monoid
wherever these conditions are met.
To mitigate the implicit problem:
I’d still be useful and would place the system well into the “not surprising” territory.
I guess all this stuff really belong in a KEEP, no?
(*) Actually a much better solution would be to make the import system smart, and some syntax to toggle wildcard import of extension types on and off (off by default). It’s a bit unorthodox, but a good compromise (also see this).
I know
Something I don’t like about your proposal though: that the extension functions implementing the extension type are not encapsulated somehow.
If you already have an extension function with the same name as a method in your interface (say zero
for Monoid
), things get awkward and you have to disambiguate in some fashion.
I would like it to be like this:
override class String: Monoid<String> {
fun zero() : T { ... }
fun append(a1: T, a2: T) : T { ... }
}
val x = "cthulhu".zero() // illegal
val y: Monoid<String> = "cthulhu"
val z = y.zero() // okay
I believe the request to adapt something to something else is quite common, and that Kotlin could support it, but with an explicit conversion rather than implicit.
To make those explicit adapters less wordy, we could first implement KT-505 (delegating by signature), and second — provide some syntax to be able to write Monoid(stringValue)
instead of object : Monoid by stringValue {}