Happy to see experimental support for sealed inter...
# mathematics
b
Happy to see experimental support for sealed interfaces arriving in 1.5. Together with multiple inheritance and default methods, this opens up a path to implementing algebraic data types: https://blog.jetbrains.com/kotlin/2021/02/new-language-features-preview-in-kotlin-1-4-30/#sealed-interfaces AFAICT, this feature offers the same benefits of multiple receivers without needing to introduce a new syntax. Or is there some feature that KEEP-176 proposes which could not be implemented using the type class pattern on a sealed interface?
i
@breandan I don't get how to express something like this with sealed interfaces:
Copy code
object A { fun add(d1: Double, d2: Double) }
operator fun (A, Double).plus(o: Double) = this@A.add(this@Double, o)
b
I don’t think it is possible to overload/shadow operators on primitive types, but maybe I don’t understand what you’re trying to do. Can you give an expected usage from the call site, i.e. what syntax are you trying to achieve and what is the expected behavior of this operator?
i
Of course, I'm about KMath:
Copy code
RealField { 0f + 0.1 } // Double(0.1)
a
As Iaroslav mentioned, we still can't create extensions for operators an infix functions. And I am not sure how closed hierarchies would help us.
b
Hmm, I'm still not sure what you're trying to do. But assuming you could overload operators on primitive types, would something like this work?
Copy code
fun main() {
  object: RealField, OtherAlgebra{}.run {
    println(1.d + 1.0)
    someOp(1.d, 1.0)
  }
}

val Number.d: KDbl
  get() = KDbl(toDouble())

interface RealField {
  fun add(d1: KDbl, d2: KDbl) = KDbl(d1.v + d2.v)
  //...
}

interface OtherAlgebra {
  fun someOp(d1: KDbl, d: Double) = KDbl(d1.v / d)
}

class KDbl(val v: Double) {
  operator fun plus(d: Double) = KDbl(v + d)
}
Kdbl
doesn't need to be an interface, but the algebras do in order to use multiple inheritance
The default methods could be overloaded at the use site, or not. I don't see what benefit this
(A, Double).
syntax provides that isn't already possible with interfaces (but it could be I'm missing something obvious)
a
There are not problems with inheritance. I think the main confusion comes from the fact that we do not try to emulate type-classes. Type-classes are indeed similar to closed hierarchies. In KMath we use an open-world context to dispatch operations. So we can define operations in algebras and then use algebra scopes to dispatch them like
operator fun [SomethingField, FieldElement].plus(other: FieldElement): FieldElement = add(this@FieldElement, other)
I am using old Keep-176 notation instead of the new
with
sinch I am not sure it will work properly. We do not use new-types wrappings here and instead move the dispatch from types to scopes. As a bonus we can operate on initial types without wrapping them. In this example the benefit is purely cosmetice because operators require receiver and we can't define them without either bringin operator fun inside the algebra scope or multi-receivers. But there are other applications as well.
Let me underline again. Kmath uses open-world model for and a dispatch based on scope, not type. Whereas Haskel type-classes are based on a closed-world model and require type-wrapping to work. Additional syntax volume is similar, yet I see a number of benifits in our model: 1. we define scope only once, we do not need to manually wrap all elements. 2. We can several scopes operating on the same type (we use it a lot when switching between matrix operation engines. 3. Open world allows to brin new modules effortlessly. 4. We can store some intermedieate information in the scope, which provides additional safety and allows some performance optimizations.
b
I think I see where you're coming from, but it would helpful to have a concrete example of a use case that scope-based dispatch supports that is difficult to implement using multiple dispatch. As is, it's already tricky to reason about dispatch in nested contexts, KEEP-176 seems to introduce some additional indirection and it would be helpful to understand the pros and cons a little more clearly. Not only does it require a new syntax, but from what I understand you're also proposing some changes to overloading, where primitive ops can be overloaded based on scope, is that correct?
a
See example here:https://github.com/mipt-npm/kmath/blob/dev/examples/src/main/kotlin/kscience/kmath/structures/NDField.kt We have the same operation defined on the same NDStructure, but in each context, it works differently (uses different underlying library) and actually produces different type (library-specfifc type. In this way, we can easily compbine different libraries by just moving the result from one scope to another or even open an internal scope (though it could turn nasty). In haskel you would have to rewrap the type each time you are doing this and track the individual types of objects. Here, the scope guarantees that all operations in it use the same logic and the same engine. For example I use NDscopes to ensure that all structures are of the same size. You do not need to include information about size in type, it is already in the scope.
👀 2