-
Notifications
You must be signed in to change notification settings - Fork 451
/
Copy pathOptional.kt
223 lines (197 loc) · 7.35 KB
/
Optional.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
package arrow.optics
import arrow.core.Either
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.flatMap
import arrow.core.getOrElse
import arrow.core.identity
import arrow.core.prependTo
import arrow.core.toOption
import kotlin.jvm.JvmStatic
/**
* [Optional] is a type alias for [POptional] which fixes the type arguments
* and restricts the [POptional] to monomorphic updates.
*/
public typealias Optional<S, A> = POptional<S, S, A, A>
public fun <S, A> Optional(getOption: (source: S) -> Option<A>, set: (source: S, focus: A) -> S): Optional<S, A> =
POptional({ s -> getOption(s).toEither { s } }, set)
/**
* [Optional] is an optic that allows to focus into a structure and querying or [copy]'ing an optional focus.
*
* ```kotlin
* import arrow.core.None
* import arrow.core.Option
* import arrow.core.Some
* import arrow.optics.Optional
*
* data class User(val username: String, val email: Option<String>) {
* companion object {
* // can be out generated by @optics
* val email: Optional<User, String> = Optional(User::email) { user, email ->
* user.copy(email = Some(email))
* }
* }
* }
*
* fun main() {
* val original = User("arrow-user", None)
* val set = User.email.set(original, "[email protected]")
* val modified = User.email.modify(set, String::lowercase)
* println("original: $original, set: $set, modified: $modified")
* }
* ```
* <!--- KNIT example-optional-01.kt -->
*
* A (polymorphic) [POptional] is useful when setting or modifying a value for a type with a optional polymorphic focus
* i.e. POptional<Either<Int, Double>, Either<String, Double>, Int, String>
*
* A [POptional] can be seen as a weaker [Lens] and [Prism] and combines their weakest functions:
* - `set: (S, B) -> T` meaning we can focus into an `S` and set a value `B` for a target `A` and obtain a modified source `T`
* - `getOrModify: (S) -> Either<T, A>` meaning it returns the focus of a [POptional] OR the original value
*
* @param S the source of a [POptional]
* @param T the modified source of a [POptional]
* @param A the focus of a [POptional]
* @param B the modified focus of a [POptional]
*/
public interface POptional<S, T, A, B> : PTraversal<S, T, A, B> {
/**
* Get the modified source of a [POptional]
*/
override fun set(source: S, focus: B): T
/**
* Get the focus of a [POptional] or return the original value while allowing the type to change if it does not match
*/
public fun getOrModify(source: S): Either<T, A>
/**
* Get the focus of an [Optional] or `null` if the is not there
*/
public fun getOrNull(source: S): A? =
getOrModify(source).getOrNull()
override fun <R> foldMap(initial: R, combine: (R, R) -> R, source: S, map: (focus: A) -> R): R =
getOrModify(source).map(map).getOrElse { initial }
/**
* Modify the focus of a [POptional] with a function [map]
*/
override fun modify(source: S, map: (focus: A) -> B): T =
getOrModify(source).fold(::identity) { a -> set(source, map(a)) }
/**
* Set the focus of a [POptional] with a value.
* @return null if the [POptional] is not matching
*/
public fun setNullable(source: S, b: B): T? =
modifyNullable(source) { b }
/**
* Modify the focus of a [POptional] with a function [map]
* @return null if the [POptional] is not matching
*/
public fun modifyNullable(source: S, map: (focus: A) -> B): T? =
getOrNull(source)?.let { set(source, map(it)) }
/**
* Join two [POptional] with the same focus [B]
*/
public infix fun <S1, T1> choice(other: POptional<S1, T1, A, B>): POptional<Either<S, S1>, Either<T, T1>, A, B> =
POptional(
{ sources ->
sources.fold(
{ leftSource ->
getOrModify(leftSource).mapLeft { Either.Left(it) }
},
{ rightSource ->
other.getOrModify(rightSource).mapLeft { Either.Right(it) }
}
)
},
{ sources, focus ->
sources.mapLeft { leftSource -> this.set(leftSource, focus) }.map { rightSource -> other.set(rightSource, focus) }
}
)
/**
* Create a product of the [POptional] and a type [C]
*/
public fun <C> first(): POptional<Pair<S, C>, Pair<T, C>, Pair<A, C>, Pair<B, C>> =
POptional(
{ (source, c) -> getOrModify(source).mapLeft { Pair(it, c) }.map { Pair(it, c) } },
{ (source, c2), (update, c) -> setNullable(source, update)?.let { Pair(it, c) } ?: Pair(set(source, update), c2) }
)
/**
* Create a product of a type [C] and the [POptional]
*/
public fun <C> second(): POptional<Pair<C, S>, Pair<C, T>, Pair<C, A>, Pair<C, B>> =
POptional(
{ (c, s) -> getOrModify(s).mapLeft { c to it }.map { c to it } },
{ (c2, s), (c, b) -> setNullable(s, b)?.let { c to it } ?: (c2 to set(s, b)) }
)
/**
* Compose a [POptional] with a [POptional]
*/
public infix fun <C, D> compose(other: POptional<in A, out B, out C, in D>): POptional<S, T, C, D> =
POptional(
{ source ->
getOrModify(source).flatMap { a ->
other.getOrModify(a).mapLeft { b -> set(source, b) }
}
},
{ source, d -> modify(source) { a -> other.set(a, d) } }
)
public operator fun <C, D> plus(other: POptional<in A, out B, out C, in D>): POptional<S, T, C, D> =
this compose other
public companion object {
public fun <S> id(): Iso<S, S> = PIso.id()
/**
* [POptional] that takes either [S] or [S] and strips the choice of [S].
*/
public fun <S> codiagonal(): Optional<Either<S, S>, S> = POptional(
{ sources -> sources.fold({ Either.Right(it) }, { Either.Right(it) }) },
{ sources, focus -> sources.mapLeft { focus }.map { focus } }
)
/**
* Invoke operator overload to create a [POptional] of type `S` with focus `A`.
* Can also be used to construct [Optional]
*/
public operator fun <S, T, A, B> invoke(
getOrModify: (source: S) -> Either<T, A>,
set: (source: S, focus: B) -> T
): POptional<S, T, A, B> = object : POptional<S, T, A, B> {
override fun getOrModify(source: S): Either<T, A> = getOrModify(source)
override fun set(source: S, focus: B): T = set(source, focus)
}
/**
* [POptional] that never sees its focus
*/
public fun <A, B> void(): Optional<A, B> = POptional(
{ Either.Left(it) },
{ source, _ -> source }
)
/**
* [Optional] to safely operate on the head of a list
*/
@JvmStatic
public fun <A> listHead(): Optional<List<A>, A> = Optional(
getOption = { if (it.isNotEmpty()) Some(it[0]) else None },
set = { list, newHead -> if (list.isNotEmpty()) newHead prependTo list.drop(1) else emptyList() }
)
/**
* [Optional] to safely operate on the tail of a list
*/
@JvmStatic
public fun <A> listTail(): Optional<List<A>, List<A>> = Optional(
getOption = { if (it.isEmpty()) None else Some(it.drop(1)) },
set = { list, newTail -> if (list.isNotEmpty()) list[0] prependTo newTail else emptyList() }
)
/**
* [Optional] to safely operate in a nullable value.
*/
@JvmStatic
public fun <A> nullable(): Optional<A?, A> = Optional(
getOption = { it.toOption() },
set = { source, new -> source?.let { new } }
)
/**
* [Optional] to safely operate in a nullable value.
*/
@JvmStatic
public fun <A> notNull(): Optional<A?, A> = nullable()
}
}