├── .github └── workflows │ └── build.yml ├── .gitignore ├── buildverify.nim ├── docs └── index.html ├── license.md ├── maybe.nimble ├── readme.md └── src ├── examples └── example.nim ├── maybe └── maybe.nim └── tests └── basic.nim /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: macOS-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: Install 12 | run: brew install nim 13 | - name: Build 14 | run: nim c buildverify.nim 15 | - name: Test 16 | run: ./buildverify `which nim` 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **nimcache** 2 | ./src/example/example 3 | ./src/tests/test 4 | ./src/tests/basic 5 | .*.*.sw* 6 | buildverify 7 | build_log.txt 8 | src/examples/example 9 | src/maybe/maybe 10 | src/maybe/maybe.html 11 | src/maybe/maybe_.html 12 | src/tests/basic 13 | -------------------------------------------------------------------------------- /buildverify.nim: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017, Josh Filstrup 2 | # Licensed under BSD3(see license.md file for details) 3 | # 4 | # A script used by travis to verify a build, including: 5 | # - Build the code 6 | # - Run examples 7 | # - Run tests 8 | # - Generate docs(and diff them against the installed copy) 9 | import os 10 | import system 11 | 12 | var 13 | NIMCC = paramStr(1) 14 | ROOTDIR = getCurrentDir() 15 | 16 | proc runCmd(rawCmd: string, redirOutput: bool = true) = 17 | var cmd = rawCmd 18 | 19 | if redirOutput: 20 | cmd = cmd & " >> " & ROOTDIR & "/build_log.txt 2>&1" 21 | 22 | if execShellCmd(cmd) == 0: 23 | echo("✓ " & cmd) 24 | else: 25 | echo("✗ " & cmd) 26 | quit(QuitFailure) 27 | 28 | proc banner(msg : string) = 29 | echo("-------------------------------------") 30 | echo(msg) 31 | echo("-------------------------------------") 32 | 33 | proc runNimCC(fname : string) = 34 | runCmd(NIMCC & " c " & fname) 35 | 36 | runCmd("which " & NIMCC) 37 | banner("Nim compiler found!") 38 | 39 | setCurrentDir("src/maybe") 40 | echo(getCurrentDir()) 41 | runNimCC("maybe.nim") 42 | setCurrentDir("../examples/") 43 | runNimCC("example.nim") 44 | setCurrentDir("../tests/") 45 | runNimCC("basic.nim") 46 | banner("Compilation ran successfully!") 47 | 48 | setCurrentDir("../examples") 49 | runCmd("./example") 50 | banner("Examples ran successfully!") 51 | 52 | setCurrentDir("../tests/") 53 | runCmd("./basic") 54 | banner("Tests ran successfully!") 55 | 56 | setCurrentDir("../maybe/") 57 | # Clear out the timestamp lines 58 | #runCmd(NIMCC & " doc maybe.nim") 59 | #runCmd("sed \"/Generated:/d\" maybe.html > maybe_.html", false) 60 | #runCmd("diff maybe_.html ../../docs/index.html") 61 | #banner("Docgen ran successfully!") 62 | # 63 | ## Cleanup, mostly useful for local runs 64 | #setCurrentDir("../../") 65 | #runCmd("rm ./src/tests/basic") 66 | #runCmd("rm ./src/examples/example") 67 | #runCmd("rm ./src/maybe/maybe") 68 | #runCmd("rm ./src/maybe/maybe.html") 69 | #runCmd("rm ./src/maybe/maybe_.html") 70 | #banner("Cleanup ran successfully!") 71 | 72 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Module maybe 20 | 1165 | 1166 | 1167 | 1168 | 1193 | 1194 | 1195 | 1196 |
1197 |
1198 |

Module maybe

1199 |
1200 |
1201 |
1202 | Search: 1204 |
1205 |
1206 | Group by: 1207 | 1211 |
1212 | 1258 | 1259 |
1260 |
1261 |
1262 |

1263 |
1264 |

Imports

1265 |
1266 | future, macros 1267 |
1268 |
1269 |

Types

1270 |
1271 |
Maybe*[T] = object
1272 |   case valid*: bool
1273 |   of true: value: T
1274 |   of false: nil
1275 |   
1276 |
1277 | 1278 | 1279 |
1280 | 1281 |
1282 |
1283 |

Procs

1284 |
1285 |
proc `==`*[T](m1: Maybe[T]; m2: Maybe[T]): bool
1286 |
1287 | Equality for maybe objects 1288 | 1289 |
1290 |
proc nothing*[T](): Maybe[T]
1291 |
1292 | Construct a maybe instance in the invalid state. 1293 | 1294 |
1295 |
proc just*[T](val: T): Maybe[T]
1296 |
1297 | Construct a maybe instance in the valid state. 1298 | 1299 |
1300 |
proc `$`*[T](m: Maybe[T]): string
1301 |
1302 | Convert a maybe instance to a string. 1303 | 1304 |
1305 |
proc fmap*[T, U](m: Maybe[U]; p: (U -> T)): Maybe[T] {.
procvar
.}
1306 |
1307 |

Used to map a function over a boxed value.

1308 |

Equivalent to (Functor f) => fmap :: (a->b) -> f a -> f b in Haskell.

1309 | 1310 | 1311 |
1312 |
proc `>>=`*[T, U](m: Maybe[U]; p: (U -> Maybe[T])): Maybe[T]
1313 |
1314 |

Used for chaining monadic computations together.

1315 |

Analagous to bind(>>=) in Haskell.

1316 | 1317 | 1318 |
1319 | 1320 |
1321 |
1322 |

Macros

1323 |
1324 |
macro maybeCase*[T](m: Maybe[T]; body: untyped): untyped
1325 |
1326 |

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 | ![Build status badge](https://github.com/superfunc/maybe/workflows/CI/badge.svg) 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 | --------------------------------------------------------------------------------