Skip to content

Commit

Permalink
Add module for functional manipulation (#21)
Browse files Browse the repository at this point in the history
* Add module for functional manipulation

* Dump API

* Readme fix and remove unused block in gradle

* Codecov fix and repeat requirement message update
  • Loading branch information
cedrickcooke authored Nov 25, 2020
1 parent 0753a91 commit 8e77f2a
Show file tree
Hide file tree
Showing 22 changed files with 689 additions and 3 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ Toolbox of utilities/helpers for Kotlin development.
- [`coroutines`](coroutines) - Tools/utilities for [Coroutines]
![badge][badge-js]
![badge][badge-jvm]
![badge][badge-macos]
![badge][badge-mac]

- [`functional`](functional) - Tools/utilities for manipulating functions
![badge][badge-js]
![badge][badge-jvm]

- [`test`](test) - Utilities for test suites
![badge][badge-js]
![badge][badge-jvm]
![badge][badge-macos]
![badge][badge-mac]


[Collections]: https://kotlinlang.org/docs/reference/collections-overview.html
Expand Down
1 change: 1 addition & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ coverage:
fixes:
- "com/juul/tuulbox/collections::"
- "com/juul/tuulbox/coroutines/flow::"
- "com/juul/tuulbox/functional::"
61 changes: 61 additions & 0 deletions functional/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
![badge-js]
![badge-jvm]

# Functional

Tool box with utilities for manipulating functions.
For a full functional ecosystem, complete with `Monad` and the like, prefer [Arrow](https://arrow-kt.io/).

## Installation

### Gradle

Artifacts are hosted on GitHub packages, which can be configured as follows:

```kotlin
import java.net.URI

repositories {
maven {
url = URI("https://maven.pkg.github.com/juullabs/android-github-packages")
credentials {
username = findProperty("github.packages.username") as String
password = findProperty("github.packages.password") as String
}
}
}
```

Then the needed artifact(s) can be defined as dependencies.

**Multiplatform projects**

```kotlin
kotlin {
sourceSets {
val commonMain by getting {
implementation("com.juul.tuulbox:functional:$version")
}
}
}
```

**Platform-specific projects**

```kotlin
dependencies {
implementation("com.juul.tuulbox:functional-$platform:$version")
}
```


[badge-android]: http://img.shields.io/badge/platform-android-6EDB8D.svg?style=flat
[badge-ios]: http://img.shields.io/badge/platform-ios-CDCDCD.svg?style=flat
[badge-js]: http://img.shields.io/badge/platform-js-F8DB5D.svg?style=flat
[badge-jvm]: http://img.shields.io/badge/platform-jvm-DB413D.svg?style=flat
[badge-linux]: http://img.shields.io/badge/platform-linux-2D3F6C.svg?style=flat
[badge-windows]: http://img.shields.io/badge/platform-windows-4D76CD.svg?style=flat
[badge-mac]: http://img.shields.io/badge/platform-macos-111111.svg?style=flat
[badge-watchos]: http://img.shields.io/badge/platform-watchos-C0C0C0.svg?style=flat
[badge-tvos]: http://img.shields.io/badge/platform-tvos-808080.svg?style=flat
[badge-wasm]: https://img.shields.io/badge/platform-wasm-624FE8.svg?style=flat
59 changes: 59 additions & 0 deletions functional/api/functional.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
public final class com/juul/tuulbox/functional/ComposeKt {
public static final fun of (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
public static final fun then (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
}

public final class com/juul/tuulbox/functional/CurryKt {
public static final fun curry (Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function1;
public static final fun curry (Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function1;
public static final fun curry (Lkotlin/jvm/functions/Function4;)Lkotlin/jvm/functions/Function1;
public static final fun curry (Lkotlin/jvm/functions/Function5;)Lkotlin/jvm/functions/Function1;
public static final fun curry (Lkotlin/jvm/functions/Function6;)Lkotlin/jvm/functions/Function1;
public static final fun curryFirst (Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function1;
public static final fun curryFirst (Lkotlin/jvm/functions/Function4;)Lkotlin/jvm/functions/Function1;
public static final fun curryFirst (Lkotlin/jvm/functions/Function5;)Lkotlin/jvm/functions/Function1;
public static final fun curryFirst (Lkotlin/jvm/functions/Function6;)Lkotlin/jvm/functions/Function1;
}

public final class com/juul/tuulbox/functional/MemoizeKt {
public static final fun memoize (Lkotlin/jvm/functions/Function0;)Lkotlin/jvm/functions/Function0;
public static final fun memoize (Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
public static final fun memoize (Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function2;
public static final fun memoize (Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function3;
public static final fun memoize (Lkotlin/jvm/functions/Function4;)Lkotlin/jvm/functions/Function4;
public static final fun memoize (Lkotlin/jvm/functions/Function5;)Lkotlin/jvm/functions/Function5;
public static final fun memoize (Lkotlin/jvm/functions/Function6;)Lkotlin/jvm/functions/Function6;
}

public final class com/juul/tuulbox/functional/PartialApplyKt {
public static final fun partialApply (Lkotlin/jvm/functions/Function1;Ljava/lang/Object;)Lkotlin/jvm/functions/Function0;
public static final fun partialApply (Lkotlin/jvm/functions/Function2;Ljava/lang/Object;)Lkotlin/jvm/functions/Function1;
public static final fun partialApply (Lkotlin/jvm/functions/Function3;Ljava/lang/Object;)Lkotlin/jvm/functions/Function2;
public static final fun partialApply (Lkotlin/jvm/functions/Function4;Ljava/lang/Object;)Lkotlin/jvm/functions/Function3;
public static final fun partialApply (Lkotlin/jvm/functions/Function5;Ljava/lang/Object;)Lkotlin/jvm/functions/Function4;
public static final fun partialApply (Lkotlin/jvm/functions/Function6;Ljava/lang/Object;)Lkotlin/jvm/functions/Function5;
}

public final class com/juul/tuulbox/functional/RepeatKt {
public static final fun repeat (Lkotlin/jvm/functions/Function1;I)Lkotlin/jvm/functions/Function1;
}

public final class com/juul/tuulbox/functional/ReverseKt {
public static final fun reverse (Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function2;
public static final fun reverse (Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function3;
public static final fun reverse (Lkotlin/jvm/functions/Function4;)Lkotlin/jvm/functions/Function4;
public static final fun reverse (Lkotlin/jvm/functions/Function5;)Lkotlin/jvm/functions/Function5;
public static final fun reverse (Lkotlin/jvm/functions/Function6;)Lkotlin/jvm/functions/Function6;
}

public final class com/juul/tuulbox/functional/RotateKt {
public static final fun rotate (Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function3;
public static final fun rotate (Lkotlin/jvm/functions/Function4;)Lkotlin/jvm/functions/Function4;
public static final fun rotate (Lkotlin/jvm/functions/Function5;)Lkotlin/jvm/functions/Function5;
public static final fun rotate (Lkotlin/jvm/functions/Function6;)Lkotlin/jvm/functions/Function6;
public static final fun rotateBack (Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function3;
public static final fun rotateBack (Lkotlin/jvm/functions/Function4;)Lkotlin/jvm/functions/Function4;
public static final fun rotateBack (Lkotlin/jvm/functions/Function5;)Lkotlin/jvm/functions/Function5;
public static final fun rotateBack (Lkotlin/jvm/functions/Function6;)Lkotlin/jvm/functions/Function6;
}

38 changes: 38 additions & 0 deletions functional/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
plugins {
kotlin("multiplatform")
id("org.jmailen.kotlinter")
jacoco
`maven-publish`
}

apply(from = rootProject.file("gradle/jacoco.gradle.kts"))
apply(from = rootProject.file("gradle/publish.gradle.kts"))

kotlin {
explicitApi()

jvm()
js().browser()

sourceSets {
val commonTest by getting {
dependencies {
implementation(project(":test"))
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}

val jvmTest by getting {
dependencies {
implementation(kotlin("test-junit"))
}
}

val jsTest by getting {
dependencies {
implementation(kotlin("test-js"))
}
}
}
}
9 changes: 9 additions & 0 deletions functional/src/commonMain/kotlin/Compose.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.juul.tuulbox.functional

/** Compose two functions. [this] is called first, and then [second] is called with the result. */
public fun <I1, I2, O> ((I1) -> I2).then(second: (I2) -> O): (I1) -> O =
{ i1 -> second(this(i1)) }

/** Compose two functions. [first] is called first, and then [this] is called with the result. */
public fun <I1, I2, O> ((I2) -> O).of(first: (I1) -> I2): (I1) -> O =
{ i1 -> this(first(i1)) }
37 changes: 37 additions & 0 deletions functional/src/commonMain/kotlin/Curry.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.juul.tuulbox.functional

/** Curry a two-argument function. */
public fun <I1, I2, O> ((I1, I2) -> O).curry(): (I1) -> (I2) -> O =
{ i1 -> { i2 -> this(i1, i2) } }

/** Curry a three-argument function. */
public fun <I1, I2, I3, O> ((I1, I2, I3) -> O).curry(): (I1) -> (I2) -> (I3) -> O =
{ i1 -> { i2 -> { i3 -> this(i1, i2, i3) } } }

/** Curry a four-argument function. */
public fun <I1, I2, I3, I4, O> ((I1, I2, I3, I4) -> O).curry(): (I1) -> (I2) -> (I3) -> (I4) -> O =
{ i1 -> { i2 -> { i3 -> { i4 -> this(i1, i2, i3, i4) } } } }

/** Curry a five-argument function. */
public fun <I1, I2, I3, I4, I5, O> ((I1, I2, I3, I4, I5) -> O).curry(): (I1) -> (I2) -> (I3) -> (I4) -> (I5) -> O =
{ i1 -> { i2 -> { i3 -> { i4 -> { i5 -> this(i1, i2, i3, i4, i5) } } } } }

/** Curry a six-argument function. */
public fun <I1, I2, I3, I4, I5, I6, O> ((I1, I2, I3, I4, I5, I6) -> O).curry(): (I1) -> (I2) -> (I3) -> (I4) -> (I5) -> (I6) -> O =
{ i1 -> { i2 -> { i3 -> { i4 -> { i5 -> { i6 -> this(i1, i2, i3, i4, i5, i6) } } } } } }

/** Curry the first argument of a three-argument function. */
public fun <I1, I2, I3, O> ((I1, I2, I3) -> O).curryFirst(): (I1) -> (I2, I3) -> O =
{ i1 -> { i2, i3 -> this(i1, i2, i3) } }

/** Curry the first argument of a four-argument function. */
public fun <I1, I2, I3, I4, O> ((I1, I2, I3, I4) -> O).curryFirst(): (I1) -> (I2, I3, I4) -> O =
{ i1 -> { i2, i3, i4 -> this(i1, i2, i3, i4) } }

/** Curry the first argument of a five-argument function. */
public fun <I1, I2, I3, I4, I5, O> ((I1, I2, I3, I4, I5) -> O).curryFirst(): (I1) -> (I2, I3, I4, I5) -> O =
{ i1 -> { i2, i3, i4, i5 -> this(i1, i2, i3, i4, i5) } }

/** Curry the first argument of a six-argument function. */
public fun <I1, I2, I3, I4, I5, I6, O> ((I1, I2, I3, I4, I5, I6) -> O).curryFirst(): (I1) -> (I2, I3, I4, I5, I6) -> O =
{ i1 -> { i2, i3, i4, i5, i6 -> this(i1, i2, i3, i4, i5, i6) } }
46 changes: 46 additions & 0 deletions functional/src/commonMain/kotlin/Memoize.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.juul.tuulbox.functional

/** Returns a locally-memoized version of this function. Two separate invocations of [memoize] will have two separate caches. */
public fun <O> (() -> O).memoize(): () -> O {
val value by lazy { this() }
return { value }
}

/** Returns a locally-memoized version of this function. Two separate invocations of [memoize] will have two separate caches. */
public fun <I1, O> ((I1) -> O).memoize(): (I1) -> O {
val cache = mutableMapOf<I1, O>()
return { i1 -> cache.getOrPut(i1) { this(i1) } }
}

/** Returns a locally-memoized version of this function. Two separate invocations of [memoize] will have two separate caches. */
public fun <I1, I2, O> ((I1, I2) -> O).memoize(): (I1, I2) -> O {
val cache = mutableMapOf<Pair<I1, I2>, O>()
return { i1, i2 -> cache.getOrPut(Pair(i1, i2)) { this(i1, i2) } }
}

/** Returns a locally-memoized version of this function. Two separate invocations of [memoize] will have two separate caches. */
public fun <I1, I2, I3, O> ((I1, I2, I3) -> O).memoize(): (I1, I2, I3) -> O {
val cache = mutableMapOf<Triple<I1, I2, I3>, O>()
return { i1, i2, i3 -> cache.getOrPut(Triple(i1, i2, i3)) { this(i1, i2, i3) } }
}

/** Returns a locally-memoized version of this function. Two separate invocations of [memoize] will have two separate caches. */
public fun <I1, I2, I3, I4, O> ((I1, I2, I3, I4) -> O).memoize(): (I1, I2, I3, I4) -> O {
// TODO: This is slightly less efficient than a Tuple4 data class.
val cache = mutableMapOf<List<Any?>, O>()
return { i1, i2, i3, i4 -> cache.getOrPut(listOf(i1, i2, i3, i4)) { this(i1, i2, i3, i4) } }
}

/** Returns a locally-memoized version of this function. Two separate invocations of [memoize] will have two separate caches. */
public fun <I1, I2, I3, I4, I5, O> ((I1, I2, I3, I4, I5) -> O).memoize(): (I1, I2, I3, I4, I5) -> O {
// TODO: This is slightly less efficient than a Tuple5 data class.
val cache = mutableMapOf<List<Any?>, O>()
return { i1, i2, i3, i4, i5 -> cache.getOrPut(listOf(i1, i2, i3, i4, i5)) { this(i1, i2, i3, i4, i5) } }
}

/** Returns a locally-memoized version of this function. Two separate invocations of [memoize] will have two separate caches. */
public fun <I1, I2, I3, I4, I5, I6, O> ((I1, I2, I3, I4, I5, I6) -> O).memoize(): (I1, I2, I3, I4, I5, I6) -> O {
// TODO: This is slightly less efficient than a Tuple6 data class.
val cache = mutableMapOf<List<Any?>, O>()
return { i1, i2, i3, i4, i5, i6 -> cache.getOrPut(listOf(i1, i2, i3, i4, i5, i6)) { this(i1, i2, i3, i4, i5, i6) } }
}
25 changes: 25 additions & 0 deletions functional/src/commonMain/kotlin/PartialApply.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.juul.tuulbox.functional

/** Binds the argument of this function with a constant [value]. */
public fun <I1, O> ((I1) -> O).partialApply(value: I1): () -> O =
{ this(value) }

/** Binds the first argument of this function with a constant [value]. */
public fun <I1, I2, O> ((I1, I2) -> O).partialApply(value: I1): (I2) -> O =
{ i2 -> this(value, i2) }

/** Binds the first argument of this function with a constant [value]. */
public fun <I1, I2, I3, O> ((I1, I2, I3) -> O).partialApply(value: I1): (I2, I3) -> O =
{ i2, i3 -> this(value, i2, i3) }

/** Binds the first argument of this function with a constant [value]. */
public fun <I1, I2, I3, I4, O> ((I1, I2, I3, I4) -> O).partialApply(value: I1): (I2, I3, I4) -> O =
{ i2, i3, i4 -> this(value, i2, i3, i4) }

/** Binds the first argument of this function with a constant [value]. */
public fun <I1, I2, I3, I4, I5, O> ((I1, I2, I3, I4, I5) -> O).partialApply(value: I1): (I2, I3, I4, I5) -> O =
{ i2, i3, i4, i5 -> this(value, i2, i3, i4, i5) }

/** Binds the first argument of this function with a constant [value]. */
public fun <I1, I2, I3, I4, I5, I6, O> ((I1, I2, I3, I4, I5, I6) -> O).partialApply(value: I1): (I2, I3, I4, I5, I6) -> O =
{ i2, i3, i4, i5, i6 -> this(value, i2, i3, i4, i5, i6) }
13 changes: 13 additions & 0 deletions functional/src/commonMain/kotlin/Repeat.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.juul.tuulbox.functional

/** Returns a function that repeats the application of [this] [count] times. */
public fun <T> ((T) -> T).repeat(count: Int): (T) -> T {
require(count >= 0) { "Repeat `count` must not be negative." }
return { initialValue ->
var value = initialValue
kotlin.repeat(count) {
value = this(value)
}
value
}
}
21 changes: 21 additions & 0 deletions functional/src/commonMain/kotlin/Reverse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.juul.tuulbox.functional

/** Returns a function where the argument order has been reversed. */
public fun <I1, I2, O> ((I1, I2) -> O).reverse(): (I2, I1) -> O =
{ i2, i1 -> this(i1, i2) }

/** Returns a function where the argument order has been reversed. */
public fun <I1, I2, I3, O> ((I1, I2, I3) -> O).reverse(): (I3, I2, I1) -> O =
{ i3, i2, i1 -> this(i1, i2, i3) }

/** Returns a function where the argument order has been reversed. */
public fun <I1, I2, I3, I4, O> ((I1, I2, I3, I4) -> O).reverse(): (I4, I3, I2, I1) -> O =
{ i4, i3, i2, i1 -> this(i1, i2, i3, i4) }

/** Returns a function where the argument order has been reversed. */
public fun <I1, I2, I3, I4, I5, O> ((I1, I2, I3, I4, I5) -> O).reverse(): (I5, I4, I3, I2, I1) -> O =
{ i5, i4, i3, i2, i1 -> this(i1, i2, i3, i4, i5) }

/** Returns a function where the argument order has been reversed. */
public fun <I1, I2, I3, I4, I5, I6, O> ((I1, I2, I3, I4, I5, I6) -> O).reverse(): (I6, I5, I4, I3, I2, I1) -> O =
{ i6, i5, i4, i3, i2, i1 -> this(i1, i2, i3, i4, i5, i6) }
33 changes: 33 additions & 0 deletions functional/src/commonMain/kotlin/Rotate.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.juul.tuulbox.functional

/** Returns a function where the last argument has been moved to before the first argument. */
public fun <I1, I2, I3, O> ((I1, I2, I3) -> O).rotate(): (I3, I1, I2) -> O =
{ i3, i1, i2 -> this(i1, i2, i3) }

/** Returns a function where the last argument has been moved to before the first argument. */
public fun <I1, I2, I3, I4, O> ((I1, I2, I3, I4) -> O).rotate(): (I4, I1, I2, I3) -> O =
{ i4, i1, i2, i3 -> this(i1, i2, i3, i4) }

/** Returns a function where the last argument has been moved to before the first argument. */
public fun <I1, I2, I3, I4, I5, O> ((I1, I2, I3, I4, I5) -> O).rotate(): (I5, I1, I2, I3, I4) -> O =
{ i5, i1, i2, i3, i4 -> this(i1, i2, i3, i4, i5) }

/** Returns a function where the last argument has been moved to before the first argument. */
public fun <I1, I2, I3, I4, I5, I6, O> ((I1, I2, I3, I4, I5, I6) -> O).rotate(): (I6, I1, I2, I3, I4, I5) -> O =
{ i6, i1, i2, i3, i4, i5 -> this(i1, i2, i3, i4, i5, i6) }

/** Returns a function where the first argument has been moved to after the last argument. */
public fun <I1, I2, I3, O> ((I1, I2, I3) -> O).rotateBack(): (I2, I3, I1) -> O =
{ i2, i3, i1 -> this(i1, i2, i3) }

/** Returns a function where the first argument has been moved to after the last argument. */
public fun <I1, I2, I3, I4, O> ((I1, I2, I3, I4) -> O).rotateBack(): (I2, I3, I4, I1) -> O =
{ i2, i3, i4, i1 -> this(i1, i2, i3, i4) }

/** Returns a function where the first argument has been moved to after the last argument. */
public fun <I1, I2, I3, I4, I5, O> ((I1, I2, I3, I4, I5) -> O).rotateBack(): (I2, I3, I4, I5, I1) -> O =
{ i2, i3, i4, i5, i1 -> this(i1, i2, i3, i4, i5) }

/** Returns a function where the first argument has been moved to after the last argument. */
public fun <I1, I2, I3, I4, I5, I6, O> ((I1, I2, I3, I4, I5, I6) -> O).rotateBack(): (I2, I3, I4, I5, I6, I1) -> O =
{ i2, i3, i4, i5, i6, i1 -> this(i1, i2, i3, i4, i5, i6) }
19 changes: 19 additions & 0 deletions functional/src/commonTest/kotlin/ComposeTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.juul.tuulbox.functional

import com.juul.tuulbox.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals

class ComposeTests {
@Test
fun checkOfOrderIsCorrect() = runTest {
assertEquals(202, double.of(increment).invoke(100))
assertEquals(201, increment.of(double).invoke(100))
}

@Test
fun checkThenOrderIsCorrect() = runTest {
assertEquals(201, double.then(increment).invoke(100))
assertEquals(202, increment.then(double).invoke(100))
}
}
Loading

0 comments on commit 8e77f2a

Please sign in to comment.