Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile-time Extension Interfaces #87

Open
wants to merge 85 commits into
base: master
from
Open
Changes from 2 commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
13df9dd
Type Classes via natural extensions in Kotlin
raulraja Oct 2, 2017
7494c84
Adapted code examples to new proposed syntax https://github.com/Kotli…
raulraja Oct 2, 2017
a5f9659
Fixed typo
raulraja Oct 3, 2017
de8032b
Included section on overcoming `inline` `reified` limitations as show…
raulraja Oct 4, 2017
47e1348
Adapt proposal examples to new style using `given`
raulraja Oct 9, 2017
eb4f687
replace `extension class` for `extension object` where possible addre…
raulraja Oct 10, 2017
3777fa6
added imports to code examples
raulraja Oct 10, 2017
374e848
Add language changes and instance resolution rules order
raulraja Oct 12, 2017
2f2f27e
fixed wrong type param reference
raulraja Oct 24, 2017
3b568bf
code review comments on `most` vs `immediately`
raulraja Nov 7, 2017
7d10901
Reverted to use `typeclass` and `instance`
raulraja Nov 7, 2017
f8dce43
Added sentence clarifying the Reified example.
raulraja Nov 12, 2017
323a550
`Monoid.empty` is a value
fvasco Feb 19, 2018
a105496
Use IntMonoind in the example
fvasco Feb 27, 2018
505619c
Specify package in 'with'
fvasco Feb 27, 2018
115a483
Remove extra import in example 4
fvasco Feb 27, 2018
8a7dd15
Merge pull request #4 from fvasco/import4
raulraja Apr 9, 2018
8a74e37
Merge pull request #1 from fvasco/empty-value
raulraja Apr 9, 2018
20976f6
Merge branch 'master' into IntMonoind_empty
raulraja Apr 9, 2018
73b5d33
Merge pull request #2 from fvasco/IntMonoind_empty
raulraja Apr 9, 2018
7027bde
Merge pull request #3 from fvasco/intext_package
raulraja Apr 9, 2018
4a98c86
Using extension keyword
fvasco Apr 10, 2018
0496c73
Fix lambda declaration
fvasco Apr 11, 2018
da913fe
Compile resolution rules #4 clarification
fvasco Apr 11, 2018
6a10e93
Merge pull request #5 from fvasco/extension-interface
raulraja Apr 11, 2018
00d6aca
Update type class KEEP
Jun 25, 2018
d47eeef
Merge pull request #6 from ClaireNeveu/master
raulraja Jun 28, 2018
f495563
Update type-classes.md
raulraja Jun 28, 2018
d70f40c
Remove out-of-date section.
Jun 28, 2018
23b5276
Merge pull request #7 from ClaireNeveu/master
raulraja Jun 28, 2018
4671389
anonymous parameter clarification (#8)
fvasco Jun 29, 2018
84d16ce
Update proposal based on the initial implementation (#10)
truizlop Nov 14, 2018
b632229
Fix misuse of encoding (#9)
pakoito Nov 14, 2018
2a8b95a
Linguistic improvements within type-classes.md (#11)
TAGC Nov 15, 2018
3b74240
Fix typo: The intro… allow -> The intro… allows (#12)
LouisCAD Apr 10, 2019
5f7b61c
Updates contributors list.
JorgeCastilloPrz Apr 11, 2019
ce47c2e
Renames keep file.
JorgeCastilloPrz Apr 11, 2019
2417af2
First pass on Summary and Motivation.
JorgeCastilloPrz Apr 11, 2019
34d89c8
Description first pass.
JorgeCastilloPrz Apr 11, 2019
97f772f
Pass over inline reified section.
JorgeCastilloPrz Apr 11, 2019
c6835ea
Pass over composition and chain evidences section.
JorgeCastilloPrz Apr 11, 2019
9b6c6fd
Pass over Language changes section.
JorgeCastilloPrz Apr 11, 2019
f4ba65a
Polish Language changes a bit.
JorgeCastilloPrz Apr 11, 2019
ae5c0a7
Review resolution order.
JorgeCastilloPrz Apr 11, 2019
8c0737a
Remove type constructors section.
JorgeCastilloPrz Apr 11, 2019
eff56ad
Remove reified and inline generics section.
JorgeCastilloPrz Apr 11, 2019
33f55fc
Improve resolution order and add error reporting.
JorgeCastilloPrz Apr 11, 2019
988c8a9
Adds how to try sections.
JorgeCastilloPrz Apr 11, 2019
94a1a99
Polish how to test sections.
JorgeCastilloPrz Apr 11, 2019
bcbd746
Ploishes Orphan instances description a bit.
JorgeCastilloPrz Apr 11, 2019
dfc0b64
Completes first pass.
JorgeCastilloPrz Apr 11, 2019
a4fd7f4
Adds more details to some sections.
JorgeCastilloPrz Apr 11, 2019
a32eaa5
Adds some details to description.
JorgeCastilloPrz Apr 11, 2019
c811a73
Moar polishments.
JorgeCastilloPrz Apr 11, 2019
e6ba5e6
Fixes a typo.
JorgeCastilloPrz Apr 11, 2019
5485db1
Fixes a typo.
JorgeCastilloPrz Apr 11, 2019
ed8561e
Fixes a typo on a snippet.
JorgeCastilloPrz Apr 11, 2019
535c453
Includes horizontal composition in Summary.
JorgeCastilloPrz Apr 12, 2019
fba1db3
Updates repo to use Map property and adds A.save() extension.
JorgeCastilloPrz Apr 12, 2019
c563312
Update proposals/compile-time-dependency-resolution.md
raulraja Apr 12, 2019
21aeaed
swap How to try approaches.
JorgeCastilloPrz Apr 12, 2019
3dd43f5
Merge branch 'jc-keep-rewording' of github.com:47deg/KEEP into jc-kee…
JorgeCastilloPrz Apr 12, 2019
c8bf869
Fixes a typo in one of the snippets where a monoid was wrongly mentio…
JorgeCastilloPrz Apr 12, 2019
7792dd7
Adds proper package to User definition snippet.
JorgeCastilloPrz Apr 12, 2019
67c8f3e
Polishes wording over resolution order.
JorgeCastilloPrz Apr 12, 2019
6ba2577
Drops implicits from comparison.
JorgeCastilloPrz Apr 12, 2019
87d1c43
Fix wrong typing in loadById call sites.
JorgeCastilloPrz Apr 12, 2019
531711d
Rethink wording about named extensions.
JorgeCastilloPrz Apr 12, 2019
bd1416d
Rethink wording about named extensions.
JorgeCastilloPrz Apr 12, 2019
7bed642
fix conflicts.
JorgeCastilloPrz Apr 12, 2019
87eafe6
Remove FP constructs mentions from Error reporting screenshots.
JorgeCastilloPrz Apr 12, 2019
3753301
Refactor keep to reflect proper wording on first resolution scope.
JorgeCastilloPrz Apr 12, 2019
951ea3d
Reflect how syntax becomes available in methods and functions bodies.
JorgeCastilloPrz Apr 12, 2019
297e65f
Adds fun and val extension providers to the TODO list.
JorgeCastilloPrz Apr 15, 2019
071d6b7
Adds both big still open issues to the KEEP.
JorgeCastilloPrz Apr 15, 2019
6ac0d48
Uploads and links fixed Keep87Sample zip project using proper interna…
JorgeCastilloPrz Apr 15, 2019
e564b61
Adds clarification for internal modifier requirement in some scopes.
JorgeCastilloPrz Apr 15, 2019
21003c6
Switches chained to nested wording.
JorgeCastilloPrz Apr 15, 2019
83a1a94
Adds internal flag to required snippet extensions.
JorgeCastilloPrz Apr 15, 2019
abcd547
small lang changes
MaureenElsberry Apr 16, 2019
46e6a34
Merge pull request #14 from 47deg/jc-keep-rewording
JorgeCastilloPrz Apr 17, 2019
e863b25
Move Group data class to GroupRepository (#15)
bloderxd Apr 18, 2019
d488d79
Update compile-time-dependency-resolution.md
bassjacob May 5, 2019
78f5459
Merge pull request #16 from bassjacob/patch-1
JorgeCastilloPrz May 5, 2019
da6a274
Update proposals/compile-time-dependency-resolution.md
raulraja May 31, 2019
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -0,0 +1,182 @@
# Type classes

* **Type**: Design proposal
* **Author**: Raul Raja
* **Status**: New
* **Prototype**: -

## Summary

The goal of this proposal is to enable `typeclasses` and lightweight `Higher Kinded Types` in Kotlin to enable ad-hoc polymorphism and better extension syntax.
Type classes is the most important feature that Kotlin lacks in order to support a broader range of FP idioms.
Kotlin already has an excellent extension mechanism where this proposal fits nicely. As a side effect `Type classes as extensions` also allows for compile time
dependency injection which will improve the current landscape where trivial applications rely on heavy frameworks based on runtime Dependency Injection.

## Motivation

* Support Typeclass evidence compile time verification
* Support a broader range of Typed FP patterns
* Enable multiple extension functions groups for type declarations
* Enable compile time DI through the use of the Typeclass pattern

## Description

We propose to introduce a new top level declaration `typeclass` that allows for generic definition of typeclasses and their instances with the same style as extension functions are defined

```kotlin
typeclass Monoid {
fun Self.combine(b: Self): Self
fun empty(): Self
}
```

The above declaration can serve as target for implementations for any arbitrary datatype.
In the implementation below we provide evidence that there is an `Int: Monoid` instance that enables `combine` and `empty` on `Int`

```kotlin
extension Int : Monoid {
fun Int.combine(b: Int): Int = this + b
fun empty(): Int = 0

This comment has been minimized.

Copy link
@fvasco

fvasco Feb 25, 2018

empty should be a value not a function.

}
1.combine(2) // 3
Int.empty() // 0

This comment has been minimized.

Copy link
@fvasco

fvasco Feb 25, 2018

empty() isn't an extension function of Int.Companion, this line should be replaced by IntMonoind.empty().

```

Because of this constrain where we are stating that there is a `Monoid` constrain for a given type `A` we can also encode polymorphic definitions based on those constrains:

```kotlin
fun <A : Monoid> add(a: A, b: A): A = a.combine(b)
add(1, 1) // compiles
add("a", "b") // does not compile: No `String: Monoid` instance defined in scope
```

On top of the value this brings to typed FP in Kotlin it also helps in OOP contexts where dependencies can be provided at compile time:

```kotlin
typeclass Context {
fun Self.config(): Config
}
```

```kotlin
package prod
extension Service: Context {
fun Service.config(): Config = ProdConfig
}
```

```kotlin
package test
extension Service: Context {
fun Service.config(): Config = TestConfig
}
```

```kotlin
package prod
service.config() // ProdConfig
```

```kotlin
package test
service.config() // TestConfig
```

Type class instances and declarations can encode further constrains in their generic args so they can be composed nicely:

```kotlin
extension Option<A: Monoid> : Monoid {
fun empty(): Option<A> = None
fun Option.combine(ob: Option<A>): Option<A> =
when (this) {
is Some<A> -> when (ob) {
is Some<A> -> Some(this.value.combine(b.value))
is None -> ob
}
is None -> this
}
}
```

The above instance declares a `Monoid: Option<A>` as long as there is a `A: Monoid` in scope.

```kotlin
Option(1).combine(Option(1)) // Option(2)
Option("a").combine(Option("b")) // does not compile. Found `Option<A>: Monoid` instance providing `combine` but no `String: Monoid` instance was in scope
```

We believe the above proposed encoding fits nicely with Kotlin's philosophy of extensions and will reduce the boilerplate compared to other langs that also support typeclasses such as Scala where this is done via implicits.

# Typeclasses over type constructors

We recommend if this proposal is accepted that a lightweight version of higher kinds support is included to unveil the true power of typeclasses through the extensions mechanisms

A syntax that would allow for higher kinds in these definitions may look like this:

```kotlin
typeclass FunctionK<F<_>, G<_>> {
fun <A> invoke(fa: F<A>): G<A>
}
extension Option2List : FunctionK<Option, List> {
fun <A> invoke(fa: Option<A>): List<A> =
fa.fold({ emptyList() }, { listOf(it) })
}
```

If Higher Kinds where added along with typeclasses to the lang an alternate definition to the encoding below:

```kotlin
typeclass Functor {
fun Self.map(b: Self): Self
}
```

could be provided such as:

```kotlin
typeclass F<_> : Functor {
fun <A, B> map(fa: F<A>, f: (A) -> B): F<B>
}
extension Option: Functor {
fun <A, B> map(fa: Option<A>, f: (A) -> B): Option<B>
}
```

Here `F<_>` refers to a type that has a hole on it such as `Option`, `List`, etc.

A use of this declaration in a polymorphic function would look like:

```kotlin
fun <F<_> : Functor, A, B> transform(fa: F<A>, f: (A) -> B): F<B> = F.map(fa, f)
transform(Option(1), { it + 1 }) // Option(2)
transform("", { it + "b" }) // Does not compile: `String` is not type constructor with shape F<_>
transform(listOf(1), { it + 1 }) // does not compile: No `List<_>: Functor` instance defined in scope.
```

Once typeclasses extensions are defined over datatypes the compiler could automatically
add extensions to the target datatypes unless the target datatype already defines a method with the same signature:

```kotlin
typeclass F<_> : Functor {
fun <A, B> map(fa: F<A>, f: (A) -> B): F<B>
fun <A, B> Self.Companion.lift(f: (A) -> B): (F<A>) -> F<B>
}

This comment has been minimized.

Copy link
@abreslav

abreslav Nov 7, 2017

Member

So, no *-imports? People may not be happy with it.

I'd suspect that we'll need to think in advance of a strategy for code completion for such functions: where does the IDE look for instances and what does it import if needed?

This comment has been minimized.

Copy link
@raulraja

raulraja Nov 7, 2017

Author

I would like * imports. The only issue with * imports is that if the compiler found ambiguous instances it should bail with a proper message. It was suggested at some point that those so be explicit so that newbies were not confused as to where the instances where getting applied from but if we want to support import kategory.* for example to bring all of our instance into scope that would be awesome. The IDE is doing something similar in the Scala plugin in the case of implicits. It looks in all symbols imported in a given scope trying to find candidates for resolution and it if it finds one it activates the syntax.

This comment has been minimized.

Copy link
@abreslav

abreslav Nov 7, 2017

Member

*-imports will likely pose a performance challenge for the compiler

This comment has been minimized.

Copy link
@raulraja

raulraja Nov 7, 2017

Author

Something worth mentioning is that instances are only resolved at call sites where the invocation is concrete so the compiler does not need to look into all * where they are declared just where functions are invoked. I believe is the same way resolution now works to bring other symbols into scope without fully qualifying them with the full package name.

This comment has been minimized.

Copy link
@abreslav

abreslav Nov 8, 2017

Member

just where functions are invoked.

It's very-very many places in the code :)

I believe is the same way resolution now works to bring other symbols into scope without fully qualifying them with the full package name.

Not quite. Other symbols are bound by name, here we are binding by type, and it's a lot more work for the compiler

This comment has been minimized.

Copy link
@JLLeitschuh

JLLeitschuh Jun 16, 2018

What about cases where you want to import two implementations of the Monoid<Int> into the scope of one file?

Do you have to resolve this conflict by calling each scoped by a with block??

import intext.IntMonoid1
import intext.IntMonoid2

fun addInts1(a: Int, b: Int): Int = with(IntMonoid1) { add(a, b) }
fun addInts2(a: Int, b: Int): Int = with(IntMonoid2) { add(a, b) }

Edit:
NVM: This is defined below

extension Option: Functor {
fun <A, B> Self.map(fa: Option<A>, f: (A) -> B): Option<B> = ... //does not enable `Option(1).map(Option(1)) becase `Option#map` already exists with the same signature as an instance method
fun <A, B> lift(f: (A) -> B): (Option<A>) -> Option<B> = ... //enables Option.lift({n: Int -> n.toString() }) because the Option companion does not define `lift` // Option<Int> -> Option<String>
}
```

Some of this examples where originally proposed by Roman Elizarov and the Kategory contributors where these features where originally discussed https://kotlinlang.slack.com/archives/C1JMF6UDV/p1506897887000023

This comment has been minimized.

Copy link
@mkobit

mkobit Oct 2, 2017

Small note seeing this link, you may want to copy/paste the snippets if you believe it provides important context because the Kotlin slack is free and only has the most recent 10k messages.

This comment has been minimized.

Copy link
@raulraja

raulraja Oct 2, 2017

Author

@mkobit thanks, I copied most of the snippets from @elizarov into the proposal so I think we are good if that goes away.

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.