Skip to content

Commit

Permalink
Optics Cons (#1114)
Browse files Browse the repository at this point in the history
  • Loading branch information
nomisRev committed Nov 15, 2018
1 parent f4ad5be commit 86dd8cc
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 11 deletions.
3 changes: 3 additions & 0 deletions modules/docs/arrow-docs/docs/_data/menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,9 @@ options:
- title: Traversal
url: /docs/optics/traversal/

- title: Cons
url: /docs/optics/cons

- title: At
url: /docs/optics/at/

Expand Down
55 changes: 55 additions & 0 deletions modules/docs/arrow-docs/docs/docs/optics/cons/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
layout: docs
title: Cons
permalink: /docs/optics/cons/
---

## Cons

{:.beginner}
beginner

`Cons` provides a [Prism]({{ '/docs/optics/prism' | relative_url }}) between a structure `S` and its first element `A` and tail `S`.
It provides a convenient way to attach or detach elements to the beginning side of a structure [S].

It can be constructed by providing the `Prism`.

```kotlin:ank
import arrow.data.ListK
import arrow.optics.instances.listk.cons.cons
import arrow.optics.typeclasses.Cons
val listFirst = ListK.cons<Int>().cons()
val instance = Cons(listFirst)
instance
```

It defines two functions `cons` and `uncons`.

`cons` prepends an element `A` to a structure `S`.

```kotlin:ank
import arrow.optics.instances.list.cons.cons
1.cons(listOf(2, 3))
```

`uncons` detaches the first element `A` from a structure `S`.

```kotlin:ank
import arrow.optics.instances.list.cons.uncons
listOf(1, 2, 3).uncons()
```
```kotlin:ank
emptyList<Int>().uncons()
```

### Data types

```kotlin:ank:replace
import arrow.reflect.*
import arrow.optics.typeclasses.*
TypeClass(Cons::class).dtMarkdownList()
```
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package arrow.optics.instances

import arrow.Kind
import arrow.core.ListInstances
import arrow.core.left
import arrow.core.right
import arrow.core.toT
import arrow.core.*
import arrow.data.k
import arrow.optics.Optional
import arrow.optics.POptional
import arrow.optics.Traversal
import arrow.optics.*
import arrow.optics.typeclasses.Cons
import arrow.optics.typeclasses.Each
import arrow.optics.typeclasses.FilterIndex
import arrow.optics.typeclasses.Index
Expand Down Expand Up @@ -94,3 +90,20 @@ interface ListIndexInstance<A> : Index<List<A>, Int, A> {
operator fun <A> invoke() = object : ListIndexInstance<A> {}
}
}

fun <A> ListInstances.cons(): Cons<List<A>, A> = ListConsInstance()

