Skip to content

Commit 120691f

Browse files
committed
Implemented feedback for options
1 parent 860c0ce commit 120691f

File tree

2 files changed

+67
-80
lines changed

2 files changed

+67
-80
lines changed

changelog.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161

6262
- The options module has been mostly rewritten. Most things should work, but
6363
it's no longer possible to compare two ``none`` options. Use ``isNone``
64-
instead.
64+
instead. The comparator `==` is also gone, use `==?` instead.
6565

6666

6767
#### Breaking changes in the compiler

lib/pure/options.nim

Lines changed: 66 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@
2828
## option returns an option.
2929
##
3030
## Comparing
31-
## Comparing one option to another like ``some(100) == some(50)`` returns an
31+
## Because of the way Nim treats comparators options uses special names to
32+
## compare values. Every comparator is post-fixed with a question mark. So the
33+
## equality operator becomes ``==?``, less than or equal to becomes ``<=?``
34+
## and so on. This is also done to help alleviate some of the confusion.
35+
## Comparing one option to another like ``some(100) ==? some(50)`` returns an
3236
## option. In this case they are not equal, so they return a ``none``. Note
3337
## however that it's not possible to compare two ``none`` values like this
34-
## ``none(int) == none(int)`` as it returns a ``none``. In the case that a
38+
## ``none(int) ==? none(int)`` as it returns a ``none``. In the case that a
3539
## comparator passes, the *first* option is returned so
36-
## ``some(100) < some(200)`` returns ``some(100)``.
40+
## ``some(100) <? some(200)`` returns ``some(100)``.
3741
##
3842
## Logic operations
3943
## Logic operations work on the options "has-ity" whether it has a value or
@@ -49,22 +53,6 @@
4953
## the one passed in. Since a ``none`` can't implicitly get a value ``not``
5054
## still returns a ``none`` when used on a ``none``.
5155
##
52-
## Booleans
53-
## Since both the comparators and the logic operators returns options it
54-
## might be confusing to look at something like:
55-
## ``if some(100) == some(100) == true`` but options will also implicitly
56-
## convert to a boolean when used in that context (with it's has-ity as the
57-
## value). So the above example could simply be written as
58-
## ``if some(100) == some(100)``. But any value can also implicitly be
59-
## converted to a ``some`` option. So we can simplify further with
60-
## ``if some(100) == 100``, this works as 100 is converted to a ``some``
61-
## which is then compared to the first ``some`` which returns a ``some(100)``
62-
## that automatically get's evaluated to true since it has a value. Should
63-
## the comparisson not hold we don't get an error but rather the comparator
64-
## would evaluate to a ``none[int]`` and all ``none`` options evaluates to
65-
## boolean false. Here we can see how treating the has-ity of an option makes
66-
## a lot of intuitive sense.
67-
##
6856
## Now that we have a better understanding of the nature of options we can take
6957
## a closer look at how to use them.
7058
##
@@ -157,14 +145,12 @@
157145
## This means that we can insert checks in a stream of options:
158146
##
159147
## .. code-block:: nim
160-
## assert (some(10) <= 20) == some(10)
161-
## assert (some(30) <= 20).isNone
162-
## assert (none(int) <= 20).isNone
148+
## assert (some(10) <=? some(20)) == some(10)
149+
## assert (some(30) <=? some(20)).isNone
150+
## assert (none(int) <=? some(20)).isNone
163151
## let x = some(10)
164-
## assert ((x >= 5) <= 20) == x
152+
## assert ((x >=? some(5)) <=? some(20)) == x
165153
## # This only returns some if x is in the range 5..20
166-
## # Note that since Nim currently rewrites >= to a <= statement with flipped
167-
## # arguments this currently doesn't work as it returns some(5).
168154
##
169155
## Options can also be used for conditional chaining. Let's use the ``find``
170156
## procedure we defined above:
@@ -186,10 +172,11 @@
186172
## also be used in if statements:
187173
##
188174
## .. code-block:: nim
189-
## if "hello world".find('w').?min(4) <= 2:
175+
## if ("hello world".find('w').?min(4) <=? some(2)).isSome:
190176
## echo "Never run" # the first statement returns a none option, which when
191177
## # compared to 2 returns another none option. This none option is then
192-
## # evaluated to false. In this case min is still never run.
178+
## # obviously not a some. Note that in this case it would be better to use
179+
## # a `match`.
193180

