Skip to content

Commit f910717

Browse files
committed
add Option syntax
1 parent d21b111 commit f910717

3 files changed

Lines changed: 121 additions & 0 deletions

File tree

core/src/main/scala/cats/mtl/syntax/all.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ trait AllSyntax
2727
with TellSyntax
2828
with HandleSyntax
2929
with ChronicleSyntax
30+
with OptionSyntax
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2021 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package cats
18+
package mtl
19+
package syntax
20+
21+
object option extends OptionSyntax
22+
23+
private[mtl] trait OptionSyntax {
24+
implicit def catsMtsSyntaxToOptionOps[A](oa: Option[A]): OptionOps[A] = new OptionOps[A](oa)
25+
}
26+
27+
final class OptionOps[A] private[mtl] (private val self: Option[A]) extends AnyVal {
28+
import OptionOps.*
29+
30+
/**
31+
* Lifts `Option[A]` to `F[A]` as long as there's `Raise[F, E]` in the scope and `F` is an
32+
* `Applicative`, with `E` used as the error value when the `Option` is empty.
33+
*
34+
* @note
35+
* method `.rescue` in the example requires `ApplicativeError[F, E]` instance.
36+
*
37+
* @example
38+
* (Scala 3)
39+
* {{{
40+
* import scala.util.*
41+
* import cats.mtl.*, syntax.option.*
42+
*
43+
* case class MyErr(err: String) extends Exception(err)
44+
*
45+
* val res1 =
46+
* Handle.allow:
47+
* Some(123).liftTo[Try]("OOPS")
48+
* .rescue: err =>
49+
* Failure(MyErr(err))
50+
*
51+
* assertEquals(res1, Success(123))
52+
*
53+
* val res2 =
54+
* Handle.allow:
55+
* None.liftTo[Try]("OOPS")
56+
* .rescue: err =>
57+
* Failure(MyErr(err))
58+
*
59+
* assertEquals(res2, Failure(MyErr("OOPS")))
60+
* }}}
61+
*/
62+
def liftTo[F[_]]: LiftToPartiallyApplied[F, A] =
63+
new LiftToPartiallyApplied(self)
64+
65+
/**
66+
* Raises `Option[A]` to `F[Unit]` as an error when present as long as there's `Raise[F, A]`
67+
* in the scope and `F` is an `Applicative`
68+
*
69+
* @note
70+
* method `.rescue` in the example requires `ApplicativeError[F, E]` instance.
71+
*
72+
* @example
73+
* (Scala 3)
74+
* {{{
75+
* import scala.util.*
76+
* import cats.mtl.*, syntax.option.*
77+
*
78+
* case class MyErr(err: String) extends Exception(err)
79+
*
80+
* val res1 =
81+
* Handle.allow:
82+
* Some("OOPS").raiseTo[Try]
83+
* .rescue: err =>
84+
* Failure(MyErr(err))
85+
*
86+
* assertEquals(res1, Failure(MyErr("OOPS")))
87+
*
88+
* val res2 =
89+
* Handle.allow:
90+
* (None: Option[String]).raiseTo[Try]
91+
* .rescue: err =>
92+
* Failure(MyErr(err))
93+
*
94+
* assertEquals(res2, Success(()))
95+
* }}}
96+
*/
97+
def raiseTo[F[_]](implicit F: Applicative[F], raise: Raise[F, A]): F[Unit] =
98+
self.fold(F.unit)(raise.raise)
99+
}
100+
101+
object OptionOps {
102+
final class LiftToPartiallyApplied[F[_], A](private val self: Option[A]) extends AnyVal {
103+
def apply[E](ifEmpty: => E)(implicit F: Applicative[F], raise: Raise[F, E]): F[A] =
104+
raise.fromOption(self)(ifEmpty)
105+
}
106+
}

tests/shared/src/test/scala/cats/mtl/tests/Syntax.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,20 @@ final class Syntax extends BaseSuite {
7070
bar[F] *> Bar(42).raise
7171

7272
val _ = foo[Either[Foo, *]]
73+
74+
def fooWithApplicativeError[F[_]](implicit
75+
// `Applicative` is required by `liftTo`.
76+
// `ApplicativeError` is used to enforce checks for non-ambiguity.
77+
F: ApplicativeError[F, Foo],
78+
raise: Raise[F, Foo]): Unit = {
79+
80+
val _ = (
81+
Some("abc").liftTo[F](Bar(123)): F[String],
82+
Some(Bar(456)).raiseTo[F]: F[Unit]
83+
)
84+
}
85+
86+
fooWithApplicativeError[Either[Foo, *]]
7387
}
7488

7589
test("Handle") {

0 commit comments

Comments
 (0)