-
Notifications
You must be signed in to change notification settings - Fork 0
/
Checking.scala
186 lines (184 loc) · 6.65 KB
/
Checking.scala
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
/*
* Copyright 2012 Latterfrosken Software Development Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.lafros.scala
/**
* Mix-in alternative to importing Checking._, when wishing to use the nested class, Checked.
*/
trait Checking {
/**
* Yet another 'biased Either' candidate.
*
* For use as the return type, in place of A, wherever an exception might otherwise be thrown
* and a more 'functional' style is preferred.
*
* Thus, a Checked[A, R] will contain either an A wrapped in an instance of Okay or else an R
* wrapped in an instance of Reason, depending on whether or not an exceptional condition
* arose.
*/
sealed abstract class Checked[+A, +R] {
def isOkay: Boolean
/**
* throws a NoSuchElementException if this is a Reason.
*/
def get: A = this match {
case Okay(a) => a
case Reason(_) => throw new NoSuchElementException("Reason.get")
}
/**
* throws a NoSuchElementException if this is an Okay.
*/
def reason: R = this match {
case Okay(_) => throw new NoSuchElementException("Okay.reason")
case Reason(r) => r
}
def getOrElse[B >: A](b: => B) = this match {
case Okay(a) => a
case Reason(_) => b
}
def map[B](f: A => B): Checked[B, R] = this match {
case Okay(a) => Okay(f(a))
case Reason(r) => Reason(r)
}
def flatMap[B, S >: R](f: A => Checked[B, S]): Checked[B, S] = this match {
case Okay(a) => f(a)
case Reason(s) => Reason(s)
}
def foreach[B](f: A => B): Unit = this match {
case Okay(a) => f(a)
case Reason(_) =>
}
def toOption: Option[A] = if (isOkay) Some(get) else None
}
/**
* see Checked.
*/
final case class Okay[+A, +R](a: A) extends Checked[A, R] {
def isOkay = true
}
/**
* see Checked.
*/
final case class Reason[+A, +R](r: R) extends Checked[A, R] {
def isOkay = false
}
/**
* type to which some T is converted by the implicit conversion, any2CheckedLift.
*/
abstract class CheckedLift[T] {
def toOkay [R]: Okay[T, R]
def toReason[A]: Reason[A, T]
/**
* ff as in fail-fast: applies checks to the target T, in a fail-fast manner (stopping after
* the first Reason is encountered).
*/
def ff [R](checks: (T => Checked[T, R])*): Checked[T, R]
/**
* fs as in fail-slowly: applies checks to the target T, in a fail-slowly manner (continuing
* after the first Reason is encounted).
*/
def fs [R](checks: (T => Checked[T, R])*): Checked[T, Iterable[R]]
}
/**
* See CheckedLift.
*/
implicit def any2CheckedLift[T](any: T): CheckedLift[T] = new CheckedLift[T] {
def toOkay [R] = Okay[T, R](any)
def toReason[A] = Reason[A, T](any)
def ff[R](checks: (T => Checked[T, R])*) = {
var opt: Option[Reason[T, R]] = None
checks.find { _.apply(any) match {
case reason @ Reason(_) => opt = Some(reason); true
case _ => false
}
}
opt.getOrElse(Okay(any))
}
def fs[R](checks: (T => Checked[T, R])*) = {
val rs: Iterable[R] = for {
check <- checks
res = check(any)
if !res.isOkay
} yield res.reason
if (rs.isEmpty) Okay(any) else Reason(rs)
}
}
/**
* the type of value that will be wrapped in a Reason, that only needs to be supplied when
* using the implicitly-supplied operator, <*>.
*/
type R
trait FailFastAppFunct[A, B] {
/**
* handles a fail-slowly result where Reason contains an Iterable. */
def <*>[Rs](checked: Checked[A, Rs])(implicit ev: Rs <:< Iterable[R]): Checked[B, R]
/**
* confers fail-fast applicative-functor status: lets you apply a FunctionN to N values in N
* contexts, failing as soon as the first Reason is encountered. */
def <*>(checked: Checked[A, R]): Checked[B, R]
}
trait FailSlowlyAppFunct[A, B] {
/**
* handles a fail-slowly result where Reason contains an Iterable. */
def <*>[Rs](checked: Checked[A, Rs])(implicit ev: Rs <:< Iterable[R]): Checked[B, Iterable[R]]
/**
* confers fail-slowly applicative-functor status: lets you apply a FunctionN to N values
* in N contexts, continuing to accumulate further Reasons after the first is encountered. */
def <*>(checked: Checked[A, R]): Checked[B, Iterable[R]]
}
/**
* lifts f, which must be curried, into an Okay, for fail-fast application (stopping after
* the first Reason is encountered). */
def ff[A, B](f: A => B) = Okay[A => B, R](f)
/**
* as above, but for fail-slowly application (continuing after the first Reason is
* encountered). */
def fs[A, B](f: A => B) = Okay[A => B, Iterable[R]](f)
implicit def checkedOfFun2ffap[A, B](f: Checked[A => B, R]): FailFastAppFunct[A, B] =
new FailFastAppFunct[A, B] {
def <*>[Rs](checked: Checked[A, Rs])(implicit ev: Rs <:< Iterable[R]) = checked match {
case Okay(a) => <*>(Okay[A, R](a))
case Reason(rs) => <*>(Reason[A, R](rs.head))
}
def <*>(checked: Checked[A, R]) = (f, checked) match {
case ( Okay(f), Okay(a)) => Okay(f(a))
case ( Okay(_), Reason(r)) => Reason(r)
case (Reason(r), _) => Reason(r)
}
}
implicit def checkedOfFun2fsap[A, B](f: Checked[A => B, Iterable[R]]): FailSlowlyAppFunct[A, B] =
new FailSlowlyAppFunct[A, B] {
def <*>[Rs](checked: Checked[A, Rs])(implicit ev: Rs <:< Iterable[R]) =
(f, checked) match {
case (Okay(f), Okay(a)) => Okay(f(a))
case (Okay(_), Reason(rs)) => Reason(rs)
case (Reason(rs), Okay(_)) => Reason(rs)
case (Reason(rs1), Reason(rs2)) => Reason(rs1 ++ rs2)
}
def <*>(checked: Checked[A, R]) = (f, checked) match {
case (Okay(f), Okay(a)) => Okay(f(a))
case (Okay(_), Reason(r)) => Reason(Iterable(r))
case (Reason(rs), Okay(_)) => Reason(rs)
case (Reason(rs), Reason(r)) => Reason(rs ++ Iterable(r))
}
}
}
/**
* companion object provided as a convenience, that may be imported, instead of mixing in the
* trait, and having R set to String.
*/
object Checking extends Checking {
type R = String
}