/**
* [Cons] instance definition for [List].
*/
interface ListConsInstance<A> : Cons<List<A>, A> {
override fun cons(): Prism<List<A>, Tuple2<A, List<A>>> = PPrism(
getOrModify = { list -> list.firstOrNull()?.let { Tuple2(it, list.drop(1)) }?.right() ?: list.left() },
reverseGet = { (a, aas) -> listOf(a) + aas }
)

companion object {

operator fun <A> invoke() = object : ListConsInstance<A> {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import arrow.Kind
import arrow.core.*
import arrow.data.*
import arrow.extension
import arrow.optics.Optional
import arrow.optics.POptional
import arrow.optics.Traversal
import arrow.optics.*
import arrow.optics.typeclasses.Cons
import arrow.optics.typeclasses.Each
import arrow.optics.typeclasses.FilterIndex
import arrow.optics.typeclasses.Index
Expand Down Expand Up @@ -55,4 +54,15 @@ interface ListKIndexInstance<A> : Index<ListK<A>, Int, A> {
getOrModify = { it.getOrNull(i)?.right() ?: it.left() },
set = { l, a -> l.mapIndexed { index: Int, aa: A -> if (index == i) a else aa }.k() }
)
}
}

/**
* [Cons] instance definition for [ListK].
*/
@extension
interface ListKConsInstance<A> : Cons<ListK<A>, A> {
override fun cons(): Prism<ListK<A>, Tuple2<A, ListK<A>>> = PPrism(
getOrModify = { list -> list.firstOrNull()?.let { Tuple2(it, list.drop(1).k()) }?.right() ?: list.left() },
reverseGet = { (a, aas) -> ListK(listOf(a) + aas) }
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package arrow.optics.instances

import arrow.*
import arrow.core.Tuple2
import arrow.core.left
import arrow.core.right
import arrow.data.*
import arrow.typeclasses.*
import arrow.optics.*
Expand Down Expand Up @@ -102,3 +105,26 @@ interface StringIndexInstance : Index<String, Int, Char> {
}

}

/**
* [String]'s [Cons] instance
*/
fun String.Companion.cons(): Cons<String, Char> = StringConsInstance()

interface StringConsInstance : Cons<String, Char> {

override fun cons(): Prism<String, Tuple2<Char, String>> = Prism(
getOrModify = { if (it.isNotEmpty()) Tuple2(it.first(), it.drop(1)).right() else it.left() },
reverseGet = { (h, t) -> h + t }
)

companion object {
/**
* Operator overload to instantiate typeclass instance.
*
* @return [Cons] instance for [String]
*/
operator fun invoke(): Cons<String, Char> = object : StringConsInstance {}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package arrow.optics.typeclasses

import arrow.core.*
import arrow.optics.*

/**
* [Cons] provides a [Prism] between [S] and its first element [A] and tail [S].
* It provides a convenient way to attach or detach elements to the left side of a structure [S].
*
* @param S source of [Prism] and tail of [Prism] focus.
* @param A first element of [Prism] focus, [A] is supposed to be unique for a given [S].
*/
interface Cons<S, A> {

/**
* Provides a [Prism] between [S] and its first element [A] and tail [S].
*/
fun cons(): Prism<S, Tuple2<A, S>>

/**
* Provides an [Optional] between [S] and its first element [A].
*/
fun firstOption(): Optional<S, A> =
cons() compose Tuple2.first()

/**
* Provides an [Optional] between [S] and its tail [S].
*/
fun tailOption(): Optional<S, S> =
cons() compose Tuple2.second()

/**
* Prepend an element [A] to the first element of [S].
*
* @receiver [A] element to prepend or attach on left side of [tail].
*/
infix fun A.cons(tail: S): S =
cons().reverseGet(Tuple2(this, tail))

/**
* Deconstruct an [S] to its optional first element [A] and tail [S].
*
* @receiver [S] structure to uncons into its first element [A] and tail [S].
*/
fun S.uncons(): Option<Tuple2<A, S>> =
cons().getOption(this)

companion object {

/**
* Lift an instance of [Cons] using an [Iso].
*/
fun <S, A, B> fromIso(C: Cons<A, B>, iso: Iso<S, A>): Cons<S, B> = object : Cons<S, B> {
override fun cons(): Prism<S, Tuple2<B, S>> = iso compose C.cons() compose iso.reverse().second()
}

operator fun <S, A> invoke(prism: Prism<S, Tuple2<A, S>>): Cons<S, A> = object : Cons<S, A> {
override fun cons(): Prism<S, Tuple2<A, S>> = prism
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ package arrow.optics.instances

import arrow.core.*
import arrow.data.*
import arrow.instances.eq
import arrow.instances.listk.eq.eq
import arrow.instances.option.eq.eq
import arrow.instances.tuple2.eq.eq
import arrow.optics.instances.listk.cons.cons
import arrow.optics.instances.listk.each.each
import arrow.optics.instances.listk.filterIndex.filterIndex
import arrow.optics.instances.listk.index.index
import arrow.optics.typeclasses.FilterIndex
import arrow.test.UnitSpec
import arrow.test.generators.*
import arrow.test.laws.OptionalLaws
import arrow.test.laws.PrismLaws
import arrow.test.laws.TraversalLaws
import arrow.typeclasses.Eq
import io.kotlintest.KTestJUnitRunner
Expand Down Expand Up @@ -80,5 +84,14 @@ class ListInstanceTest : UnitSpec() {
EQA = Eq.any()
))

testLaws(PrismLaws.laws(
prism = ListK.cons<Int>().cons(),
aGen = genListK(Gen.int()),
bGen = genTuple(Gen.int(), genListK(Gen.int())),
funcGen = genFunctionAToB(genTuple(Gen.int(), genListK(Gen.int()))),
EQA = ListK.eq(Int.eq()),
EQOptionB = Option.eq(Tuple2.eq(Int.eq(), ListK.eq(Int.eq())))
))

}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package arrow.optics.instances

import arrow.core.Option
import arrow.core.Tuple2
import arrow.data.ListK
import arrow.instances.eq
import arrow.instances.listk.eq.eq
import arrow.instances.option.eq.eq
import arrow.instances.tuple2.eq.eq
import arrow.test.UnitSpec
import arrow.test.generators.genChars
import arrow.test.generators.genFunctionAToB
import arrow.test.generators.genTuple
import arrow.test.laws.OptionalLaws
import arrow.test.laws.PrismLaws
import arrow.test.laws.TraversalLaws
import arrow.typeclasses.Eq
import io.kotlintest.KTestJUnitRunner
Expand Down Expand Up @@ -48,5 +53,14 @@ class StringInstanceTest : UnitSpec() {
EQA = Eq.any()
))

testLaws(PrismLaws.laws(
prism = String.cons().cons(),
aGen = Gen.string(),
bGen = genTuple(genChars(), Gen.string()),
funcGen = genFunctionAToB(genTuple(genChars(), Gen.string())),
EQA = String.eq(),
EQOptionB = Option.eq(Tuple2.eq(Char.eq(), String.eq()))
))

}
}

0 comments on commit 86dd8cc

Please sign in to comment.