{"id":19814109,"url":"https://github.com/guibrandt/higher-kt","last_synced_at":"2025-02-28T14:02:15.587Z","repository":{"id":129285762,"uuid":"397968685","full_name":"GuiBrandt/higher-kt","owner":"GuiBrandt","description":"Some experiments with annotation processors, code generation, higher kinded types (sort of) and typeclasses (sort of) in Kotlin","archived":false,"fork":false,"pushed_at":"2021-08-19T16:26:25.000Z","size":75,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-11-12T09:50:37.761Z","etag":null,"topics":["annotation-processor","category-theory","code-generation","functional-programming","higher-kinded-types","kotlin","typeclasses"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GuiBrandt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-08-19T14:18:54.000Z","updated_at":"2021-08-19T16:26:28.000Z","dependencies_parsed_at":"2023-07-14T22:00:10.431Z","dependency_job_id":null,"html_url":"https://github.com/GuiBrandt/higher-kt","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GuiBrandt%2Fhigher-kt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GuiBrandt%2Fhigher-kt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GuiBrandt%2Fhigher-kt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GuiBrandt%2Fhigher-kt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GuiBrandt","download_url":"https://codeload.github.com/GuiBrandt/higher-kt/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233455820,"owners_count":18678963,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["annotation-processor","category-theory","code-generation","functional-programming","higher-kinded-types","kotlin","typeclasses"],"created_at":"2024-11-12T09:38:07.840Z","updated_at":"2025-01-11T07:35:34.024Z","avatar_url":"https://github.com/GuiBrandt.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Higher Kotlin\n\nSome experiments with annotation processors, code generation, higher kinded types\n(sort of) and typeclasses (sort of) in Kotlin. Most of the stuff here isn't practical.\n\n## Higher-Kinded types\n\nSee: https://en.wikipedia.org/wiki/Kind_(type_theory)\n\nImplemented as suggested by Yallop \u0026 White (2014) in their paper on higher-kinded\npolymorphism for languages that do not support it.\n\n### Usage\n\nJust annotate some class with `@Higher` and have the annotation processor on scope\n(see [\"Using kapt\"][kapt]):\n\n```kotlin\n@Higher\ndata class Pair\u003cA, B\u003e(val left: A, val right: B) { companion object }\n```\n\n(P.S.: this requires a companion object on the target class)\n\n[kapt]: https://kotlinlang.org/docs/kapt.html\n\n#### Interface\n\nThe annotation processor generates the following entities:\n- `{Class}Kind` (e.g. `PairKind`), an empty object whose sole purpose is serving\n  as a tag for higher-kind type expressions (more on those later)\n- Two projection/injection functions to transition between the \"lower-kinded\"\n  and \"higher-kinded\" worlds:\n  - `{Class}.Companion.inj` (e.g. `Pair.inj`). An injection function mapping an\n    object (e.g. `Pair\u003cA, B\u003e`) to its \"higher-kinded\" equivalent (e.g. `Ap2\u003cPairKind, A, B\u003e`).\n    This is only necessary because there's no way to modify the class to implement\n    the corresponding interface with an annotation processor, unfortunately.\n  - `{Class}.Companion.prj` (e.g. `Pair.prj`). The inverse function of `inj`.\n- Two helper functions for lifting/unlifting functions to/from the \"higher-kinded\" world:\n  - `{Class}.Companion.lift` takes `{Class}\u003cA, B, ...\u003e -\u003e {Class}\u003cC, D, ...\u003e` to\n    `ApN\u003c{Class}Kind, A, B, ...\u003e -\u003e Ap{N}\u003c{Class}Kind, C, D, ...\u003e`\n  - `{Class}.Companion.lift` is the inverse function of `lift`.\n\n#### Higher-Kind type expressions\n\nWe define the following (purposefully empty) interface:\n\n```kotlin\ninterface Ap\u003cT, X\u003e\n```\n\nA higher-kinded type expression is any valid parametrization of `Ap`.\n\nKinds are curried, which means a kind of two arguments (`* -\u003e * -\u003e *`) is just a\nnested parametrization of `Ap`: `Ap\u003cAp\u003cKind, A\u003e, B\u003e`.\nWe provide a shorthand in the form of `Ap{N}\u003cKind, A, B, ...\u003e = Ap\u003cAp\u003cAp\u003c...\u003e, A\u003e, B\u003e`, up\nto 8 arguments (i.e. `Ap2`, `Ap3`, ... `Ap8`).\n\n##### Example with lists\n\nFor instance, suppose the following type:\n```kotlin\nsealed class List\u003cT\u003e { companion object }\nclass Nil\u003cT\u003e : List\u003cT\u003e()\ndata class Cons\u003cT\u003e(val head: T, val tail: List\u003cT\u003e) : List\u003cT\u003e()\n```\n\nWe could refer to the \"higher kind\" of List by creating a tag (i.e. an empty object) for it:\n```kotlin\nobject ListKind // List\u003cT\u003e ~ Ap\u003cListKind, T\u003e\n```\n\nObviously, there's nothing linking `List` to `ListKind`. We do this by defining injection and\nprojection functions from `List\u003cT\u003e` to `Ap\u003cListKind, T\u003e` and vice-versa, respectively:\n\n```kotlin\nprivate data class ListWrapper\u003cT\u003e(val it: List\u003cT\u003e): Ap\u003cListKind, T\u003e\n\nfun \u003cT\u003e inject(list: List\u003cT\u003e): Ap\u003cListKind, T\u003e = ListWrapper(list)\n\n// Assume an unique implementation of Ap\u003cListKind, T\u003e\nfun \u003cT\u003e project(it: Ap\u003cListKind, T\u003e): List\u003cT\u003e = (it as ListWrapper\u003cT\u003e).it\n```\n\nOr simply:\n```kotlin\nobject ListKind\nsealed class List\u003cT\u003e: Ap\u003cListKind, T\u003e {\n  companion object {\n    fun \u003cT\u003e inject(list: List\u003cT\u003e): Ap\u003cListKind, T\u003e = list\n    fun \u003cT\u003e project(it: Ap\u003cListKind, T\u003e): List\u003cT\u003e = it as List\u003cT\u003e\n  }\n}\nclass Nil\u003cT\u003e : List\u003cT\u003e()\ndata class Cons\u003cT\u003e(val head: T, val tail: List\u003cT\u003e) : List\u003cT\u003e()\n```\n\nThe first approach is essentially what the annotation processor does, since it can't\nchange `List` to make it implement `Ap\u003cListKind, T\u003e`.\n\n### References\n\n- Yallop, Jeremy, and Leo White. \"Lightweight higher-kinded polymorphism.\"\n  International Symposium on Functional and Logic Programming. Springer, Cham, 2014.\n  Available at https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf.\n\n## Typeclasses\n\nOne interesting application for higher-kinded types is in typeclasses, like\n[those seen in Haskell][hs-typeclasses]. \n\nSadly, those too require some support from the language. We can work our way around\nthis, though in a similar way to what is done in [Scala Cats][scala-cats]: a typeclass\nsignature is replaced by an interface, and an instance is replaced by an object that\nimplements the interface.\n\nWe still cannot automatically derive instances from a set of constraints\n(e.g. `(Monoid a, Monoid b) =\u003e Monoid (a, b)`), but it's simple enough (although significantly\nmore verbose) to simulate this with functions\n(e.g. `Pair\u003cA, B\u003e.monoid: (Monoid\u003cA\u003e, Monoid\u003cB\u003e) -\u003e Monoid\u003cPair\u003cA, B\u003e\u003e`).\n\n### Example with `Functor`\n\nSee https://en.wikipedia.org/wiki/Functor_(functional_programming)\n\nIt's pretty straightforward to define the interface for a Functor:\n\n```kotlin\ninterface Functor\u003cF\u003e {\n    fun fmap(f: (A) -\u003e B): (Ap\u003cF, A\u003e) -\u003e Ap\u003cF, B\u003e\n}\n```\n\nNotice that we couldn't possibly have defined it without `Ap`, though, because `F\u003cA\u003e`\nwould be syntactically invalid in Kotlin.\n\nNow let's use the `List` kind (this time with all the functions generated by the\nannotation processor, instead of done by hand) to define a functor for lists:\n\n```kotlin\n@Higher\nsealed class List\u003cT\u003e { companion object }\nclass Nil\u003cT\u003e : List\u003cT\u003e()\ndata class Cons\u003cT\u003e(val head: T, val tail: List\u003cT\u003e) : List\u003cT\u003e()\n\nfun List.Companion.functor() = object : Functor\u003cListKind\u003e {\n  override fun \u003cX, Y\u003e fmap(f: (X) -\u003e Y): (Ap\u003cListKind, X\u003e) -\u003e Ap\u003cListKind, Y\u003e =\n    List.lift { list: List\u003cX\u003e -\u003e\n      when (list) {\n        is Nil -\u003e Nil()\n        is Cons -\u003e Cons(f(list.head), List.unlift(fmap(f))(list.tail))\n      }\n    }\n}\n```\n\nUsing the instance of functor then is not too hard:\n\n```kotlin\nfun main() {\n    val list = Cons(1, Cons(2, Cons(3, Nil())))\n    val double = { x: Int -\u003e x * 2 }\n    val doubleList = List.unlift(List.functor().fmap(double))\n\n    println(doubleList(list)) // Cons(head=2, tail=Cons(head=4, tail=Cons(head=6, tail=io.github.higherkt.sample.Nil@4769b07b)))\n}\n```\n\nWe could also simplify the declaration of `doubleList` by defining a shorthand\nfor the application of `unlift` to an `fmap`'ed function, or even hide the functor\naltogether with an instance method (a.k.a. `map`):\n\n```kotlin\nfun \u003cX, Y\u003e List.Companion.fmap(f: (X) -\u003e Y): (List\u003cX\u003e) -\u003e List\u003cY\u003e = List.unlift(List.functor().fmap(f))\n\nfun \u003cX, Y\u003e List\u003cX\u003e.map(f: (X) -\u003e Y): List\u003cY\u003e = List.fmap(f)(this)\n```\n\nThis shows that the need to convert between `Ap` and `List` is indeed inconvenient,\nbut can usually be worked around fairly easily.\n\n[hs-typeclasses]: https://www.haskell.org/tutorial/classes.html\n[scala-cats]: https://typelevel.org/cats/typeclasses.html\n\n\n## [WIP]\n\nTODO: Polynomial functors, cata/ana/hylo/paramorphisms.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguibrandt%2Fhigher-kt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fguibrandt%2Fhigher-kt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguibrandt%2Fhigher-kt/lists"}