194181
import typetraits
195182
import macros
@@ -267,10 +254,10 @@ proc get*[T](self: Option[T], otherwise: T): T =
267254
otherwise
268255
269256
template either*(self, otherwise: untyped): untyped =
270-
## Wrapper around get with a default, for use as ``either(x, 20)``, if ``x``
271-
## is a ``none`` then it will return ``20``, otherwise it will return the
272-
## value of ``x``.
273-
get(self, otherwise)
257+
## Similar in function to get, but if ``otherwise`` is a procedure it will not
258+
## be evaluated if ``self`` is a ``some``. This means that ``otherwise`` can
259+
## have side effects.
260+
if self.isSome: self.val else: otherwise
274261
275262
proc map*[T](self: Option[T], callback: proc (input: T)) =
276263
## Applies a callback to the value in this Option
@@ -487,27 +474,29 @@ proc optCmp*[T](self: Option[T], value: Option[T],
487474
if self.isSome and value.isSome and cmp(self.val, value.val):
488475
return self
489476
490-
template `==`*[T](self: Option[T], value: Option[T]): Option[T] =
477+
proc `==`*[T](self: Option[T], value: Option[T]): bool {.error.} = discard
478+
479+
template `==?`*[T](self: Option[T], value: Option[T]): Option[T] =
491480
## Wrapper for optCmp with ``==`` as the comparator
492481
optCmp(self, value, proc (val1, val2: T): bool = val1 == val2)
493482
494-
template `not`*[T](self: Option[T]): Option[T] =
495-
## Not always returns a ``none`` as a ``none`` can't implicitly get a value
496-
none[T]()
483+
template `!=?`*[T](self: Option[T], value: Option[T]): Option[T] =
484+
## Wrapper for optCmp with ``==`` as the comparator
485+
optCmp(self, value, proc (val1, val2: T): bool = val1 != val2)
497486
498-
template `<=`*[T](self: Option[T], value: Option[T]): Option[T] =
487+
template `<=?`*[T](self: Option[T], value: Option[T]): Option[T] =
499488
## Wrapper for optCmp with ``<=`` as the comparator
500489
optCmp(self, value, proc (val1, val2: T): bool = val1 <= val2)
501490
502-
template `>=`*[T](self: Option[T], value: Option[T]): Option[T] =
491+
template `>=?`*[T](self: Option[T], value: Option[T]): Option[T] =
503492
## Wrapper for optCmp with ``>=`` as the comparator
504493
optCmp(self, value, proc (val1, val2: T): bool = val1 >= val2)
505494
506-
template `>`*[T](self: Option[T], value: Option[T]): Option[T] =
495+
template `>?`*[T](self: Option[T], value: Option[T]): Option[T] =
507496
## Wrapper for optCmp with ``>`` as the comparator
508497
optCmp(self, value, proc (val1, val2: T): bool = val1 > val2)
509498
510-
template `<`*[T](self: Option[T], value: Option[T]): Option[T] =
499+
template `<?`*[T](self: Option[T], value: Option[T]): Option[T] =
511500
## Wrapper for optCmp with ``<`` as the comparator
512501
optCmp(self, value, proc (val1, val2: T): bool = val1 < val2)
513502
@@ -521,6 +510,10 @@ proc toOpt*[T](value: T): Option[T] =
521510
## it's not already an option.
522511
some(value)
523512
513+
template `not`*[T](self: Option[T]): Option[T] =
514+
## Not always returns a ``none`` as a ``none`` can't implicitly get a value
515+
none[T]()
516+
524517
template `or`*[T](x, y: Option[T]): Option[T] =
525518
## When ``x`` is none, return ``y``
526519
let xx = x
@@ -536,14 +529,8 @@ template `and`*[T,U](x: Option[T]; y: Option[U]): Option[U] =
536529
else:
537530
none[U]()
538531
539-
converter toSome*[T](value: T): Option[T] =
540-
## Automatic wrapping of values into ``some``
541-
some(value)
542-
converter toBool*[T](opt: Option[T]): bool =
543-
## Options use their has-ity as their boolean value
544-
opt.isSome
545-
546532
when isMainModule:
533+
echo some(10) < some(5)
547534
import unittest, sequtils
548535
549536
suite "options":
@@ -576,12 +563,12 @@ when isMainModule:
576563
check none(string).isSome == false
577564
578565
test "equality":
579-
check some("a") == some("a")
580-
check some(7) != some(6)
566+
check (some("a") ==? some("a")).isSome
567+
check (some(7) !=? some(6)).isSome
581568
check some("a").isNone == false
582569
check intNone.isNone
583-
check (intNone == intNone) == false
584-
check (none(int) == intNone) == false
570+
check (intNone ==? intNone).isNone
571+
check (none(int) ==? intNone).isNone
585572
586573
when compiles(some("a") == some(5)):
587574
check false
@@ -591,7 +578,7 @@ when isMainModule:
591578
test "get with a default value":
592579
check(some("Correct").get("Wrong") == "Correct")
593580
check(stringNone.get("Correct") == "Correct")
594-
check(either(some(100) < some(200), 10) == 100)
581+
check(either(some(100) <? some(200), 10) == 100)
595582
596583
test "$":
597584
check($(some("Correct")) == "Some(Correct)")
@@ -604,13 +591,13 @@ when isMainModule:
604591
intNone.map(proc (v: int) = check false)
605592
606593
test "map":
607-
check(some(123).map(proc (v: int): int = v * 2) == some(246))
594+
check (some(123).map(proc (v: int): int = v * 2) ==? some(246)).isSome
608595
check(intNone.map(proc (v: int): int = v * 2).isNone)
609596
610597
test "filter":
611-
check(some(123).filter(proc (v: int): bool = v == 123) == some(123))
612-
check(some(456).filter(proc (v: int): bool = v == 123).isNone)
613-
check(intNone.filter(proc (v: int): bool = check false).isNone)
598+
check (some(123).filter(proc (v: int): bool = v == 123) ==? some(123)).isSome
599+
check some(456).filter(proc (v: int): bool = v == 123).isNone
600+
check intNone.filter(proc (v: int): bool = check false).isNone
614601
615602
test "flatMap":
616603
proc addOneIfNotZero(v: int): Option[int] =
@@ -619,26 +606,26 @@ when isMainModule:
619606
else:
620607
result = none(int)
621608
622-
check(some(1).flatMap(addOneIfNotZero) == some(2))
623-
check(some(0).flatMap(addOneIfNotZero).isNone)
624-
check(some(1).flatMap(addOneIfNotZero).flatMap(addOneIfNotZero) ==
625-
some(3))
609+
check (some(1).flatMap(addOneIfNotZero) ==? some(2)).isSome
610+
check some(0).flatMap(addOneIfNotZero).isNone
611+
check (some(1).flatMap(addOneIfNotZero).flatMap(addOneIfNotZero) ==?
612+
some(3)).isSome
626613
627614
proc maybeToString(v: int): Option[string] =
628615
if v != 0:
629616
result = some($v)
630617
else:
631618
result = none(string)
632619
633-
check(some(1).flatMap(maybeToString) == some("1"))
620+
check (some(1).flatMap(maybeToString) ==? some("1")).isSome
634621
635622
proc maybeExclaim(v: string): Option[string] =
636623
if v != "":
637624
result = some v & "!"
638625
else:
639626
result = none(string)
640627
641-
check(some(1).flatMap(maybeToString).flatMap(maybeExclaim) == some("1!"))
628+
check (some(1).flatMap(maybeToString).flatMap(maybeExclaim) ==? some("1!")).isSome
642629
check(some(0).flatMap(maybeToString).flatMap(maybeExclaim).isNone)
643630
644631
test "SomePointer":
@@ -697,8 +684,8 @@ when isMainModule:
697684
test "compare":
698685
proc leq(x, y: int): bool =
699686
x <= y
700-
check optCmp(10, 20, leq) == some(10)
701-
check optCmp(30, 20, leq).isNone
687+
check (optCmp(some(10), some(20), leq) ==? some(10)).isSome
688+
check optCmp(some(30), some(20), leq).isNone
702689

703690
test "logical operators":
704691
let a = none[string]()
@@ -724,29 +711,29 @@ when isMainModule:
724711
check(sideEffects == 3)
725712
reset()
726713

727-
check ((genNone() or genSome()) == some("b")) == true
714+
check ((genNone() or genSome()) ==? some("b")).isSome
728715
check sideEffects == 2
729716
reset()
730717

731-
check (((genNone() or genSome()) or genSome()) == some("b")) == true
718+
check (((genNone() or genSome()) or genSome()) ==? some("b")).isSome
732719
check sideEffects == 2
733720
reset()
734721

735-
check ((genNone() or genSome()) or "c" == "b") == true
722+
check ((genNone() or genSome()) or some("c")).unsafeGet == "b"
736723
check sideEffects == 2
737724
reset()
738725

739-
check ((genNone() or genNone()) or "c" == "c") == true
726+
check ((genNone() or genNone()) or some("c")).unsafeGet == "c"
740727
check sideEffects == 2
741728
reset()
742729

743-
check (((genSome() or genNone()) or genSome()) or "c" == "a") == true
730+
check (((genSome() or genNone()) or genSome()) or some("c")).unsafeGet == "a"
744731
check(sideEffects == 1)
745732
reset()
746733

747734
# test ``and`` operator
748735

749-
check (((genSome() and genSome()) and genSome()) == some("c")) == true
736+
check (((genSome() and genSome()) and genSome()) ==? some("c")).isSome
750737
check sideEffects == 3
751738
reset()
752739

@@ -758,34 +745,34 @@ when isMainModule:
758745
check sideEffects == 2
759746
reset()
760747

761-
check isNone((genSome() and genNone()) and "c")
748+
check isNone((genSome() and genNone()) and some("c"))
762749
check sideEffects == 2
763750
reset()
764751

765-
check (((genSome() and genSome()) and "c") == some("c")) == true
752+
check ((genSome() and genSome()) and some("c")).unsafeGet == "c"
766753
check sideEffects == 2
767754
reset()
768755

769-
check isNone(((genNone() and genSome()) and genNone()) and "c")
756+
check isNone(((genNone() and genSome()) and genNone()) and some("c"))
770757
check sideEffects == 1
771758
reset()
772759

773760
# change the type during the expression and mix ``and`` with ``or``
774-
check genNone() and 1 or 2 == 2 == true
775-
check genSome() and 1 or 2 == 1 == true
761+
check (genNone() and some(1) or some(2)).unsafeGet == 2
762+
check (genSome() and some(1) or some(2)).unsafeGet == 1
776763

777764
test "conditional continuation":
778765
when not compiles(some("Hello world").?find('w').echo):
779766
check false
780-
check (some("Hello world").?find('w') == 6)
781-
check (none(string) or some("hello corld") or some("hello world")).?
782-
find('w') == -1
767+
check (some("Hello world").?find('w') ==? some(6)).isSome
768+
check ((none(string) or some("hello corld") or some("hello world")).?
769+
find('w') ==? some(-1)).isSome
783770
var evaluated = false
784-
if some("team").?find('i') == -1:
771+
if (some("team").?find('i') ==? some(-1)).isSome:
785772
evaluated = true
786773
check evaluated == true
787774
evaluated = false
788-
if none(string).?find('i') == -1:
775+
if (none(string).?find('i') ==? some(-1)).isSome:
789776
evaluated = true
790777
check evaluated == false
791778

0 commit comments

Comments
 (0)