kmath/docs/contexts.md

74 lines
3.5 KiB
Markdown
Raw Normal View History

# Context-oriented mathematics
2018-12-17 12:32:47 +03:00
## The problem
2018-12-28 08:01:57 +03:00
A known problem for implementing mathematics in statically-typed languages (but not only in them) is that different sets
of mathematical operators can be defined on the same mathematical objects. Sometimes there is no single way to treat
some operations, including basic arithmetic operations, on a Java/Kotlin `Number`. Sometimes there are different ways to
define the same structure, such as Euclidean and elliptic geometry vector spaces over real vectors. Another problem
arises when one wants to add some kind of behavior to an existing entity. In dynamic languages those problems are
usually solved by adding dynamic context-specific behaviors at runtime, but this solution has a lot of drawbacks.
## Context-oriented approach
2018-12-28 07:26:52 +03:00
One possible solution to these problems is to divorce numerical representations from behaviors. For example in Kotlin
one can define a separate class representing some entity without any operations, ex. a complex number:
```kotlin
data class Complex(val re: Double, val im: Double)
```
2018-12-28 07:26:52 +03:00
2018-12-28 08:01:57 +03:00
And then to define a separate class or singleton, representing an operation on those complex numbers:
2018-12-28 07:26:52 +03:00
```kotlin
2018-12-28 07:26:52 +03:00
object ComplexOperations {
operator fun Complex.plus(other: Complex) = Complex(re + other.re, im + other.im)
operator fun Complex.minus(other: Complex) = Complex(re - other.re, im - other.im)
}
```
In Java, applying such external operations could be cumbersome, but Kotlin has a unique feature that allows us
implement this
naturally: [extensions with receivers](https://kotlinlang.org/docs/reference/extensions.html#extension-functions). In
Kotlin, an operation on complex number could be implemented as:
2018-12-28 07:26:52 +03:00
```kotlin
2018-12-28 07:26:52 +03:00
with(ComplexOperations) { c1 + c2 - c3 }
```
2018-12-28 07:26:52 +03:00
2018-12-28 08:01:57 +03:00
Kotlin also allows the creation of functions with receivers:
2018-12-28 07:26:52 +03:00
```kotlin
fun ComplexOperations.doSomethingWithComplex(c1: Complex, c2: Complex, c3: Complex) = c1 + c2 - c3
2018-12-28 08:01:57 +03:00
ComplexOperations.doComethingWithComplex(c1, c2, c3)
```
2018-12-28 07:26:52 +03:00
In fact, whole parts of a program may be run within a mathematical context or even multiple nested contexts.
2018-12-28 08:01:57 +03:00
In KMath, contexts are not only responsible for operations, but also for raw object creation and advanced features.
## Other possibilities
### Type classes
An obvious candidate to get more or less the same functionality is the type class, which allows one to bind a behavior
to a specific type without modifying the type itself. On the plus side, type classes do not require explicit context
declaration, so the code looks cleaner. On the minus side, if there are different sets of behaviors for the same types,
it is impossible to combine them into one module. Also, unlike type classes, context can have parameters or even state.
For example in KMath, sizes and strides for `NDElement` or `Matrix` could be moved to context to optimize performance in
case of a large amount of structures.
### Wildcard imports and importing-on-demand
Sometimes, one may wish to use a single context throughout a file. In this case, is possible to import all members from
a package or file, via `import context.complex.*`. Effectively, this is the same as enclosing an entire file with a
single context. However, when using multiple contexts, this technique can introduce operator ambiguity, due to namespace
pollution. If there are multiple scoped contexts that define the same operation, it is still possible to import
specific operations as needed, without using an explicit context with extension functions, for example:
```
import context.complex.op1
import context.quaternion.op2
```