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

Add module for functional manipulation #21

Merged
merged 4 commits into from
Nov 25, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
cedrickcooke marked this conversation as resolved.
Show resolved Hide resolved

- [`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
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;
twyatt marked this conversation as resolved.
Show resolved Hide resolved
}

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) { "`times` must not be negative" }
cedrickcooke marked this conversation as resolved.
Show resolved Hide resolved
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