A macro which provides a safe access pattern to the maybe type. This avoids the need to have a get function which throws an exception when its used improperly.
1327 |
This makes the following conversion
1328 |
maybeCase m:
1329 |
just x:
1330 |
expr1 using x
1331 |
nothing:
1332 |
expr2 that does not use x(trying to refer to x will not compile)
1333 |
1334 |
1335 |
1336 |
converts to --->
1337 |
if m.isValid:
1338 |
eval expr using x where x is replaced with m.value
1339 |
else:
1340 |
eval expr2
1341 |
1342 |
1343 |
1344 |
1345 |
1346 |
1347 |
1348 |
1349 |
1350 |
1351 |
1352 |
1356 |
1357 |
1358 |
1359 |
1360 |
1361 |
1362 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014, Josh Filstrup
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 |
8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 |
10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13 |
--------------------------------------------------------------------------------
/maybe.nimble:
--------------------------------------------------------------------------------
1 | # Package
2 |
3 | version = "2.2"
4 | author = "superfunc"
5 | description = "A maybe type for nim."
6 | license = "BSD"
7 | srcDir = "src"
8 | skipDirs = @["examples", "tests"]
9 |
10 | # Deps
11 |
12 | requires "nim >= 0.9.6"
13 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | maybe
2 | --
3 | 
4 |
5 | **Nimble Package:** https://nimble.directory/pkg/maybe
6 |
7 | **Docs:** https://superfunc.github.io/maybe
8 |
9 | > Note 1: ~There is a [chance](https://github.com/nim-lang/Nim/pull/8358)
10 | > the main macro(maybeCase) may get merged into the standard library.
11 | > If this happens I'll recommend people use that, but will accept bugfixes
12 | > and reports on this library going forward, just no new features.~
13 |
14 | > Note 2: The PR was not accepted, so maybe lives on!
15 |
16 | An implementation of a maybe type, also known as option(al) in other languages.
17 |
18 | **Why Not Just use Option[T] from the standard library?**: In short, this library doesn't throw
19 | exceptions. It achieves this by using a macro to provide a safe pattern
20 | in which a maybe object can't be invalidly accessed, see `maybeCase` in the
21 | docs for further details. For a small example:
22 |
23 | ```nim
24 | var m = maybe.just(4)
25 | maybeCase m:
26 | just x:
27 | var y = 3
28 | echo $(x+y)
29 | nothing:
30 | echo "no value"
31 |
32 | var nada = maybe.nothing[int]()
33 | maybeCase nada:
34 | just foo:
35 | echo "hi this is a value we cant print" & $foo
36 | nothing:
37 | echo "nope no value, nice try with your invalid access"
38 |
39 | ## This prints out:
40 | ## >> 7
41 | ## >> nope no value, nice try with your invalid access
42 | ```
43 |
44 | Note that trying to access our local binding(`x` and `foo`)
45 | outside of the `just` blocks will result in a compile time error.
46 | This is how we achieve safe access.
47 |
48 | ## Installation
49 | Should be installed via [nimble](http://github.com/nimrod-code/nimble)
50 |
51 | ``` nimble install maybe ```
52 |
53 | ## License Info
54 | > Copyright (c) Josh Filstrup 2014-2019
55 | Licensed under BSD3 (see license.md for details)
56 |
--------------------------------------------------------------------------------
/src/examples/example.nim:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2014-2017, Josh Filstrup
2 | # Licensed under BSD3(see license.md file for details)
3 |
4 | import ../maybe/maybe
5 |
6 | # -------------------------------------------
7 | # The maybe type
8 | # -------------------------------------------
9 |
10 | # A simple adding function to use with our monads
11 | proc adder(x: int) : Maybe[int] =
12 | return maybe.just(x+x)
13 |
14 | # Initialize two maybe values
15 | var
16 | maybe1 = maybe.just(5)
17 | maybe2 = maybe.nothing[int]()
18 |
19 | # Create two results, of type Maybe[int]
20 | # based on a chain of computations
21 | var
22 | maybeResult1 = maybe1 >>= adder >>= adder >>= adder
23 | maybeResult2 = maybe2 >>= adder >>= adder >>= adder
24 |
25 | # We specify the type here as the call to box() could be for any
26 | # soon-to-be monadic value
27 | maybeResult3 = maybe.just(5) >>= adder >>= adder >>= adder
28 |
29 | # Test the resultant values of our computations
30 | echo($maybeResult1) # Outputs 'Just 40'
31 | echo($maybeResult2) # Outputs 'Nothing'
32 | echo($maybeResult3)
33 |
34 | # Here are some simpler results showing how to use it as a functor
35 | proc add5(x : int) : int =
36 | x + 5
37 |
38 | var
39 | value1 = maybe.fmap(maybe.just(10), add5)
40 | value2 = maybe.fmap(maybe.nothing[int](), add5)
41 |
42 | # Here we see that maybe handles the optional-ness for us, so
43 | # we can go along applying functions and obfuscate that detail
44 | echo($value1)
45 | echo($value2)
46 |
47 | var m = maybe.just(4)
48 |
49 | # macro usage
50 | maybeCase m:
51 | just x:
52 | var y = 3
53 | echo $(x+y)
54 | nothing:
55 | echo "no value"
56 |
57 | var nada = maybe.nothing[int]()
58 | maybeCase nada:
59 | just foo:
60 | echo "hi this is a value we cant print" & $foo
61 | nothing:
62 | echo "nope no value, nice try with your invalid access"
63 |
64 |
--------------------------------------------------------------------------------
/src/maybe/maybe.nim:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2014-2017, Josh Filstrup
2 | # Licensed under BSD3(see license.md file for details)
3 | #
4 | # An implementation of an exeptionless maybe type.
5 |
6 | import future
7 | import macros
8 |
9 | type
10 | ## Maybe provides a type which encapsulates the concept
11 | ## of null-ness in a safe way. This allows one to perform function
12 | ## calls over the underlying object, without forgetting to check if
13 | ## it is in a valid state.
14 | Maybe*[T] = object
15 | case valid*: bool
16 | of true: value : T
17 | of false: nil
18 |
19 | proc `==`*[T](m1: Maybe[T], m2: Maybe[T]) : bool =
20 | ## Equality for maybe objects
21 | if (m1.valid == false and m2.valid == false):
22 | return true
23 | elif (m1.valid == true and m2.valid == true):
24 | return m1.value == m2.value
25 | else:
26 | return false
27 |
28 | proc nothing*[T]() : Maybe[T] =
29 | ## Construct a maybe instance in the invalid state.
30 | Maybe[T](valid: false)
31 |
32 | proc just*[T](val: T) : Maybe[T] =
33 | ## Construct a maybe instance in the valid state.
34 | Maybe[T](valid: true, value: val)
35 |
36 | macro maybeCase*[T](m : Maybe[T], body : untyped) : untyped =
37 | ## A macro which provides a safe access pattern to
38 | ## the maybe type. This avoids the need to have a get function
39 | ## which throws an exception when its used improperly.
40 | ##
41 | ## This makes the following conversion
42 | ##
43 | ## maybeCase m:
44 | ## just x:
45 | ## expr1 using x
46 | ## nothing:
47 | ## expr2 that does not use x(trying to refer
48 | ## to x will not compile)
49 | ##
50 | ## converts to --->
51 | ##
52 | ## if m.isValid:
53 | ## eval expr using x where x is replaced with m.value
54 | ## else:
55 | ## eval expr2
56 | ##
57 | assert body.len == 2
58 | var justHead = body[0][0]
59 | var nothingHead = body[1][0]
60 |
61 | assert $justHead == "just",
62 | "\n\nFirst case must be of the form \njust ident: \n body"
63 | assert $nothingHead == "nothing",
64 | "\n\nSecond case must be of the form \nnothing: \n body"
65 |
66 | let
67 | mVal = genSym(nskLet)
68 | validExpr = newDotExpr(mVal, ident("valid"))
69 | valueExpr = newDotExpr(mVal, ident("value"))
70 | justClause = nnkStmtList.newTree(
71 | nnkLetSection.newTree(
72 | nnkIdentDefs.newTree(
73 | body[0][1],
74 | newEmptyNode(),
75 | valueExpr
76 | )
77 | ),
78 | body[0][2]
79 | )
80 | nothingClause = body[1][1]
81 |
82 | var ifExpr = newNimNode(nnkIfExpr)
83 | ifExpr.add(newNimNode(nnkElifExpr).add(validExpr, justClause))
84 | ifExpr.add(newNimNode(nnkElseExpr).add(nothingClause))
85 |
86 | result = nnkStmtList.newTree(
87 | nnkLetSection.newTree(
88 | nnkIdentDefs.newTree(
89 | mVal,
90 | newEmptyNode(),
91 | m
92 | )
93 | ),
94 | ifExpr
95 | )
96 |
97 | proc `$`*[T](m: Maybe[T]) : string =
98 | ## Convert a maybe instance to a string.
99 | if m.valid:
100 | result = "Just " & $m.value
101 | else:
102 | result = "Nothing"
103 |
104 | proc fmap*[T,U](m: Maybe[U], p: (U -> T) ) : Maybe[T] {. procvar .} =
105 | ## Used to map a function over a boxed value.
106 | ##
107 | ## Equivalent to (Functor f) => fmap :: (a->b) -> f a -> f b in Haskell.
108 | if m.valid:
109 | result = Maybe[T](valid: true, value: p(m.value))
110 | else:
111 | result = Maybe[T](valid: false)
112 |
113 | proc `>>=`*[T,U](m: Maybe[U], p: (U -> Maybe[T]) ) : Maybe[T] =
114 | ## Used for chaining monadic computations together.
115 | ##
116 | ## Analagous to bind(>>=) in Haskell.
117 | if m.valid:
118 | result = p(m.value)
119 | else:
120 | result = Maybe[T](valid: false)
121 |
--------------------------------------------------------------------------------
/src/tests/basic.nim:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2017, Josh Filstrup
2 | # Licensed under BSD3(see license.md file for details)
3 | #
4 |
5 | import ../maybe/maybe
6 |
7 | proc add5(x : int) : int =
8 | return x + 5
9 |
10 | proc testBasic() =
11 | var
12 | a = maybe.just(5)
13 | b = maybe.just(3)
14 | c = maybe.nothing[int]()
15 |
16 | a = maybe.fmap(a, add5)
17 | b = maybe.fmap(b, add5)
18 | c = maybe.fmap(c, add5)
19 |
20 | maybeCase a:
21 | just aInner:
22 | assert aInner == 10, "Value in 'a' should be 10"
23 | assert a.valid, "'a' should only be valid in this clause"
24 | a = maybe.just(15)
25 |
26 | nothing:
27 | assert false, "This clause should be unreachable"
28 |
29 | maybeCase b:
30 | just bInner:
31 | assert bInner == 8, "Value in 'b' should be 8"
32 | assert b.valid, "'b' shoud only be valid in this clause"
33 | nothing:
34 | assert false, "This clause should be unreachable"
35 |
36 | maybeCase c:
37 | just cInner:
38 | assert false, "This clause should be unreachable"
39 | nothing:
40 | assert c.valid == false, "'c' should only be invalid in this clause"
41 |
42 | assert $a == "Just 15"
43 |
44 | type Temp = enum
45 | VHot, Hot, Med, Cold, VCold
46 |
47 | proc testCustomTypes() =
48 | var
49 | a = maybe.just(VHot)
50 | b = maybe.just(Cold)
51 | c = maybe.nothing[Temp]()
52 | d = maybe.just(VHot)
53 |
54 | # Using maybes eq operator
55 | assert a != b, "The inner values should not be equal"
56 | assert a == d, "These inner values should be equal"
57 | assert c != b, "The inner values should not be equal"
58 |
59 | testBasic()
60 | testCustomTypes()
61 |
--------------------------------------------------------------------------------