summaryrefslogtreecommitdiff
path: root/examples/scala-js/library/src/main/scala/scala/scalajs/js/UndefOr.scala
blob: b356e3ae01223704c9f74d09bf1487caddc161f4 (plain) (blame)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/*                     __                                               *\
**     ________ ___   / /  ___      __ ____  Scala.js API               **
**    / __/ __// _ | / /  / _ | __ / // __/  (c) 2013, LAMP/EPFL        **
**  __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \    http://scala-lang.org/     **
** /____/\___/_/ |_/____/_/ | |__/ /____/                               **
**                          |/____/                                     **
\*                                                                      */


package scala.scalajs.js

import scala.language.implicitConversions

/** Value of type A or the JS undefined value.
 *  In a type system with union types, this would really be
 *  `A | js.prim.Undefined`. Since Scala does not have union types, but this
 *  particular union is crucial to many interoperability scenarios, it is
 *  provided as this trait.
 *
 *  An API similar to that of [[scala.Option]] is provided through the
 *  [[UndefOrOps]] implicit class, with the understanding that `undefined` is
 *  the None value.
 */
@scala.scalajs.js.annotation.RawJSType // Don't do this at home!
sealed trait UndefOr[+A]

object UndefOr {
  implicit def any2undefOrA[A](value: A): UndefOr[A] =
    value.asInstanceOf[UndefOr[A]]

  implicit def undef2undefOr(value: prim.Undefined): UndefOr[Nothing] =
    value.asInstanceOf[UndefOr[Nothing]]

  implicit def undefOr2ops[A](value: UndefOr[A]): UndefOrOps[A] =
    new UndefOrOps(value)

  implicit def undefOr2jsAny[A](value: UndefOr[A])(implicit ev: A => Any): Any =
    value.map(ev).asInstanceOf[Any]
}

final class UndefOrOps[A](val self: UndefOr[A]) extends AnyVal {
  import UndefOrOps._

  /** Returns true if the option is `undefined`, false otherwise.
   */
  @inline final def isEmpty: Boolean = isUndefined(self)

  /** Returns true if the option is not `undefined`, false otherwise.
   */
  @inline final def isDefined: Boolean = !isEmpty

  /** Returns the option's value.
   *  @note The option must be nonEmpty.
   *  @throws Predef.NoSuchElementException if the option is empty.
   */
  @inline final def get: A =
    if (isEmpty) throw new NoSuchElementException("undefined.get")
    else self.asInstanceOf[A]

  @inline final private def forceGet: A = self.asInstanceOf[A]

  /** Returns the option's value if the option is nonempty, otherwise
   *  return the result of evaluating `default`.
   *
   *  @param default  the default expression.
   */
  @inline final def getOrElse[B >: A](default: => B): B =
    if (isEmpty) default else this.forceGet

  /** Returns the option's value if it is nonempty,
   *  or `null` if it is empty.
   *  Although the use of null is discouraged, code written to use
   *  $option must often interface with code that expects and returns nulls.
   *  @example {{{
   *  val initalText: Option[String] = getInitialText
   *  val textField = new JComponent(initalText.orNull,20)
   *  }}}
   */
  @inline final def orNull[A1 >: A](implicit ev: Null <:< A1): A1 =
    this getOrElse ev(null)

  /** Returns a $some containing the result of applying $f to this $option's
   *  value if this $option is nonempty.
   *  Otherwise return $none.
   *
   *  @note This is similar to `flatMap` except here,
   *  $f does not need to wrap its result in an $option.
   *
   *  @param  f   the function to apply
   *  @see flatMap
   *  @see foreach
   */
  @inline final def map[B](f: A => B): UndefOr[B] =
    if (isEmpty) undefined else f(this.forceGet)

  /** Returns the result of applying $f to this $option's
   *  value if the $option is nonempty.  Otherwise, evaluates
   *  expression `ifEmpty`.
   *
   *  @note This is equivalent to `$option map f getOrElse ifEmpty`.
   *
   *  @param  ifEmpty the expression to evaluate if empty.
   *  @param  f       the function to apply if nonempty.
   */
  @inline final def fold[B](ifEmpty: => B)(f: A => B): B =
    if (isEmpty) ifEmpty else f(this.forceGet)

  /** Returns the result of applying $f to this $option's value if
   *  this $option is nonempty.
   *  Returns $none if this $option is empty.
   *  Slightly different from `map` in that $f is expected to
   *  return an $option (which could be $none).
   *
   *  @param  f   the function to apply
   *  @see map
   *  @see foreach
   */
  @inline final def flatMap[B](f: A => UndefOr[B]): UndefOr[B] =
    if (isEmpty) undefined else f(this.forceGet)

  def flatten[B](implicit ev: A <:< UndefOr[B]): UndefOr[B] =
    if (isEmpty) undefined else ev(this.forceGet)

  /** Returns this $option if it is nonempty '''and''' applying the predicate $p to
   *  this $option's value returns true. Otherwise, return $none.
   *
   *  @param  p   the predicate used for testing.
   */
  @inline final def filter(p: A => Boolean): UndefOr[A] =
    if (isEmpty || p(this.forceGet)) self else undefined

  /** Returns this $option if it is nonempty '''and''' applying the predicate $p to
   *  this $option's value returns false. Otherwise, return $none.
   *
   *  @param  p   the predicate used for testing.
   */
  @inline final def filterNot(p: A => Boolean): UndefOr[A] =
    if (isEmpty || !p(this.forceGet)) self else undefined

  /** Returns false if the option is $none, true otherwise.
   *  @note   Implemented here to avoid the implicit conversion to Iterable.
   */
  final def nonEmpty = isDefined

  /** Necessary to keep $option from being implicitly converted to
   *  [[scala.collection.Iterable]] in `for` comprehensions.
   */
  @inline final def withFilter(p: A => Boolean): WithFilter[A] =
    new WithFilter(self, p)

  /** Returns true if this option is nonempty '''and''' the predicate
   *  $p returns true when applied to this $option's value.
   *  Otherwise, returns false.
   *
   *  @param  p   the predicate to test
   */
  @inline final def exists(p: A => Boolean): Boolean =
    !isEmpty && p(this.forceGet)

  /** Returns true if this option is empty '''or''' the predicate
   *  $p returns true when applied to this $option's value.
   *
   *  @param  p   the predicate to test
   */
  @inline final def forall(p: A => Boolean): Boolean =
    isEmpty || p(this.forceGet)

  /** Apply the given procedure $f to the option's value,
   *  if it is nonempty. Otherwise, do nothing.
   *
   *  @param  f   the procedure to apply.
   *  @see map
   *  @see flatMap
   */
  @inline final def foreach[U](f: A => U): Unit =
    if (!isEmpty) f(this.forceGet)

  /** Returns a $some containing the result of
   *  applying `pf` to this $option's contained
   *  value, '''if''' this option is
   *  nonempty '''and''' `pf` is defined for that value.
   *  Returns $none otherwise.
   *
   *  @param  pf   the partial function.
   *  @return the result of applying `pf` to this $option's
   *  value (if possible), or $none.
   */
  @inline final def collect[B](pf: PartialFunction[A, B]): UndefOr[B] =
    if (isEmpty) undefined
    else pf.applyOrElse(this.forceGet, (_: A) => undefined).asInstanceOf[UndefOr[B]]

  /** Returns this $option if it is nonempty,
   *  otherwise return the result of evaluating `alternative`.
   *  @param alternative the alternative expression.
   */
  @inline final def orElse[B >: A](alternative: => UndefOr[B]): UndefOr[B] =
    if (isEmpty) alternative else self

  /** Returns a singleton iterator returning the $option's value
   *  if it is nonempty, or an empty iterator if the option is empty.
   */
  def iterator: Iterator[A] =
    if (isEmpty) scala.collection.Iterator.empty
    else scala.collection.Iterator.single(this.forceGet)

  /** Returns a singleton list containing the $option's value
   *  if it is nonempty, or the empty list if the $option is empty.
   */
  def toList: List[A] =
    if (isEmpty) Nil else this.forceGet :: Nil

  /** Returns a [[scala.util.Left]] containing the given
   *  argument `left` if this $option is empty, or
   *  a [[scala.util.Right]] containing this $option's value if
   *  this is nonempty.
   *
   *  @param left the expression to evaluate and return if this is empty
   *  @see toLeft
   */
  @inline final def toRight[X](left: => X): Either[X, A] =
    if (isEmpty) Left(left) else Right(this.forceGet)

  /** Returns a [[scala.util.Right]] containing the given
   *  argument `right` if this is empty, or
   *  a [[scala.util.Left]] containing this $option's value
   *  if this $option is nonempty.
   *
   *  @param right the expression to evaluate and return if this is empty
   *  @see toRight
   */
  @inline final def toLeft[X](right: => X): Either[A, X] =
    if (isEmpty) Right(right) else Left(this.forceGet)

  /** Returns a [[scala.Some]] containing this $options's value
   *  if this $option is nonempty, [[scala.None]] otherwise.
   */
  @inline final def toOption: Option[A] =
    if (isEmpty) None else Some(this.forceGet)
}

object UndefOrOps {

  /** We need a whole WithFilter class to honor the "doesn't create a new
   *  collection" contract even though it seems unlikely to matter much in a
   *  collection with max size 1.
   */
  class WithFilter[A](self: UndefOr[A], p: A => Boolean) {
    def map[B](f: A => B): UndefOr[B] = self filter p map f
    def flatMap[B](f: A => UndefOr[B]): UndefOr[B] = self filter p flatMap f
    def foreach[U](f: A => U): Unit = self filter p foreach f
    def withFilter(q: A => Boolean): WithFilter[A] =
      new WithFilter[A](self, x => p(x) && q(x))
  }
}