├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── config.nims ├── examples └── httpactor │ └── main.nim ├── nimfp.nimble ├── src ├── fp.nim └── fp │ ├── concurrent.nim │ ├── either.nim │ ├── forcomp.nim │ ├── function.nim │ ├── futurem.nim │ ├── iterable.nim │ ├── kleisli.nim │ ├── list.nim │ ├── map.nim │ ├── mtransf.nim │ ├── option.nim │ ├── std │ └── jsonops.nim │ ├── stream.nim │ └── trym.nim └── tests └── fp ├── std └── test_jsonops.nim ├── test_all.nim ├── test_concurrent.nim ├── test_either.nim ├── test_forcomp.nim ├── test_forcomp_bind.nim ├── test_function.nim ├── test_futurem.nim ├── test_iterable.nim ├── test_list.nim ├── test_map.nim ├── test_mtransf.nim ├── test_option.nim ├── test_stream.nim └── test_trym.nim /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache 2 | bin 3 | build 4 | *.log 5 | .#*.* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | env: 4 | # Nim versions to test against 5 | - CHOOSENIM_CHOOSE_VERSION=devel 6 | - CHOOSENIM_CHOOSE_VERSION=0.19.4 7 | - CHOOSENIM_CHOOSE_VERSION=0.19.0 8 | 9 | matrix: 10 | allow_failures: 11 | # devel branch is often broken 12 | - env: CHOOSENIM_CHOOSE_VERSION=devel 13 | 14 | install: 15 | - curl https://nim-lang.org/choosenim/init.sh -sSf | sh -s -- -y 16 | - export PATH=~/.nimble/bin:$PATH 17 | - nimble update 18 | - nimble install -d -y 19 | 20 | before_script: 21 | - set -e 22 | - export PATH=~/.nimble/bin:$PATH 23 | - export CHOOSENIM_NO_ANALYTICS=1 24 | script: 25 | - nim test 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Anatoly Galiulin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nimfp [![nimble](https://raw.githubusercontent.com/yglukhov/nimble-tag/master/nimble.png)](https://github.com/yglukhov/nimble-tag) 2 | 3 | [![Build Status](https://travis-ci.org/vegansk/nimfp.svg?branch=master)](https://travis-ci.org/vegansk/nimfp) 4 | 5 | Nim functional programming library. Includes: 6 | 7 | * Option type [src/fp/option.nim](src/fp/option.nim) 8 | * List type [src/fp/list.nim](src/fp/list.nim) 9 | * Either type [src/fp/either.nim](src/fp/either.nim) 10 | * Map type [src/fp/map.nim](src/fp/map.nim) 11 | * Stream type [src/fp/stream.nim](src/fp/stream.nim) 12 | * Scala like _for comprehension_ and Haskell like _do notation_ support [src/fp/forcomp.nim](src/fp/forcomp.nim) 13 | 14 | While there is no documentation, you can see examples in the [tests/fp](tests/fp) directory. 15 | -------------------------------------------------------------------------------- /config.nims: -------------------------------------------------------------------------------- 1 | import ospaths 2 | 3 | srcdir = "src" 4 | 5 | template dep(name: untyped): untyped = 6 | exec "nim " & astToStr(name) 7 | 8 | proc buildBase(debug: bool, bin: string, src: string) = 9 | switch("out", (thisDir() & "/" & bin).toExe) 10 | --nimcache: build 11 | --threads:on 12 | if not debug: 13 | --forceBuild 14 | --define: release 15 | --opt: size 16 | else: 17 | --define: debug 18 | --debuginfo 19 | --debugger: native 20 | --linedir: on 21 | --stacktrace: on 22 | --linetrace: on 23 | --verbosity: 1 24 | 25 | --path: src 26 | --path: srcdir 27 | 28 | setCommand "c", src 29 | 30 | proc test(name: string) = 31 | if not dirExists "bin": 32 | mkDir "bin" 33 | --run 34 | buildBase true, "bin/test_" & name, "tests/fp/test_" & name 35 | 36 | proc example(bin: string, name: string) = 37 | if not dirExists "bin": 38 | mkDir "bin" 39 | --run 40 | buildBase false, "bin/ex_" & bin, "examples" / name 41 | 42 | task test_int, "Run all tests - internal": 43 | test "all" 44 | 45 | task test, "Run all tests": 46 | dep test_int 47 | 48 | task test_either, "Run Either tests": 49 | test "either" 50 | 51 | task test_list, "Run List tests": 52 | test "list" 53 | 54 | task test_option, "Run Option tests": 55 | test "option" 56 | 57 | task test_forcomp, "Run for comprehension tests": 58 | test "forcomp" 59 | 60 | task test_concurrent, "Run concurrent tests": 61 | test "concurrent" 62 | 63 | task test_futurem, "Run futurem tests": 64 | test "futurem" 65 | 66 | task ex_httpactor, "Run httpactor example": 67 | example "httpactor", "httpactor" / "main" 68 | -------------------------------------------------------------------------------- /examples/httpactor/main.nim: -------------------------------------------------------------------------------- 1 | import boost/http/asynchttpserver, 2 | asyncdispatch, 3 | asyncnet 4 | 5 | const useActors = true 6 | 7 | proc fac(i: int64): int64 = 8 | if i == 0: 9 | result = 1'i64 10 | else: 11 | result = i * fac(i - 1'i64) 12 | 13 | when useActors: 14 | import ../../src/fp 15 | 16 | # For asynchttpserver we can use only sequental or async strategies here 17 | let httpActor = newActor[Request](asyncStrategy, dequeQueue) do(req: Request) -> void: 18 | discard req.respond(Http200, "Factorial of 20 is " & $fac(20'i64)) 19 | let httpActorPtr = httpActor.unsafeAddr 20 | 21 | proc handler(req: Request) {.async.} = 22 | httpActorPtr ! req 23 | else: 24 | proc handler(req: Request) {.async.} = 25 | await req.respond(Http200, "Factorial of 20 is " & $fac(20'i64)) 26 | 27 | var server = newAsyncHttpServer() 28 | 29 | asyncCheck server.serve(Port(5555), handler) 30 | 31 | runForever() 32 | -------------------------------------------------------------------------------- /nimfp.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | version = "0.4.5" 3 | author = "Anatoly Galiulin " 4 | description = "Nim functional programming library" 5 | license = "MIT" 6 | 7 | srcDir = "src" 8 | 9 | # Deps 10 | requires "nim >= 0.19.0", "nimboost >= 0.5.5", "classy >= 0.0.3" 11 | -------------------------------------------------------------------------------- /src/fp.nim: -------------------------------------------------------------------------------- 1 | import ./fp/option, 2 | ./fp/list, 3 | ./fp/either, 4 | ./fp/trym, 5 | ./fp/futurem, 6 | ./fp/concurrent, 7 | ./fp/map, 8 | ./fp/function, 9 | ./fp/stream, 10 | ./fp/forcomp, 11 | ./fp/mtransf, 12 | ./fp/iterable, 13 | 14 | ./fp/std/jsonops 15 | 16 | export option, 17 | list, 18 | either, 19 | trym, 20 | futurem, 21 | concurrent, 22 | map, 23 | function, 24 | stream, 25 | forcomp, 26 | mtransf, 27 | iterable, 28 | 29 | jsonops 30 | -------------------------------------------------------------------------------- /src/fp/concurrent.nim: -------------------------------------------------------------------------------- 1 | import boost/types, 2 | boost/typeutils, 3 | ./futurem, 4 | ./option, 5 | ./trym, 6 | sugar, 7 | deques 8 | 9 | const hasThreads = compileOption("threads") 10 | when hasThreads: 11 | import threadpool, locks 12 | 13 | type Strategy*[A] = proc(a: () -> A): () -> A 14 | 15 | proc sequentalStrategy*[A](a: () -> A): () -> A = 16 | let r = a() 17 | result = () => r 18 | 19 | when hasThreads: 20 | proc spawnStrategy*[A](a: proc(): A {.gcsafe.}): () -> A = 21 | let r = spawn a() 22 | result = proc(): auto = 23 | return ^r 24 | 25 | proc asyncStrategy*[A](a: () -> A): () -> A = 26 | let f = newFuture[A](a) 27 | result = proc(): auto = 28 | f.run.get 29 | 30 | type Handler*[A] = proc(v: A) {.gcsafe.} 31 | type HandlerS*[S,A] = proc(s: S, v: A): S {.gcsafe.} 32 | type ErrorHandler* = proc(e: ref Exception): void 33 | 34 | let rethrowError*: ErrorHandler = proc(e: ref Exception) = 35 | raise e 36 | 37 | # Queue implementation 38 | type Queue*[A] = ref object of RootObj 39 | init*: proc(q: var Queue[A]) 40 | put*: proc(q: var Queue[A], a: A) 41 | get*: proc(q: var Queue[A]): Option[A] 42 | close*: proc(q: var Queue[A]) 43 | 44 | type QueueImpl*[A] = proc(): Queue[A] 45 | 46 | when hasThreads: 47 | type ChannelQueue[A] = ref object of Queue[A] 48 | ch: Channel[A] 49 | 50 | proc channelQueue*[A](): Queue[A] {.procvar.} = 51 | result = new(ChannelQueue[A]) 52 | result.init = proc(q: var Queue[A]) = 53 | let chq = ChannelQueue[A](q) 54 | chq.ch.open 55 | result.put = proc(q: var Queue[A], a: A) = 56 | let chq = ChannelQueue[A](q) 57 | chq.ch.send(a) 58 | result.get = proc(q: var Queue[A]): Option[A] = 59 | let chq = ChannelQueue[A](q) 60 | let d = chq.ch.tryRecv() 61 | if d[0]: 62 | d[1].some 63 | else: 64 | A.none 65 | result.close = proc(q: var Queue[A]) = 66 | cast[ChannelQueue[A]](q).ch.close 67 | 68 | type DequeQueue[A] = ref object of Queue[A] 69 | q: Deque[A] 70 | 71 | proc dequeQueue*[A](): Queue[A] {.procvar.} = 72 | result = new(DequeQueue[A]) 73 | result.init = proc(q: var Queue[A]) = 74 | ((DequeQueue[A])q).q = initDeque[A]() 75 | result.put = proc(q: var Queue[A], a: A) = 76 | ((DequeQueue[A])q).q.addLast(a) 77 | result.get = proc(q: var Queue[A]): Option[A] = 78 | if ((DequeQueue[A])q).q.len > 0: 79 | ((DequeQueue[A])q).q.popFirst().some 80 | else: 81 | A.none 82 | result.close = proc(q: var Queue[A]) = 83 | discard 84 | 85 | type Actor*[A] = ref object 86 | strategy: Strategy[Unit] 87 | handler: Handler[A] 88 | onError: ErrorHandler 89 | queue: Queue[A] 90 | activeReader: bool 91 | 92 | proc releaseActor[A](a: Actor[A]) = 93 | a.queue.close(a.queue) 94 | 95 | proc newActor*[A](strategy: Strategy[Unit], queueImpl: QueueImpl[A], handler: Handler[A], onError = rethrowError): Actor[A] = 96 | new(result, releaseActor[A]) 97 | result.strategy = strategy 98 | result.handler = handler 99 | result.onError = onError 100 | result.queue = queueImpl() 101 | result.queue.init(result.queue) 102 | result.activeReader = false 103 | 104 | proc processQueue[A](a: ptr Actor[A]) = 105 | while true: 106 | let msg = a[].queue.get(a[].queue) 107 | if msg.isEmpty: 108 | discard cas(a[].activeReader.addr, true, false) 109 | break 110 | try: 111 | a[].handler(msg.get) 112 | except: 113 | a[].onError(getCurrentException()) 114 | 115 | proc send*[A](a: Actor[A]|ptr Actor[A], v: A) = 116 | let ap = when a is ptr: a else: a.unsafeAddr 117 | ap[].queue.put(ap[].queue, v) 118 | if cas(ap[].activeReader.addr, false, true): 119 | discard a.strategy(() => (ap.processQueue(); ()))() 120 | 121 | proc `!`*[A](a: Actor[A]|ptr Actor[A], v: A) = 122 | a.send(v) 123 | 124 | type ActorS*[S,A] = ref object 125 | strategy: Strategy[Unit] 126 | handler: HandlerS[S,A] 127 | onError: ErrorHandler 128 | queue: Queue[A] 129 | state: Queue[S] 130 | activeReader: bool 131 | 132 | proc releaseActorS[S,A](a: ActorS[S,A]) = 133 | a.state.close(a.state) 134 | a.queue.close(a.queue) 135 | 136 | proc newActorS*[S,A](strategy: Strategy[Unit], queueImpl: QueueImpl[A], stateQueueImpl: QueueImpl[S], initialState: S, handler: HandlerS[S,A], onError = rethrowError): ActorS[S,A] = 137 | new(result, releaseActorS[S,A]) 138 | result.strategy = strategy 139 | result.handler = handler 140 | result.onError = onError 141 | result.queue = queueImpl() 142 | result.queue.init(result.queue) 143 | result.state = stateQueueImpl() 144 | result.state.init(result.state) 145 | result.state.put(result.state, initialState) 146 | result.activeReader = false 147 | 148 | proc processQueue[S,A](a: ptr ActorS[S,A]) = 149 | while true: 150 | let msg = a[].queue.get(a[].queue) 151 | if msg.isEmpty: 152 | discard cas(a[].activeReader.addr, true, false) 153 | break 154 | let state = a[].state.get(a[].state) 155 | assert state.isDefined 156 | try: 157 | a[].state.put(a[].state, a[].handler(state.get, msg.get)) 158 | except: 159 | a[].onError(getCurrentException()) 160 | # Set old state 161 | a[].state.put(a[].state, state.get) 162 | 163 | proc send*[S,A](a: ActorS[S,A]|ptr ActorS[S,A], v: A) = 164 | let ap = when a is ptr: a else: a.unsafeAddr 165 | ap[].queue.put(ap[].queue, v) 166 | if cas(ap[].activeReader.addr, false, true): 167 | discard a.strategy(() => (ap.processQueue(); ()))() 168 | 169 | proc `!`*[S,A](a: ActorS[S,A]|ptr ActorS[S,A], v: A) = 170 | a.send(v) 171 | -------------------------------------------------------------------------------- /src/fp/either.nim: -------------------------------------------------------------------------------- 1 | import sugar, 2 | boost/types, 3 | classy, 4 | ./list, 5 | ./option, 6 | ./kleisli, 7 | macros, 8 | ./function 9 | 10 | {.experimental.} 11 | 12 | type 13 | EitherKind = enum 14 | ekLeft, ekRight 15 | Either*[E,A] = ref object 16 | ## Either ADT 17 | case kind: EitherKind 18 | of ekLeft: 19 | lValue: E 20 | else: 21 | rValue: A 22 | EitherE*[A] = Either[ref Exception, A] 23 | EitherS*[A] = Either[string, A] 24 | 25 | proc Left*[E,A](value: E): Either[E,A] = 26 | ## Constructs left value 27 | Either[E,A](kind: ekLeft, lValue: value) 28 | 29 | proc Right*[E,A](value: A): Either[E,A] = 30 | ## Constructs right value 31 | Either[E,A](kind: ekRight, rValue: value) 32 | 33 | proc left*[E,A](value: E, d: A): Either[E,A] = 34 | ## Constructs left value 35 | Left[E,A](value) 36 | 37 | proc left*[E](value: E, A: typedesc): Either[E,A] = 38 | ## Constructs left value 39 | Left[E,A](value) 40 | 41 | proc right*[E,A](value: A, d: E): Either[E,A] = 42 | ## Constructs right value 43 | Right[E,A](value) 44 | 45 | proc right*[A](value: A, E: typedesc): Either[E,A] = 46 | ## Constructs right value 47 | Right[E,A](value) 48 | 49 | proc rightE*[A](value: A): EitherE[A] = 50 | ## Constructs right value 51 | Right[ref Exception,A](value) 52 | 53 | proc rightS*[A](value: A): EitherS[A] = 54 | ## Constructs right value 55 | Right[string,A](value) 56 | 57 | proc isLeft*[E,A](e: Either[E,A]): bool = 58 | ## Checks if `e` contains left value 59 | e.kind == ekLeft 60 | 61 | proc isRight*[E,A](e: Either[E,A]): bool = 62 | ## Checks if `e` contains right value 63 | e.kind == ekRight 64 | 65 | proc `$`(e: ref Exception): string = 66 | e.msg 67 | 68 | proc errorMsg*[A](e: EitherE[A]): string = 69 | ## Returns error message or empty string 70 | if e.isLeft: 71 | e.lValue.msg 72 | else: 73 | "" 74 | 75 | proc errorMsg*[A](e: EitherS[A]): string = 76 | ## Returns error message or empty string 77 | if e.isLeft: 78 | e.lValue 79 | else: 80 | "" 81 | 82 | proc `$`*[E,A](e: Either[E,A]): string = 83 | ## Returns string representation of `e` 84 | if e.isLeft: 85 | "Left(" & $e.lValue & ")" 86 | else: 87 | "Right(" & $e.rValue & ")" 88 | 89 | proc `==`*[E,A](x, y: Either[E,A]): bool = 90 | ## Compares two values 91 | let r = (x.isLeft, y.isLeft) 92 | if r == (true, true): 93 | x.lValue == y.lValue 94 | elif r == (false, false): 95 | x.rValue == y.rValue 96 | else: 97 | false 98 | 99 | proc map*[E,A,B](e: Either[E,A], f: A -> B): Either[E,B] = 100 | ## Maps right value of `e` via `f` or returns left value 101 | if e.isLeft: e.lValue.left(B) else: f(e.rValue).right(E) 102 | 103 | proc mapLeft*[E,F,A](e: Either[E,A], f: E -> F): Either[F,A] = 104 | ## Maps left value of `e` via `f` or returns right value 105 | if e.isRight: e.rValue.right(F) else: f(e.lValue).left(A) 106 | 107 | proc flatMap*[E,A,B](e: Either[E,A], f: A -> Either[E,B]): Either[E,B] = 108 | ## Returns the result of applying `f` to the right value or returns left value 109 | if e.isLeft: e.lValue.left(B) else: f(e.rValue) 110 | 111 | proc get*[E,A](e: Either[E,A]): A = 112 | ## Returns either's value if it is right, or fail 113 | doAssert(e.isRight, "Can't get Either's value") 114 | e.rValue 115 | 116 | proc getLeft*[E,A](e: Either[E,A]): E = 117 | ## Returns either's value if it is left, or fail 118 | doAssert(e.isLeft, "Can't get Either's left value") 119 | e.lValue 120 | 121 | proc getOrElse*[E,A](e: Either[E,A], d: A): A = 122 | ## Return right value or `d` 123 | if e.isRight: e.rValue else: d 124 | 125 | proc getOrElse*[E,A](e: Either[E,A], f: void -> A): A = 126 | ## Return right value or result of `f` 127 | if e.isRight: e.rValue else: f() 128 | 129 | proc orElse*[E,A](e: Either[E,A], d: Either[E,A]): Either[E,A] = 130 | ## Returns `e` if it contains right value, or `d` 131 | if e.isRight: e else: d 132 | 133 | proc orElse*[E,A](e: Either[E,A], f: void -> Either[E,A]): Either[E,A] = 134 | ## Returns `e` if it contains right value, or the result of `f()` 135 | if e.isRight: e else: f() 136 | 137 | proc map2*[E,A,B,C](a: Either[E,A], b: Either[E,B], f: (A, B) -> C): Either[E,C] = 138 | ## Maps 2 `Either` values via `f` 139 | a.flatMap((a: A) => b.map((b: B) => f(a,b))) 140 | 141 | proc map2F*[A, B, C, E]( 142 | ma: Either[E, A], 143 | mb: () -> Either[E, B], 144 | f: (A, B) -> C 145 | ): Either[E, C] = 146 | ## Maps 2 `Either` values via `f`. Lazy in second argument. 147 | ma.flatMap((a: A) => mb().map((b: B) => f(a, b))) 148 | 149 | proc join*[E,A](e: Either[E, Either[E,A]]): Either[E,A] = 150 | ## Flattens Either's value 151 | e.flatMap(id) 152 | 153 | when compiles(getCurrentException()): 154 | proc tryE*[A](f: () -> A): EitherE[A] = 155 | ## Transforms exception to EitherE type 156 | (try: f().rightE except: getCurrentException().left(A)) 157 | 158 | proc flatTryE*[A](f: () -> EitherE[A]): EitherE[A] = 159 | ## Transforms exception to EitherE type 160 | (try: f() except: getCurrentException().left(A)) 161 | 162 | template tryETImpl(body: typed): untyped = 163 | when type(body) is EitherE: 164 | flatTryE do() -> auto: 165 | body 166 | else: 167 | tryE do() -> auto: 168 | body 169 | 170 | macro tryET*(body: untyped): untyped = 171 | ## Combination of flatTryS and tryS 172 | var b = if body.kind == nnkDo: body[^1] else: body 173 | result = quote do: 174 | tryETImpl((block: 175 | `b` 176 | )) 177 | 178 | proc tryS*[A](f: () -> A): EitherS[A] = 179 | ## Transforms exception to EitherS type 180 | (try: f().rightS except: getCurrentExceptionMsg().left(A)) 181 | 182 | proc flatTryS*[A](f: () -> EitherS[A]): EitherS[A] = 183 | ## Transforms exception to EitherS type 184 | (try: f() except: getCurrentExceptionMsg().left(A)) 185 | 186 | template trySTImpl(body: untyped): untyped = 187 | when type(body) is EitherS: 188 | flatTryS do() -> auto: 189 | body 190 | else: 191 | tryS do() -> auto: 192 | body 193 | 194 | macro tryST*(body: untyped): untyped = 195 | ## Combination of flatTryS and tryS 196 | var b = if body.kind == nnkDo: body[^1] else: body 197 | result = quote do: 198 | trySTImpl((block: 199 | `b` 200 | )) 201 | 202 | proc run*[E,A](e: Either[E,A]): A = 203 | ## Returns right value or raises the error contained 204 | ## in the left part 205 | if e.isRight: 206 | result = e.get 207 | else: 208 | when E is ref Exception: 209 | raise e.getLeft 210 | else: 211 | raise newException(Exception, $e.getLeft) 212 | 213 | proc fold*[E,A,B](v: Either[E, A], ifLeft: E -> B, ifRight: A -> B): B = 214 | ## Applies `ifLeft` if `v` is left, or `ifRight` if `v` is right 215 | if v.isLeft: 216 | ifLeft(v.lValue) 217 | else: 218 | ifRight(v.rValue) 219 | 220 | proc traverse*[E, A, B](xs: List[A], f: A -> Either[E, B]): Either[E, List[B]] = 221 | ## Transforms the list of `A` into the list of `B` f via `f` only if 222 | ## all results of applying `f` are `Right`. 223 | ## Doesnt execute `f` for elements after the first `Left` is encountered. 224 | 225 | # Implementation with foldRightF breaks semcheck when inferring 226 | # gcsafe. So we have to keep this basic. 227 | # Also, since tail calls are not guaranteed, we use a loop instead 228 | # of recursion. 229 | 230 | var rest = xs 231 | var acc = Nil[B]() 232 | while not rest.isEmpty: 233 | let headRes = f(rest.head) 234 | if headRes.isLeft: 235 | return headRes.getLeft.left(List[B]) 236 | acc = Cons(headRes.get, acc) 237 | rest = rest.tail 238 | acc.reverse.right(E) 239 | 240 | proc sequence*[E,A](xs: List[Either[E,A]]): Either[E,List[A]] = 241 | xs.traverse((x: Either[E,A]) => x) 242 | 243 | proc traverseU*[E,A,B](xs: List[A], f: A -> Either[E,B]): Either[E,Unit] = 244 | var rest = xs 245 | while not rest.isEmpty: 246 | let headRes = f(rest.head) 247 | if headRes.isLeft: 248 | return headRes.getLeft.left(Unit) 249 | rest = rest.tail 250 | ().right(E) 251 | 252 | proc sequenceU*[E,A](xs: List[Either[E,A]]): Either[E,Unit] = 253 | xs.traverseU((x: Either[E,A]) => x) 254 | 255 | proc traverse*[E, A, B]( 256 | opt: Option[A], 257 | f: A -> Either[E, B] 258 | ): Either[E, Option[B]] = 259 | if opt.isEmpty: 260 | B.none.right(E) 261 | else: 262 | f(opt.get).map((b: B) => b.some) 263 | 264 | proc sequence*[E, A](oea: Option[Either[E, A]]): Either[E, Option[A]] = 265 | oea.traverse((ea: Either[E, A]) => ea) 266 | 267 | proc traverseU*[E, A, B]( 268 | opt: Option[A], 269 | f: A -> Either[E, B] 270 | ): Either[E, Unit] = 271 | if opt.isEmpty: 272 | ().right(E) 273 | else: 274 | f(opt.get).fold( 275 | (e: E) => e.left(Unit), 276 | (v: B) => ().right(E) 277 | ) 278 | 279 | proc sequenceU*[E, A](oea: Option[Either[E, A]]): Either[E, Unit] = 280 | oea.traverseU((ea: Either[E, A]) => ea) 281 | 282 | proc forEach*[E,A](a: Either[E,A], f: A -> void): void = 283 | ## Applies `f` to the Either's value if it's right 284 | if a.isRight: 285 | f(a.get) 286 | 287 | proc cond*[E,A](flag: bool, a: A, e: E): Either[E,A] = 288 | ## If the condition is satisfied, returns a else returns e 289 | if flag: a.right(E) else: e.left(A) 290 | 291 | proc condF*[E,A](flag: bool, a: () -> A, e: () -> E): Either[E,A] = 292 | ## If the condition is satisfied, returns a else returns e 293 | if flag: a().right(E) else: e().left(A) 294 | 295 | proc asEither*[E,A](o: Option[A], e: E): Either[E,A] = 296 | ## Converts Option to Either type 297 | condF(o.isDefined, () => o.get, () => e) 298 | 299 | proc asEitherF*[E,A](o: Option[A], e: () -> E): Either[E,A] = 300 | ## Converts Option to Either type 301 | condF(o.isDefined, () => o.get, e()) 302 | 303 | proc asOption*[E,A](e: Either[E,A]): Option[A] = 304 | ## Converts Either to Option type 305 | if e.isRight: e.get.some 306 | else: A.none 307 | 308 | proc flip*[E,A](e: Either[E,A]): Either[A,E] = 309 | ## Flips Either's left and right parts 310 | if e.isRight: e.get.left(E) 311 | else: e.getLeft.right(A) 312 | 313 | proc whenF*[E](flag: bool, body: () -> Either[E, Unit]): Either[E, Unit] = 314 | ## Executes `body` if `flag` is true 315 | if flag: body() 316 | else: ().right(E) 317 | 318 | proc whileM*[E,A](a: A, cond: A -> Either[E, bool], body: A -> Either[E, A]): Either[E,A] = 319 | ## Executes the body while `cond` returns ``true.right(E)`` 320 | var acc = a 321 | while true: 322 | let condRes = cond(acc) 323 | if condRes.isLeft: 324 | return condRes.getLeft.left(A) 325 | elif not condRes.get: 326 | return acc.right(E) 327 | result = body(acc) 328 | if result.isLeft: 329 | return 330 | acc = result.get 331 | 332 | proc whileM*[E](cond: () -> Either[E, bool], body: () -> Either[E, Unit]): Either[E, Unit] = 333 | ## Executes the body while `cond` returns ``true.right(E)`` 334 | whileM[E, Unit]((), _ => cond(), _ => body()) 335 | 336 | proc toUnit*[E,A](e: Either[E,A]): Either[E, Unit] = 337 | ## Discards the Either's value 338 | e.flatMap((_:A) => ().right(E)) 339 | 340 | proc bracket*[E,A,B]( 341 | acquire: () -> Either[E,A], 342 | release: A -> Either[E, Unit], 343 | body: A -> Either[E,B] 344 | ): Either[E,B] = 345 | ## Acquires the resource with `acquire`, then executes `body` 346 | ## and then releases it with `release`. 347 | acquire().flatMap do (a: A) -> auto: 348 | let r = body(a) 349 | release(a).flatMap((_: Unit) => r) 350 | 351 | proc catch*[E1,E2,A]( 352 | body: Either[E1,A], 353 | handler: E1 -> Either[E2,A] 354 | ): Either[E2,A] = 355 | ## Runs `body`. If it fails, execute `handler` with the 356 | ## value of exception 357 | if body.isLeft: 358 | handler(body.getLeft) 359 | else: 360 | body.get.right(E2) 361 | 362 | proc asEitherS*[E,A](e: Either[E,A]): EitherS[A] = 363 | ## Converts Either to EitherS 364 | e.mapLeft((err: E) => $err) 365 | 366 | proc asEitherE*[E,A](e: Either[E,A]): EitherE[A] = 367 | ## Converts Either to EitherE 368 | e.mapLeft((err: E) => newException(Exception, $err)) 369 | 370 | proc asList*[E,A](e: Either[E,A]): List[A] = 371 | ## Converts Either to List 372 | if e.isLeft: 373 | Nil[A]() 374 | else: 375 | asList(e.get) 376 | 377 | template elemType*(v: Either): typedesc = 378 | ## Part of ``do notation`` contract 379 | type(v.get) 380 | 381 | proc point*[E,A](v: A, e: typedesc[Either[E,A]]): Either[E,A] = 382 | v.right(E) 383 | 384 | instance KleisliInst, E => Either[E,_], exporting(_) 385 | 386 | -------------------------------------------------------------------------------- /src/fp/forcomp.nim: -------------------------------------------------------------------------------- 1 | import macros, strutils, sequtils 2 | 3 | type ForComprehension = distinct object 4 | type ForComprehensionYield = distinct object 5 | 6 | var fc*: ForComprehension 7 | 8 | proc parseExpression(node: NimNode): NimNode {.compileTime.} = 9 | if node.len == 3: 10 | return node[2] 11 | elif node.len == 4 and 12 | node[2].kind == nnkIdent and 13 | node[3].kind in {nnkStmtList, nnkDo}: 14 | return newNimNode( 15 | nnkCall 16 | ).add(node[2]).add(node[3]) 17 | else: 18 | echo node.toStrLit 19 | echo treeRepr(node) 20 | error("Can't create expression from node", node) 21 | 22 | proc forCompImpl(yieldResult: bool, comp: NimNode): NimNode {.compileTime.} = 23 | expectLen(comp, 3) 24 | expectKind(comp, nnkInfix) 25 | expectKind(comp[0], nnkIdent) 26 | assert($comp[0] == "|") 27 | 28 | result = comp[1] 29 | var yieldNow = yieldResult 30 | 31 | for i in countdown(comp[2].len-1, 0): 32 | var x = comp[2][i] 33 | if x.kind != nnkInfix or $x[0] != "<-": 34 | x = newNimNode(nnkInfix).add(ident"<-").add(ident"_").add(x) 35 | expectMinLen(x, 3) 36 | let expr = parseExpression(x) 37 | var iDef: NimNode 38 | var iType: NimNode 39 | if x[1].kind == nnkIdent: 40 | iDef = x[1] 41 | iType = newCall(ident"elemType", expr) 42 | else: 43 | expectLen(x[1], 1) 44 | expectMinLen(x[1][0], 2) 45 | expectKind(x[1][0][0], nnkIdent) 46 | iDef = x[1][0][0] 47 | iType = x[1][0][1] 48 | let lmb = newProc(params = @[ident"auto", newIdentDefs(iDef, iType)], body = result, procType = nnkLambda) 49 | let p = newNimNode(nnkPragma) 50 | p.add(ident"closure") 51 | lmb[4] = p 52 | if yieldNow: 53 | yieldNow = false 54 | result = quote do: 55 | (`expr`).map(`lmb`) 56 | else: 57 | result = quote do: 58 | (`expr`).flatmap(`lmb`) 59 | 60 | macro `[]`*(fc: ForComprehension, comp: untyped): untyped = 61 | ## For comprehension with list comprehension like syntax. 62 | ## Example: 63 | ## 64 | ## .. code-block:: nim 65 | ## 66 | ## let res = fc[(y*100).some | ( 67 | ## (x: int) <- 1.some, 68 | ## (y: int) <- (x + 3).some 69 | ## )] 70 | ## assert(res == 400.some) 71 | ## 72 | ## The only requirement for the user is to implement `foldMap`` function for the type 73 | ## 74 | forCompImpl(false, comp) 75 | 76 | macro act*(comp: untyped): untyped = 77 | ## For comprehension with Haskell ``do notation`` like syntax. 78 | ## Example: 79 | ## 80 | ## .. code-block:: nim 81 | ## 82 | ## let res = act: 83 | ## (x: int) <- 1.some, 84 | ## (y: int) <- (x + 3).some 85 | ## (y*100).some 86 | ## assert(res == 400.some) 87 | ## 88 | ## The only requirement for the user is to implement `foldMap`` function for the type 89 | ## 90 | expectKind comp, {nnkStmtList, nnkDo} 91 | let stmts = if comp.kind == nnkStmtList: comp else: comp.findChild(it.kind == nnkStmtList) 92 | expectMinLen(stmts, 2) 93 | let op = newNimNode(nnkInfix) 94 | op.add(ident"|") 95 | let res = stmts[stmts.len-1] 96 | var yieldResult = false 97 | if res.kind == nnkYieldStmt: 98 | yieldResult = true 99 | op.add(res[0].copyNimTree) 100 | else: 101 | op.add(res.copyNimTree) 102 | let par = newNimNode(nnkPar) 103 | op.add(par) 104 | for i in 0..<(stmts.len-1): 105 | par.add(stmts[i].copyNimTree) 106 | 107 | forCompImpl(yieldResult, op) 108 | -------------------------------------------------------------------------------- /src/fp/function.nim: -------------------------------------------------------------------------------- 1 | import sugar, 2 | classy 3 | 4 | type 5 | Func0*[R] = () -> R 6 | Func1*[T1,R] = (T1) -> R 7 | Func2*[T1,T2,R] = (T1,T2) -> R 8 | Curried2*[T1,T2,R] = T1 -> (T2 -> R) 9 | Func3*[T1,T2,T3,R] = (T1,T2,T3) -> R 10 | Curried3*[T1,T2,T3,R] = T1 -> (T2 -> (T3 -> R)) 11 | Func4*[T1,T2,T3,T4,R] = (T1,T2,T3,T4) -> R 12 | Curried4*[T1,T2,T3,T4,R] = T1 -> (T2 -> (T3 -> (T4 -> R))) 13 | Func5*[T1,T2,T3,T4,T5,R] = (T1,T2,T3,T4,T5) -> R 14 | Func6*[T1,T2,T3,T4,T5,T6,R] = (T1,T2,T3,T4,T5,T6) -> R 15 | Func7*[T1,T2,T3,T4,T5,T6,T7,R] = (T1,T2,T3,T4,T5,T6,T7) -> R 16 | Func8*[T1,T2,T3,T4,T5,T6,T7,T8,R] = (T1,T2,T3,T4,T5,T6,T7,T8) -> R 17 | 18 | proc memoize*[T](f: Func0[T]): Func0[T] = 19 | ## Create function's value cache (not thread safe yet) 20 | var hasValue = false 21 | var value: T 22 | result = proc(): T = 23 | if not hasValue: 24 | hasValue = true 25 | value = f() 26 | return value 27 | 28 | # Func2 29 | template curried*[T1,T2,R](f: Func2[T1,T2,R]): Curried2[T1,T2,R] = 30 | (x1: T1) => ((x2: T2) => f(x1, x2)) 31 | 32 | template uncurried2*[T1,T2,R](f: Curried2[T1,T2,R]): Func2[T1,T2,R] = 33 | (x1: T1, x2: T2) => f(x1)(x2) 34 | 35 | # Func3 36 | template curried*[T1,T2,T3,R](f: Func3[T1,T2,T3,R]): Curried3[T1,T2,T3,R] = 37 | (x1: T1) => ((x2: T2) => ((x3: T3) => f(x1, x2, x3))) 38 | 39 | template curried1*[T1,T2,T3,R](f: Func3[T1,T2,T3,R]): T1 -> Func2[T2,T3,R] = 40 | (x1: T1) => ((x2: T2, x3: T3) => f(x1, x2, x3)) 41 | 42 | template uncurried3*[T1,T2,T3,R](f: Curried3[T1,T2,T3,R]): Func3[T1,T2,T3,R] = 43 | (x1: T1, x2: T2, x3: T3) => f(x1)(x2)(x3) 44 | 45 | # Func4 46 | template curried*[T1,T2,T3,T4,R](f: Func4[T1,T2,T3,T4,R]): Curried4[T1,T2,T3,T4,R] = 47 | (x1: T1) => ((x2: T2) => ((x3: T3) => ((x4: T4) => f(x1, x2, x3, x4)))) 48 | 49 | template curried1*[T1,T2,T3,T4,R](f: Func4[T1,T2,T3,T4,R]): T1 -> Func3[T2,T3,T4,R] = 50 | (x1: T1) => ((x2: T2, x3: T3, x4: T4) => f(x1, x2, x3, x4)) 51 | 52 | template uncurried4*[T1,T2,T3,T4,R](f: Curried4[T1,T2,T3,T4,R]): Func4[T1,T2,T3,T4,R] = 53 | (x1: T1, x2: T2, x3: T3, x4: T4) => f(x1)(x2)(x3)(x4) 54 | 55 | proc compose*[A,B,C](f: B -> C, g: A -> B): A -> C = 56 | (x: A) => f(g(x)) 57 | 58 | proc andThen*[A,B,C](f: A -> B, g: B -> C): A -> C = 59 | (x: A) => g(f(x)) 60 | 61 | proc `<<<`*[A,B,C](f: B -> C, g: A -> B): A -> C = 62 | compose(f, g) 63 | 64 | proc `>>>`*[A,B,C](f: A -> B, g: B -> C): A -> C = 65 | andThen(f, g) 66 | 67 | proc flip*[A,B,C](f: Func2[B,A,C]): Func2[A,B,C] = 68 | (a: A, b: B) => f(b, a) 69 | 70 | proc flip*[A,B,C](f: Curried2[B,A,C]): Curried2[A,B,C] = 71 | (a: A) => ((b: B) => f(b)(a)) 72 | 73 | proc id*[A](v: A): A {.procvar.} = v 74 | -------------------------------------------------------------------------------- /src/fp/futurem.nim: -------------------------------------------------------------------------------- 1 | import sugar, 2 | asyncdispatch, 3 | ./trym, 4 | ./option, 5 | classy, 6 | ./kleisli, 7 | boost/types, 8 | macros, 9 | ./function, 10 | boost/typeclasses 11 | 12 | export asyncdispatch 13 | 14 | proc value*(f: Future[void]): Option[Try[Unit]] = 15 | if f.finished: 16 | if f.failed: 17 | f.readError.failure(Unit).some 18 | else: 19 | ().success.some 20 | else: 21 | none(Try[Unit]) 22 | 23 | proc value*[T: NonVoid](f: Future[T]): Option[Try[T]] = 24 | if f.finished: 25 | if f.failed: 26 | f.readError.failure(T).some 27 | else: 28 | f.read.success.some 29 | else: 30 | none(Try[T]) 31 | 32 | proc map*[T: NonVoid,U](v: Future[T], f: T -> U): Future[U] = 33 | var res = newFuture[U]() 34 | v.callback = () => (block: 35 | let vv = v.value.get 36 | if vv.isFailure: 37 | res.fail(vv.getError) 38 | else: 39 | let fv = tryM f(vv.get) 40 | if fv.isFailure: 41 | res.fail(fv.getError) 42 | else: 43 | res.complete(fv.get) 44 | ) 45 | return res 46 | 47 | proc map*[U](v: Future[void], f: Unit -> U): Future[U] = 48 | var res = newFuture[U]() 49 | v.callback = () => (block: 50 | if v.failed: 51 | res.fail(v.readError) 52 | else: 53 | let fv = tryM f(()) 54 | if fv.isFailure: 55 | res.fail(fv.getError) 56 | else: 57 | res.complete(fv.get) 58 | ) 59 | return res 60 | 61 | proc flatMap*[U](v: Future[void], f: Unit -> Future[U]): Future[U] = 62 | var res = newFuture[U]() 63 | v.callback = () => (block: 64 | if v.failed: 65 | res.fail(v.readError) 66 | else: 67 | let fv = tryM f(()) 68 | if fv.isFailure: 69 | res.fail(fv.getError) 70 | else: 71 | let fvv = fv.get 72 | fvv.callback = () => (block: 73 | if fvv.failed: 74 | res.fail(fvv.readError) 75 | else: 76 | res.complete(fvv.value.get.get) 77 | ) 78 | ) 79 | return res 80 | 81 | proc flatMap*[T: NonVoid,U](v: Future[T], f: T -> Future[U]): Future[U] = 82 | var res = newFuture[U]() 83 | v.callback = () => (block: 84 | if v.failed: 85 | res.fail(v.readError) 86 | else: 87 | let newF = tryM(f(v.value.get.get)) 88 | if newF.isFailure: 89 | res.fail(newF.getError) 90 | else: 91 | newF.get.callback = () => (block: 92 | let fv = newF.get.value.get 93 | if newF.isFailure: 94 | res.fail(fv.getError) 95 | else: 96 | if fv.isFailure: 97 | res.fail(fv.getError) 98 | else: 99 | res.complete(fv.get) 100 | ) 101 | ) 102 | return res 103 | 104 | template elemType*(v: Future): typedesc = 105 | ## Part of ``do notation`` contract 106 | type(v.value.get.get) 107 | 108 | proc unit*[T](v: T): Future[T] = 109 | result = newFuture[T]() 110 | when T is void: 111 | result.complete() 112 | else: 113 | result.complete(v) 114 | 115 | proc newFuture*[T: NonVoid](f: () -> T): Future[T] = 116 | result = unit(()).map(_ => f()) 117 | 118 | template futureImpl(body: typed): untyped = 119 | newFuture do() -> auto: 120 | body 121 | 122 | macro future*(body: untyped): untyped = 123 | ## Creates new future from `body`. 124 | var b = if body.kind == nnkDo: body[^1] else: body 125 | result = quote do: 126 | futureImpl((block: 127 | `b` 128 | )) 129 | 130 | proc run*(f: Future[void]): Try[Unit] = 131 | while not f.finished: 132 | asyncdispatch.poll(10) 133 | f.value.get 134 | 135 | proc run*[T: NonVoid](f: Future[T]): Try[T] = 136 | while not f.finished: 137 | asyncdispatch.poll(10) 138 | f.value.get 139 | 140 | proc join*[T](f: Future[Future[T]]): Future[T] = 141 | f.flatMap(id) 142 | 143 | proc flattenF*[T](f: Future[Try[T]]): Future[T] = 144 | f.flatMap do(v: Try[T]) -> auto: 145 | var res = newFuture[T]() 146 | if v.isFailure: 147 | res.fail(v.getError) 148 | else: 149 | res.complete(v.get) 150 | return res 151 | 152 | proc onComplete*[T](v: Future[T], f: Try[T] -> void) = 153 | v.callback = () => (block: 154 | f(v.value.get) 155 | ) 156 | 157 | proc timeout*[T](v: Future[T], timeout: int): Future[Option[T]] = 158 | let tm = v.withTimeout(timeout) 159 | let res = newFuture[Option[T]]() 160 | tm.callback = () => (block: 161 | if tm.failed: 162 | res.fail(tm.readError) 163 | else: 164 | res.complete(if tm.read: v.read.some else: T.none) 165 | ) 166 | return res 167 | 168 | proc complete*(f: Future[void], v: Unit) = 169 | complete(f) 170 | 171 | instance KleisliInst, Future[_], exporting(_) 172 | -------------------------------------------------------------------------------- /src/fp/iterable.nim: -------------------------------------------------------------------------------- 1 | import sugar, 2 | classy, 3 | ./option, 4 | ./either, 5 | ./list 6 | 7 | typeclass FlattenInst, F[_]: 8 | proc flatten[T](xs: List[F[T]]): List[T] = 9 | xs.map((v: F[T]) => v.asList).join 10 | 11 | instance FlattenInst, Option[_], exporting(_) 12 | 13 | instance FlattenInst, E => Either[E,_], exporting(_) 14 | 15 | instance FlattenInst, List[_], exporting(_) 16 | -------------------------------------------------------------------------------- /src/fp/kleisli.nim: -------------------------------------------------------------------------------- 1 | import classy, 2 | sugar 3 | 4 | typeclass KleisliInst, F[_], exported: 5 | proc `>>=`[A,B](a: F[A], f: A -> F[B]): F[B] = 6 | a.flatMap(f) 7 | 8 | proc `>=>`[A,B,C](f: A -> F[B], g: B -> F[C]): A -> F[C] = 9 | (a: A) => f(a).flatMap(g) 10 | 11 | -------------------------------------------------------------------------------- /src/fp/list.nim: -------------------------------------------------------------------------------- 1 | import sugar, 2 | ./option, 3 | classy, 4 | ./kleisli, 5 | typetraits, 6 | boost/types 7 | 8 | {.experimental.} 9 | 10 | type 11 | ListNodeKind = enum 12 | lnkNil, lnkCons 13 | List*[T] = ref object 14 | ## List ADT 15 | case kind: ListNodeKind 16 | of lnkNil: 17 | discard 18 | of lnkCons: 19 | value: T 20 | next: List[T] 21 | 22 | proc Cons*[T](head: T, tail: List[T]): List[T] = 23 | ## Constructs non empty list 24 | List[T](kind: lnkCons, value: head, next: tail) 25 | 26 | proc Nil*[T](): List[T] = 27 | ## Constructs empty list 28 | List[T](kind: lnkNil) 29 | 30 | proc head*[T](xs: List[T]): T = 31 | ## Returns list's head 32 | case xs.kind 33 | of lnkCons: return xs.value 34 | else: doAssert(xs.kind == lnkCons) 35 | 36 | proc isEmpty*(xs: List): bool = 37 | ## Checks if list is empty 38 | xs.kind == lnkNil 39 | 40 | proc headOption*[T](xs: List[T]): Option[T] = 41 | ## Returns list's head option 42 | if xs.isEmpty: T.none else: xs.head.some 43 | 44 | proc tail*[T](xs: List[T]): List[T] = 45 | ## Returns list's tail 46 | case xs.kind 47 | of lnkCons: xs.next 48 | else: xs 49 | 50 | iterator items*[T](xs: List[T]): T = 51 | var cur = xs 52 | while not cur.isEmpty: 53 | yield cur.head 54 | cur = cur.tail 55 | 56 | iterator pairs*[T](xs: List[T]): tuple[key: int, val: T] = 57 | var cur = xs 58 | var i = 0.int 59 | while not cur.isEmpty: 60 | yield (i, cur.head) 61 | cur = cur.tail 62 | inc i 63 | 64 | proc `==`*[T](xs, ys: List[T]): bool = 65 | ## Compares two lists 66 | if (xs.isEmpty, ys.isEmpty) == (true, true): true 67 | elif (xs.isEmpty, ys.isEmpty) == (false, false): xs.head == ys.head and xs.tail == ys.tail 68 | else: false 69 | 70 | type 71 | ListFormat = enum 72 | lfADT, lfSTD 73 | 74 | proc foldLeft*[T,U](xs: List[T], z: U, f: (U, T) -> U): U = 75 | ## Fold left operation 76 | case xs.isEmpty 77 | of true: z 78 | else: foldLeft(xs.tail, f(z, xs.head), f) 79 | 80 | # foldRight can be recursive, or realized via foldLeft. 81 | proc foldRight*[T,U](xs: List[T], z: U, f: (T, U) -> U): U = 82 | ## Fold right operation. Can be defined via foldLeft (-d:foldRightViaLeft switch), or be recursive by default. 83 | when defined(foldRightViaLeft): 84 | foldLeft[T, U -> U](xs, (b: U) => b, (g: U -> U, x: T) => ((b: U) => g(f(x, b))))(z) 85 | else: 86 | case xs.isEmpty 87 | of true: z 88 | else: f(xs.head, xs.tail.foldRight(z, f)) 89 | 90 | proc foldRightF*[T, U](xs: List[T], z: () -> U, f: (T, () -> U) -> U): U = 91 | ## Right fold over lists. Lazy in accumulator - allows for early termination. 92 | if xs.isEmpty: z() 93 | else: f(xs.head, () => xs.tail.foldRightF(z, f)) 94 | 95 | proc asString[T](xs: List[T], f: ListFormat): string = 96 | proc asAdt(xs: List[T]): string = 97 | case xs.isEmpty 98 | of true: "Nil" 99 | else: "Cons(" & $xs.head & ", " & xs.tail.asAdt & ")" 100 | 101 | proc asStd(xs: List[T]): string = "List(" & xs.foldLeft("", (s: string, v: T) => (if s == "": $v else: s & ", " & $v)) & ")" 102 | 103 | case f 104 | of lfADT: xs.asAdt 105 | else: xs.asStd 106 | 107 | proc `$`*[T](xs: List[T]): string = 108 | ## Converts list to string 109 | result = xs.asString(lfSTD) 110 | 111 | proc `^^`*[T](v: T, xs: List[T]): List[T] = 112 | ## List construction operator, like ``::`` in Haskell 113 | Cons(v, xs) 114 | 115 | proc `++`*[T](xs, ys: List[T]): List[T] = 116 | ## Concatenates two lists 117 | xs.append(ys) 118 | 119 | # After bug https://github.com/nim-lang/Nim/issues/5647, remove item and seed from type signature 120 | proc unfoldLeft*[T, U](f: U -> Option[tuple[item: T, seed: U]], x:U): List[T] {. inline .}= 121 | ## Build a List from the left from function f: T -> Option(T,T) and a seed of type T 122 | result = Nil[T]() 123 | var a = x 124 | var b: T 125 | 126 | var fa = f(a) 127 | while fa.isDefined: 128 | (b, a) = fa.get() 129 | result = b ^^ result 130 | fa = f(a) 131 | 132 | proc unfoldRight*[T, U](f: U -> Option[tuple[item: T, seed: U]], x:U): List[T] {. inline .}= 133 | ## Build a List from the right from function f: T -> Option(T,T) and a seed of type T 134 | unfoldLeft(f,x).reverse 135 | 136 | proc drop*(xs: List, n: int): List = 137 | ## Drops `n` first elements of the list 138 | case xs.isEmpty 139 | of true: xs 140 | else: (if n == 0: xs else: xs.tail.drop(n - 1)) 141 | 142 | proc dropWhile*[T](xs: List[T], p: T -> bool): List[T] = 143 | ## Drops elements of the list while `p` returns true. 144 | case xs.isEmpty 145 | of true: xs 146 | else: (if not xs.head.p(): xs else: xs.tail.dropWhile(p)) 147 | 148 | proc span*[T](xs: List[T], p: T -> bool): (List[T], List[T]) = 149 | ## Splits `xs` into two parts: longest prefix for which `p` holds, 150 | ## and the remainder. 151 | proc worker(acc: List[T], todo: List[T]): (List[T], List[T]) = 152 | if todo.isEmpty or not p(todo.head): 153 | (acc.reverse, todo) 154 | else: 155 | worker(todo.head ^^ acc, todo.tail) 156 | 157 | worker(Nil[T](), xs) 158 | 159 | proc partition*[T](xs: List[T], p: T -> bool): (List[T], List[T]) = 160 | ## Splits list into two parts: elements for which `p` holds, and 161 | ## elements for which it does not. The order of elements in both 162 | ## parts is preserved. 163 | ## 164 | ## equivalent to `(xs.filter(p), xs.filter(t => not p(t)))` 165 | ## (except for side effects of `p`) 166 | 167 | # Assembles the result in reverse order. 168 | 169 | proc worker(acc: (List[T], List[T]), x: T): auto = 170 | if p(x): 171 | (x ^^ acc[0], acc[1]) 172 | else: 173 | (acc[0], x ^^ acc[1]) 174 | 175 | let acc = xs.foldLeft((Nil[T](), Nil[T]()), worker) 176 | 177 | # Restore the order 178 | (acc[0].reverse, acc[1].reverse) 179 | 180 | proc dup*[T](xs: List[T]): List[T] = 181 | ## Duplicates the list 182 | xs.foldRight(Nil[T](), (x: T, xs: List[T]) => Cons(x, xs)) 183 | 184 | proc length*[T](xs: List[T]): int = 185 | ## Calculates the length of the list 186 | xs.foldRight(0, (_: T, x: int) => x+1) 187 | 188 | proc reverse*[T](xs: List[T]): List[T] = 189 | ## Reverses the list 190 | xs.foldLeft(Nil[T](), (xs: List[T], x: T) => Cons(x, xs)) 191 | 192 | proc append*[T](xs: List[T], ys: List[T]): List[T] = 193 | ## Concatenates two lists 194 | xs.foldRight(ys, (x: T, xs: List[T]) => Cons(x, xs)) 195 | 196 | proc join*[T](xs: List[List[T]]): List[T] = 197 | ## Joins the list of lists into single list 198 | xs.foldRight(Nil[T](), append) 199 | 200 | proc map*[T, U](xs: List[T], f: T -> U): List[U] = 201 | ## ``map`` operation for the list 202 | case xs.isEmpty 203 | of true: Nil[U]() 204 | else: Cons(f(xs.head), map(xs.tail, f)) 205 | 206 | proc filter*[T](xs: List[T], p: T -> bool): List[T] = 207 | ## ``filter`` operation for the list 208 | case xs.isEmpty 209 | of true: xs 210 | else: (if p(xs.head): Cons(xs.head, filter(xs.tail, p)) else: filter(xs.tail, p)) 211 | 212 | proc forEach*[T](xs: List[T], f: T -> void): void = 213 | ## Executes operation for all elements in list 214 | if not xs.isEmpty: 215 | f(xs.head) 216 | xs.tail.forEach(f) 217 | 218 | proc forAll*[T](xs: List[T], p: T -> bool): bool = 219 | ## Tests whether `p` holds for all elements of the list 220 | if xs.isEmpty: 221 | true 222 | elif not p(xs.head): 223 | false 224 | else: 225 | xs.tail.forAll(p) 226 | 227 | proc flatMap*[T,U](xs: List[T], f: T -> List[U]): List[U] = 228 | xs.map(f).join 229 | 230 | proc zipWith*[T,U,V](xs: List[T], ys: List[U], f: (T,U) -> V): List[V] = 231 | if xs.isEmpty or ys.isEmpty: 232 | Nil[V]() 233 | else: 234 | Cons(f(xs.head, ys.head), zipWith(xs.tail, ys.tail, f)) 235 | 236 | proc zipWithIndex*[T](xs: List[T], startIndex = 0): List[(T, int)] = 237 | if xs.isEmpty: 238 | Nil[(T,int)]() 239 | else: 240 | (xs.head, startIndex) ^^ zipWithIndex(xs.tail, succ startIndex) 241 | 242 | proc zip*[T,U](xs: List[T], ys: List[U]): List[(T,U)] = 243 | xs.zipWith(ys, (x, y) => (x, y)) 244 | 245 | # See https://github.com/nim-lang/Nim/issues/4061 246 | proc unzip*[T,U](xs: List[tuple[t: T, u: U]]): (List[T], List[U]) = 247 | xs.foldRight((Nil[T](), Nil[U]()), (v: (T,U), r: (List[T], List[U])) => (v[0] ^^ r[0], v[1] ^^ r[1])) 248 | 249 | proc find*[T](xs: List[T], p: T -> bool): Option[T] = 250 | ## Finds the first element that satisfies the predicate `p` 251 | if xs.isEmpty: 252 | T.none 253 | else: 254 | if p(xs.head): xs.head.some else: xs.tail.find(p) 255 | 256 | proc contains*[T](xs: List[T], x: T): bool = 257 | xs.find((y: T) => x == y).isDefined 258 | 259 | proc lookup*[T, U](xs: List[tuple[t: T, u: U]], key: T): Option[U] = 260 | xs.find((pair: (T, U)) => pair[0] == key) 261 | .map((pair: (T, U)) => pair[1]) 262 | 263 | proc hasSubsequence*[T](xs: List[T], ys: List[T]): bool = 264 | ## Checks if `ys` in `xs` 265 | if ys.isEmpty: 266 | true 267 | elif xs.isEmpty: 268 | false 269 | elif xs.head == ys.head: 270 | xs.tail.hasSubsequence(ys.tail) 271 | else: 272 | xs.tail.hasSubsequence(ys) 273 | 274 | # proc traverseImpl*[A, B, G, GB, GLB](xs: List[A], f: A -> GB): auto = 275 | # foldRightF( 276 | # xs, 277 | # () => point(Nil[B](), type(G)), 278 | # (x: A, xs: () -> GLB) => f(x).map2F(xs, (y: B, ys: List[B]) => y ^^ ys) 279 | # ) 280 | 281 | # proc traverse*[T,U](xs: List[T], f: T -> Option[U]): Option[List[U]] = 282 | # traverseImpl[T,U,Option, Option[U], Option[List[U]]](xs, f) 283 | 284 | proc traverse*[T,U](xs: List[T], f: T -> Option[U]): Option[List[U]] = 285 | ## Transforms the list of `T` into the list of `U` f via `f` only if 286 | ## all results of applying `f` are defined. 287 | ## Doesnt execute `f` for elements after the first `None` is encountered. 288 | 289 | # Implementation with foldRightF breaks semcheck when inferring 290 | # gcsafe. So we have to keep this basic. 291 | # Also, since tail calls are not guaranteed, we use a loop instead 292 | # of recursion. 293 | 294 | var rest = xs 295 | var acc = Nil[U]() 296 | while not rest.isEmpty: 297 | let headRes = f(rest.head) 298 | if headRes.isEmpty: 299 | return List[U].none 300 | acc = Cons(headRes.get, acc) 301 | rest = rest.tail 302 | acc.reverse.some 303 | 304 | proc sequence*[T](xs: List[Option[T]]): Option[List[T]] = 305 | ## Transforms the list of options into the option of list, which 306 | ## is defined only if all of the source list options are defined 307 | xs.traverse((x: Option[T]) => x) 308 | 309 | proc traverseU*[T,U](xs: List[T], f: T -> Option[U]): Option[Unit] = 310 | var rest = xs 311 | while not rest.isEmpty: 312 | let headRes = f(rest.head) 313 | if headRes.isEmpty: 314 | return Unit.none 315 | rest = rest.tail 316 | ().some 317 | 318 | proc sequenceU*[T](xs: List[Option[T]]): Option[Unit] = 319 | xs.traverseU((x: Option[T]) => x) 320 | 321 | proc asList*[T](xs: varargs[T]): List[T] = 322 | ## Creates list from varargs 323 | proc initListImpl(i: int, xs: openarray[T]): List[T] = 324 | if i > high(xs): 325 | Nil[T]() 326 | else: 327 | Cons(xs[i], initListImpl(i+1, xs)) 328 | initListImpl(0, xs) 329 | 330 | proc asList*[T](xs: List[T]): List[T] = 331 | xs 332 | 333 | proc asList*[T](o: Option[T]): List[T] = 334 | if o.isEmpty: 335 | Nil[T]() 336 | else: 337 | o.get ^^ Nil[T]() 338 | 339 | proc asSeq*[T](xs: List[T]): seq[T] = 340 | ## Converts list to sequence 341 | var s: seq[T] = @[] 342 | xs.forEach((v: T) => (add(s, v))) 343 | result = s 344 | 345 | template elemType*(v: List): typedesc = 346 | ## Part of ``do notation`` contract 347 | type(v.head) 348 | 349 | proc point*[T](v: T, t: typedesc[List[T]]): List[T] = 350 | v ^^ Nil[T]() 351 | 352 | instance KleisliInst, List[_], exporting(_) 353 | 354 | proc sortBy*[T](xs: List, f: (T, T) -> int): List[T] = 355 | if xs.isEmpty: 356 | xs 357 | else: 358 | let h = xs.head 359 | let t = xs.tail 360 | t.filter(v => (f(v, h) < 0)).sortBy(f) ++ asList(h) ++ t.filter(v => (f(v, h) >= 0)).sortBy(f) 361 | 362 | proc sort*[T](xs: List[T]): List[T] = 363 | xs.sortBy((x: T, y: T) => cmp(x, y)) 364 | -------------------------------------------------------------------------------- /src/fp/map.nim: -------------------------------------------------------------------------------- 1 | import ./list, 2 | sugar, 3 | ./option, 4 | boost/data/rbtree, 5 | sequtils 6 | 7 | type 8 | Map*[K,V] = RBTree[K,V] 9 | 10 | proc newMap*[K,V]: Map[K,V] = newRBTree[K,V]() 11 | 12 | proc asMap*[K,V](lst: List[tuple[k: K, v: V]]): Map[K,V] = 13 | lst.foldLeft(newMap[K,V](), (res: Map[K,V], v: tuple[k: K, v: V]) => res.add(v[0], v[1])) 14 | proc asMap*[K,V](xs: varargs[tuple[k: K, v: V]]): Map[K,V] = xs.asList.asMap 15 | 16 | proc asList*[K,V](m: Map[K,V]): List[tuple[k: K, v: V]] = toSeq(m.pairs).asList.map((t: (K,V)) => (k: t[0], v: t[1])) 17 | 18 | proc key*[K,V](item: tuple[k: K, v: V]): K = item[0] 19 | proc value*[K,V](item: tuple[k: K, v: V]): V = item[1] 20 | 21 | proc `$`*[K,V](m: Map[K,V]): string = 22 | result = "Map(" 23 | var first = true 24 | for k, v in m.pairs: 25 | if not first: 26 | result &= ", " 27 | else: 28 | first = not first 29 | result &= $k & " => " & $v 30 | result &= ")" 31 | 32 | # sugar.`->` doesn't support ``MapItem[K,V] -> bool`` declaration 33 | proc find*[K,V](m: Map[K,V], p: proc(i: (K,V)): bool): Option[tuple[k: K, v: V]] = 34 | for k, v in m.pairs: 35 | if p((k,v)): 36 | return (k: k, v: v).some 37 | return none(tuple[k: K, v: V]) 38 | 39 | proc filter*[K,V](m: Map[K,V], p: proc(i: (K,V)): bool): Map[K,V] = 40 | result = newMap[K,V]() 41 | for k, v in m.pairs: 42 | if p((k, v)): 43 | result = result.add(k, v) 44 | 45 | proc get*[K,V](m: Map[K,V], k: K): Option[V] = 46 | var v: V 47 | if m.maybeGet(k, v): 48 | some(v) 49 | else: 50 | none(V) 51 | 52 | proc remove*[K,V](m: Map[K,V], k: K): Map[K,V] = 53 | m.del(k) 54 | 55 | template `-`*[K,V](m: Map[K,V], k: K): Map[K,V] = m.remove(k) 56 | 57 | proc add*[K,V](m: Map[K,V], item: (K,V)): Map[K,V] = 58 | m.add(item[0], item[1]) 59 | 60 | template `+`*[K,V](m: Map[K,V], item: (K,V)): Map[K,V] = m.add(item) 61 | 62 | proc map*[K,V,K1,V1](m: Map[K,V], f: proc(item: (K,V)): (K1,V1)): Map[K1,V1] = 63 | result = newMap[K1,V1]() 64 | for k, v in m.pairs: 65 | result = result.add(f((k, v))) 66 | 67 | proc mapValues*[K,V1,V2](m: Map[K,V1], f: V1 -> V2): Map[K,V2] = 68 | result = newMap[K,V2]() 69 | for k, v in m.pairs: 70 | result = result.add((k, f(v))) 71 | 72 | proc `==`*[K,V](m1, m2: Map[K,V]): bool = 73 | m1.equals(m2) 74 | 75 | proc forEach*[K,V](m: Map[K,V], f: proc(item: (K,V)): void): void = 76 | for k, v in m.pairs: 77 | f((k, v)) 78 | 79 | proc forAll*[K,V](m: Map[K,V], p: proc(item: (K,V)): bool): bool = 80 | result = true 81 | for k, v in m.pairs: 82 | if not p((k, v)): 83 | return false 84 | -------------------------------------------------------------------------------- /src/fp/mtransf.nim: -------------------------------------------------------------------------------- 1 | import sugar, 2 | classy, 3 | ./option, 4 | ./either, 5 | ./list 6 | 7 | type 8 | OptionTOption*[A] = ref object 9 | run*: Option[Option[A]] 10 | OptionTEither*[E,A] = ref object 11 | run*: Either[E,Option[A]] 12 | OptionTList*[A] = ref object 13 | run*: List[Option[A]] 14 | 15 | typeclass OptionTInst, [F[_], OptionTF[_]], exported: 16 | proc optionT[A](run: F[Option[A]]): OptionTF[A] = 17 | OptionTF[A](run: run) 18 | 19 | proc point[A](v: A, t: typedesc[OptionTF[A]]): OptionTF[A] = 20 | v.point(F[Option[A]]).optionT 21 | 22 | proc getOrElse[A](o: OptionTF[A], v: A): F[A] = 23 | o.run.map((o: Option[A]) => o.getOrElse(v)) 24 | 25 | proc getOrElse[A](o: OptionTF[A], f: () -> A): F[A] = 26 | o.run.map((o: Option[A]) => o.getOrElse(f)) 27 | 28 | proc getOrElseF[A](o: OptionTF[A], f: () -> F[A]): F[A] = 29 | o.run.flatMap((o: Option[A]) => o.map((v: A) => v.point(F[A])).getOrElse(f)) 30 | 31 | proc map[A,B](o: OptionTF[A], f: A -> B): OptionTF[B] = 32 | optionT(o.run.map((o: Option[A]) => o.map(f))) 33 | 34 | proc flatMap[A,B](o: OptionTF[A], f: A -> OptionTF[B]): OptionTF[B] = 35 | o.run.flatMap( 36 | (opt: Option[A]) => (if opt.isDefined: opt.get.f.run else: B.none.point(F[Option[B]])) 37 | ).optionT 38 | 39 | proc flatMapF[A,B](o: OptionTF[A], f: A -> F[B]): OptionTF[B] = 40 | o.flatMap((v: A) => f(v).map((v: B) => v.some).optionT) 41 | 42 | template elemType[A](v: OptionTF[A]): typedesc = 43 | A 44 | 45 | instance OptionTInst, [Option[_], OptionTOption[_]], exporting(_) 46 | 47 | instance OptionTInst, E => [Either[E, _], OptionTEither[E,_]], exporting(_) 48 | 49 | instance OptionTInst, [List[_], OptionTList[_]], exporting(_) 50 | -------------------------------------------------------------------------------- /src/fp/option.nim: -------------------------------------------------------------------------------- 1 | import sugar, 2 | strutils, 3 | classy, 4 | ./kleisli, 5 | ./function 6 | 7 | {.experimental.} 8 | 9 | type 10 | OptionKind = enum 11 | okNone, okSome 12 | Option*[T] = ref object 13 | ## Option ADT 14 | case kind: OptionKind 15 | of okSome: 16 | value: T 17 | else: 18 | discard 19 | 20 | proc Some*[T](value: T): Option[T] = 21 | ## Constructs option object with value 22 | Option[T](kind: okSome, value: value) 23 | 24 | proc None*[T](): Option[T] = 25 | ## Constructs empty option object 26 | Option[T](kind: okNone) 27 | 28 | # Some helpers 29 | proc some*[T](value: T): Option[T] = Some(value) 30 | proc none*[T](value: T): Option[T] = None[T]() 31 | proc none*(T: typedesc): Option[T] = None[T]() 32 | 33 | proc notNil*[T](o: Option[T]): Option[T] = 34 | ## Maps nil object to none 35 | if o.kind == okSome and o.value.isNil: 36 | none(T) 37 | else: 38 | o 39 | 40 | proc notEmpty*(o: Option[string]): Option[string] = 41 | ## Maps empty string to none 42 | if o.kind == okSome and (o.value.strip == ""): 43 | string.none 44 | else: 45 | o 46 | 47 | proc option*[T](p: bool, v: T): Option[T] = 48 | ## Returns the boxed value of `v` if ``p == true`` or None 49 | if p: v.some 50 | else: T.none 51 | 52 | proc optionF*[T](p: bool, f: () -> T): Option[T] = 53 | ## Returns the boxed value of ``f()`` if ``p == true`` or None 54 | if p: f().some 55 | else: T.none 56 | 57 | proc `==`*[T](x, y: Option[T]): bool = 58 | if x.isDefined and y.isDefined: 59 | x.value == y.value 60 | elif x.isEmpty and y.isEmpty: 61 | true 62 | else: 63 | false 64 | 65 | proc isEmpty*[T](o: Option[T]): bool = 66 | ## Checks if `o` is empty 67 | o.kind == okNone 68 | 69 | proc isDefined*[T](o: Option[T]): bool = 70 | ## Checks if `o` contains value 71 | not o.isEmpty 72 | 73 | proc `$`*[T](o: Option[T]): string = 74 | ## Returns string representation of `o` 75 | if o.isDefined: 76 | 77 | "Some(" & $o.value & ")" 78 | else: 79 | "None" 80 | 81 | proc map*[T,U](o: Option[T], f: T -> U): Option[U] = 82 | ## Returns option with result of applying f to the value of `o` if it exists 83 | if o.isDefined: 84 | f(o.value).some 85 | else: 86 | none(U) 87 | 88 | proc flatMap*[T,U](o: Option[T], f: T -> Option[U]): Option[U] = 89 | ## Returns the result of applying `f` if `o` is defined, or none 90 | if o.isDefined: f(o.value) else: none(U) 91 | 92 | proc join*[T](mmt: Option[Option[T]]): Option[T] = 93 | ## Flattens the option 94 | mmt.flatMap(id) 95 | 96 | proc get*[T](o: Option[T]): T = 97 | ## Returns option's value if defined, or fail 98 | doAssert(o.isDefined, "Can't get Option's value") 99 | o.value 100 | 101 | proc getOrElse*[T](o: Option[T], d: T): T = 102 | ## Returns option's value if defined, or `d` 103 | if o.isDefined: o.value else: d 104 | 105 | proc getOrElse*[T](o: Option[T], f: void -> T): T = 106 | ## Returns option's value if defined, or the result of applying `f` 107 | if o.isDefined: o.value else: f() 108 | 109 | proc orElse*[T](o: Option[T], d: Option[T]): Option[T] = 110 | ## Returns `o` if defined, or `d` 111 | if o.isDefined: o else: d 112 | 113 | proc orElse*[T](o: Option[T], f: void -> Option[T]): Option[T] = 114 | ## Returns `o` if defined, or the result of applying `f` 115 | if o.isDefined: o else: f() 116 | 117 | proc filter*[T](o: Option[T], p: T -> bool): Option[T] = 118 | ## Returns `o` if it is defined and the result of applying `p` 119 | ## to it's value is true 120 | if o.isDefined and p(o.value): o else: none(T) 121 | 122 | proc map2*[T,U,V](t: Option[T], u: Option[U], f: (T, U) -> V): Option[V] = 123 | ## Returns the result of applying f to `t` and `u` value if they are both defined 124 | if t.isDefined and u.isDefined: f(t.value, u.value).some else: none(V) 125 | 126 | proc map2F*[A, B, C]( 127 | ma: Option[A], 128 | mb: () -> Option[B], 129 | f: (A, B) -> C 130 | ): Option[C] = 131 | ## Maps 2 `Option` values via `f`. Lazy in second argument. 132 | ma.flatMap((a: A) => mb().map((b: B) => f(a, b))) 133 | 134 | proc zip*[T, U](t: Option[T], u: Option[U]): Option[(T, U)] = 135 | ## Returns the tuple of `t` and `u` values if they are both defined 136 | if t.isDefined and u.isDefined: 137 | (t.get, u.get).some 138 | else: 139 | none((T, U)) 140 | 141 | proc liftO*[T,U](f: T -> U): proc(o: Option[T]): Option[U] = 142 | ## Turns the function `f` of type `T -> U` into the function 143 | ## of type `Option[T] -> Option[U]` 144 | (o: Option[T]) => o.map((x: T) => f(x)) 145 | 146 | proc forEach*[T](xs: Option[T], f: T -> void): void = 147 | ## Applies `f` to the options value if it's defined 148 | if xs.isDefined: 149 | f(xs.value) 150 | 151 | proc forAll*[T](xs: Option[T], f: T -> bool): bool = 152 | ## Returns `f` applied to the option's value or true 153 | if xs.isDefined: 154 | f(xs.value) 155 | else: 156 | true 157 | 158 | proc traverse*[T, U](ts: seq[T], f: T -> Option[U]): Option[seq[U]] = 159 | ## Returns list of values of application of `f` to elements in `ts` 160 | ## if all the results are defined 161 | ## 162 | ## Example: 163 | ## .. code-block:: nim 164 | ## traverse(@[1, 2, 3], (t: int) => (t - 1).some) == @[0, 1, 2].some 165 | ## 166 | ## let f = (t: int) => (if (t < 3): t.some else: int.none) 167 | ## traverse(@[1, 2, 3], f) == seq[int].none 168 | var acc = newSeq[U](ts.len) 169 | for i, t in ts: 170 | let mu = f(t) 171 | if mu.isDefined: 172 | acc[i] = mu.get 173 | else: 174 | return none(seq[U]) 175 | return acc.some 176 | 177 | proc asSeq*[T](o: Option[T]): seq[T] = 178 | if o.isDefined: 179 | @[o.get] 180 | else: 181 | @[] 182 | 183 | template elemType*(v: Option): typedesc = 184 | ## Part of ``do notation`` contract 185 | type(v.get) 186 | 187 | proc point*[A](v: A, t: typedesc[Option[A]]): Option[A] = 188 | v.some 189 | 190 | instance KleisliInst, Option[_], exporting(_) 191 | 192 | proc fold*[A,B](v: Option[A], ifNone: () -> B, ifSome: A -> B): B = 193 | ## Returns the result of applying `ifSome` to the value if `v` is 194 | ## defined. Otherwise evaluates `ifNone`. 195 | if v.isDefined: 196 | ifSome(v.value) 197 | else: 198 | ifNone() 199 | 200 | proc foldLeft*[A,B](v: Option[A], b: B, f: (B, A) -> B): B = 201 | if v.isDefined: 202 | f(b, v.value) 203 | else: 204 | b 205 | 206 | proc foldRight*[A,B](v: Option[A], b: B, f: (A, B) -> B): B = 207 | if v.isDefined: 208 | f(v.value, b) 209 | else: 210 | b 211 | -------------------------------------------------------------------------------- /src/fp/std/jsonops.nim: -------------------------------------------------------------------------------- 1 | import json, 2 | sugar, 3 | typetraits, 4 | ../either, 5 | ../option, 6 | ../list, 7 | ../map, 8 | boost/jsonserialize 9 | 10 | proc mget*(n: JsonNode, key: string|int): EitherS[Option[JsonNode]] = 11 | ## Returns the child node if it exists, or none. 12 | ## Returns an error if `key` is int and `n` is not an array, or 13 | ## if `key` is string and `n` is not an object. 14 | case n.kind 15 | of JObject: 16 | when key is string: 17 | n.contains(key).optionF(() => n[key]).rightS 18 | else: 19 | ("JsonNode.mget: can't use string key with node of type " & $n.kind).left(Option[JsonNode]) 20 | of JArray: 21 | when key is int: 22 | (key >= 0 and key < n.len).optionF(() => n[key]).rightS 23 | else: 24 | ("JsonNode.mget: can't use int key with node of type " & $n.kind).left(Option[JsonNode]) 25 | else: 26 | ("JsonNode.mget: can't get the child node from node of type " & $n.kind).left(Option[JsonNode]) 27 | 28 | proc mget*(n: Option[JsonNode], key: string|int): EitherS[Option[JsonNode]] = 29 | ## Returns the child node if it exists, or none. 30 | ## Returns an error if `key` is int and `n` is not an array, or 31 | ## if `key` is string and `n` is not an object. 32 | if n.isDefined: 33 | n.get.mget(key) 34 | else: 35 | JsonNode.none.rightS 36 | 37 | proc mget*(key: string|int): Option[JsonNode] -> EitherS[Option[JsonNode]] = 38 | (n: Option[JsonNode]) => n.mget(key) 39 | 40 | proc value*[T](t: typedesc[T], n: JsonNode): EitherS[T] = 41 | ## Returns the value of the node `n` of type `t` 42 | template checkKind(nKind: JsonNodeKind): untyped = 43 | if n.kind != nKind: 44 | raise newException(ValueError, "Can't get Json node's value of kind " & $nKind & ", node's kind is " & $n.kind) 45 | when t is int or t is int64: 46 | tryS do() -> auto: 47 | JInt.checkKind 48 | n.getBiggestInt.T 49 | elif t is string: 50 | tryS do() -> auto: 51 | JString.checkKind 52 | n.getStr 53 | elif t is float: 54 | tryS do() -> auto: 55 | JFloat.checkKind 56 | n.getFloat 57 | elif t is bool: 58 | tryS do() -> auto: 59 | JBool.checkKind 60 | n.getBool 61 | else: 62 | proc `$`[T](some:typedesc[T]): string = name(T) 63 | {.fatal: "Can't get value of type " & $T} 64 | 65 | proc mvalue*[T](t: typedesc[T]): Option[JsonNode] -> EitherS[Option[T]] = 66 | (n: Option[JsonNode]) => (if n.isDefined: value(T, n.get).map((v: T) => v.some) else: T.none.rightS) 67 | 68 | type 69 | Jsonable* = concept t 70 | %t is JsonNode 71 | 72 | proc mjson*[T: Jsonable](v: T): Option[JsonNode] = 73 | (%v).some 74 | 75 | proc mjson*[T: Jsonable](v: Option[T]): Option[JsonNode] = 76 | v.map(v => %v) 77 | 78 | proc toJsonObject*(xs: List[(string, Option[JsonNode])]): JsonNode = 79 | var res = newJObject() 80 | xs.forEach( 81 | (v: (string, Option[JsonNode])) => (if v[1].isDefined: res[v[0]] = v[1].get) 82 | ) 83 | return res 84 | 85 | proc toJson*[T](v: Option[T]): JsonNode = 86 | mixin toJson 87 | if v.isDefined: 88 | v.get.toJson 89 | else: 90 | nil 91 | 92 | proc fromJson*[T](_: typedesc[Option[T]], n: JsonNode): Option[T] = 93 | mixin fromJson 94 | if n.isNil or n.kind == JNull: 95 | T.none 96 | else: 97 | T.fromJson(n).some 98 | 99 | proc toJson*[T](v: List[T]): JsonNode = 100 | mixin toJson 101 | let res = newJArray() 102 | v.forEach do(v: T) -> void: 103 | let n = v.toJson 104 | if n.isNil: 105 | res.add(newJNull()) 106 | else: 107 | res.add(n) 108 | return res 109 | 110 | proc fromJson*[T](_: typedesc[List[T]], n: JsonNode): List[T] = 111 | if n.isNil or n.kind != JArray: 112 | raise newFieldException("Value of the node is not an array") 113 | result = Nil[T]() 114 | mixin fromJson 115 | for i in countdown(n.len-1, 0): 116 | result = Cons(T.fromJson(n[i]), result) 117 | 118 | proc toJson*[T](v: Map[string,T]): JsonNode = 119 | mixin toJson 120 | let res = newJObject() 121 | v.forEach do(v: (string,T)) -> void: 122 | let val = v[1].toJson 123 | if not val.isNil: 124 | res[v[0]] = val 125 | return res 126 | 127 | proc fromJson*[T](_: typedesc[Map[string,T]], n: JsonNode): Map[string,T] = 128 | if n.isNil or n.kind != JObject: 129 | raise newFieldException("Value of the node is not an object") 130 | result = newMap[string,T]() 131 | mixin fromJson 132 | for k, v in n: 133 | let val = T.fromJson(v) 134 | when compiles(val.isNil): 135 | if not val.isNil: 136 | result = result.add((k, val)) 137 | else: 138 | result = result.add((k, val)) 139 | 140 | -------------------------------------------------------------------------------- /src/fp/stream.nim: -------------------------------------------------------------------------------- 1 | import sugar, 2 | ./option, 3 | ./list, 4 | ./function 5 | 6 | type 7 | StreamNodeKind = enum 8 | snkEmpty, snkCons 9 | Stream*[T] = ref object 10 | ## Lazy stream ADT 11 | case k: StreamNodeKind 12 | of snkEmpty: 13 | discard 14 | else: 15 | h: () -> T 16 | t: () -> Stream[T] 17 | 18 | proc Cons*[T](h: () -> T, t: () -> Stream[T]): Stream[T] = 19 | ## Constructs not empty stream 20 | Stream[T](k: snkCons, h: h, t: t) 21 | 22 | proc Empty*[T](): Stream[T] = 23 | ## Constructs empty stream 24 | Stream[T](k: snkEmpty) 25 | 26 | proc cons*[T](h: () -> T, t: () -> Stream[T]): Stream[T] = 27 | ## Constructs not empty stream usime memoized versions of `h` and `t` 28 | Cons(h.memoize, t.memoize) 29 | 30 | proc empty*(T: typedesc): Stream[T] = 31 | ## Constructs empty stream 32 | Empty[T]() 33 | 34 | proc asStream*[T](xs: varargs[T]): Stream[T] = 35 | ## Converts arguments list to stream 36 | let ys = @xs 37 | proc initStreamImpl(i: int, xs: seq[T]): Stream[T] = 38 | if i > high(xs): 39 | T.empty 40 | else: 41 | cons(() => xs[i], () => initStreamImpl(i+1, xs)) 42 | initStreamImpl(0, ys) 43 | 44 | proc toSeq*[T](xs: Stream[T]): seq[T] = 45 | ## Converts stream to sequence 46 | result = @[] 47 | proc fill(xs: Stream[T], s: var seq[T]) = 48 | case xs.k 49 | of snkCons: 50 | s.add(xs.h()) 51 | xs.t().fill(s) 52 | else: discard 53 | xs.fill(result) 54 | 55 | proc isEmpty*[T](xs: Stream[T]): bool = 56 | ## Checks if the stream is empty 57 | xs.k == snkEmpty 58 | 59 | proc head*[T](xs: Stream[T]): T = 60 | ## Returns the head of the stream or throws an exception if empty 61 | xs.h() 62 | 63 | proc headOption*[T](xs: Stream[T]): Option[T] = 64 | ## Returns the head of the stream or none 65 | case xs.k 66 | of snkEmpty: T.none 67 | else: xs.h().some 68 | 69 | proc tail*[T](xs: Stream[T]): Stream[T] = 70 | ## Return the tail of the stream 71 | case xs.k 72 | of snkEmpty: T.empty 73 | else: xs.t() 74 | 75 | proc `==`*[T](xs: Stream[T], ys: Stream[T]): bool = 76 | ## Checks two streams for equality 77 | if (xs.k, ys.k) == (snkCons, snkCons): 78 | xs.h() == ys.h() and xs.t() == ys.t() 79 | else: 80 | xs.k == ys.k 81 | 82 | proc foldRight*[T,U](xs: Stream[T], z: () -> U, f: (x: T, y: () -> U) -> (() -> U)): U = 83 | ## Fold right operation for lazy stream 84 | case xs.k 85 | of snkEmpty: z() 86 | else: f(xs.h(), () => xs.t().foldRight(z, f))() 87 | 88 | proc foldLeft*[T,U](xs: Stream[T], z: () -> U, f: (y: () -> U, x: T) -> (() -> U)): U = 89 | ## Fold left operation for lazy stream 90 | case xs.k 91 | of snkEmpty: z() 92 | else: xs.t().foldLeft(f(z, xs.h()), f) 93 | 94 | proc toList*[T](xs: Stream[T]): List[T] = 95 | ## Converts stream to list 96 | case xs.k 97 | of snkEmpty: Nil[T]() 98 | else: xs.h() ^^ xs.t().toList() 99 | 100 | proc asList*[T](xs: Stream[T]): List[T] = 101 | ## Converts stream to list 102 | xs.toList 103 | 104 | proc `$`*[T](xs: Stream[T]): string = 105 | ## Converts stream to string 106 | let f = (s: () -> string, x: T) => (() => ( 107 | let ss = s(); 108 | if ss == "": $x else: ss & ", " & $x 109 | )) 110 | "Stream(" & xs.foldLeft(() => "", f) & ")" 111 | 112 | proc take*[T](xs: Stream[T], n: int): Stream[T] = 113 | ## Takes `n` first elements of the stream 114 | if n == 0 or xs.k == snkEmpty: 115 | T.empty 116 | else: 117 | cons(xs.h, () => xs.t().take(n - 1)) 118 | 119 | proc drop*[T](xs: Stream[T], n: int): Stream[T] = 120 | ## Drops `n` first elements of the stream 121 | if n == 0 or xs.k == snkEmpty: 122 | xs 123 | else: 124 | xs.t().drop(n - 1) 125 | 126 | proc takeWhile*[T](xs: Stream[T], p: T -> bool): Stream[T] = 127 | ## Takes elements while `p` is true 128 | xs.foldRight(() => T.empty(), (x: T, y: () -> Stream[T]) => (() => (if x.p: cons(() => x, y) else: T.empty))) 129 | 130 | proc dropWhile*[T](xs: Stream[T], p: T -> bool): Stream[T] = 131 | ## Drops elements while `p` is true 132 | if xs.k == snkEmpty or not xs.h().p: 133 | xs 134 | else: 135 | xs.t().dropWhile(p) 136 | 137 | proc forAll*[T](xs: Stream[T], p: T -> bool): bool = 138 | ## Checks if `p` returns true for all elements in stream 139 | case xs.k 140 | of snkEmpty: true 141 | else: p(xs.h()) and xs.t().forAll(p) 142 | 143 | proc map*[T,U](xs: Stream[T], f: T -> U): Stream[U] = 144 | ## Maps one stream to another 145 | xs.foldRight(() => U.empty, (x: T, y: () -> Stream[U]) => (() => cons(() => f(x), y))) 146 | 147 | proc filter*[T](xs: Stream[T], p: T -> bool): Stream[T] = 148 | ## Filters stream with predicate `p` 149 | xs.foldRight(() => T.empty, (x: T, y: () -> Stream[T]) => (() => (if x.p: cons(() => x, y) else: y()))) 150 | 151 | proc append*[T](xs: Stream[T], x: () -> T): Stream[T] = 152 | ## Appends `x` to the end of the stream 153 | xs.foldRight(() => cons(x, () => T.empty), (x: T, y: () -> Stream[T]) => (() => cons(() => x, y))) 154 | 155 | proc flatMap*[T,U](xs: Stream[T], f: T -> Stream[U]): Stream[U] = 156 | ## Flat map operation for the stream 157 | xs.foldRight(() => U.empty, (x: T, y: () -> Stream[U]) => (() => f(x).foldRight(y, (x: U, y: () -> Stream[U]) => (() => cons(() => x, y))))) 158 | 159 | template elemType*(v: Stream): typedesc = 160 | ## Part of ``do notation`` contract 161 | type(v.head) 162 | 163 | proc point*[T](v: () -> T, t: typedesc[Stream[T]]): Stream[T] = 164 | cons(v, () => T.empty) 165 | -------------------------------------------------------------------------------- /src/fp/trym.nim: -------------------------------------------------------------------------------- 1 | import ./either, 2 | sugar, 3 | macros 4 | 5 | export either 6 | 7 | type Try*[A] = EitherE[A] 8 | ## The type representing either exception or successfully 9 | ## computed value 10 | 11 | proc success*[A](v: A): Try[A] = 12 | ## Create the successfully computed value 13 | v.rightE 14 | 15 | proc failure*[A](e: ref Exception, t: typedesc[A]): Try[A] = 16 | ## Create the result of failed computation 17 | e.left(A) 18 | 19 | proc failure*[A](msg: string, t: typedesc[A]): Try[A] {.inline.} = 20 | ## Create the result of failed computation 21 | try: 22 | raise newException(Exception, msg) 23 | except: 24 | result = getCurrentException().failure(A) 25 | 26 | proc fromEither*[E,A](v: Either[E,A]): Try[A] {.inline.}= 27 | ## Conversion from ``Either[E,A]`` type 28 | when E is ref Exception: 29 | v 30 | elif compiles(v.getLeft().`$`): 31 | if v.isLeft: 32 | v.getLeft().`$`.failure(A) 33 | else: 34 | v.get.success 35 | else: 36 | {.error: "Can't cast Either's left value to string".} 37 | 38 | template tryMImpl(body: typed): untyped = 39 | when compiles((block: 40 | tryE do() -> auto: 41 | body 42 | )): 43 | tryE do() -> auto: 44 | body 45 | else: 46 | tryE do() -> auto: 47 | body 48 | () 49 | 50 | macro tryM*(body: untyped): untyped = 51 | ## Executes `body` and places it's result in the ``Try`` container 52 | var b = if body.kind == nnkDo: body[^1] else: body 53 | result = quote do: 54 | tryMImpl((block: 55 | `b` 56 | )) 57 | 58 | proc isSuccess*[A](v: Try[A]): bool = 59 | ## Returns true if `v` contains value 60 | v.isRight 61 | 62 | proc isFailure*[A](v: Try[A]): bool = 63 | ## Returns true if `v` contains exception 64 | v.isLeft 65 | 66 | proc getError*[A](v: Try[A]): ref Exception = 67 | ## Returns the exception object 68 | v.getLeft 69 | 70 | proc getErrorMessage*[A](v: Try[A]): string = 71 | ## Returns the exception message 72 | v.getError.msg 73 | 74 | proc getErrorStack*[A](v: Try[A]): string = 75 | ## Returns the exception stack trace 76 | v.getError.getStackTrace 77 | 78 | proc recover*[A](v: Try[A], f: ref Exception -> A): Try[A] = 79 | ## Returns the result of calling `f` if `v` contains the exception. 80 | ## Otherwise returns `v` 81 | if v.isFailure: 82 | f(v.getError).success 83 | else: 84 | v 85 | 86 | proc recoverWith*[A](v: Try[A], f: ref Exception -> Try[A]): Try[A] = 87 | ## Returns the result of calling `f` if `v` contains the exception. 88 | ## Otherwise returns `v` 89 | if v.isFailure: 90 | f(v.getError) 91 | else: 92 | v 93 | 94 | proc mapErrorMessage*[A](v: Try[A], f: string -> string): Try[A] = 95 | ## Transforms the error message using `f` 96 | if v.isFailure: 97 | var errCopy: ref Exception 98 | deepCopy(errCopy, v.getError) 99 | errCopy.msg = f(errCopy.msg) 100 | errCopy.failure(A) 101 | else: 102 | v 103 | -------------------------------------------------------------------------------- /tests/fp/std/test_jsonops.nim: -------------------------------------------------------------------------------- 1 | import unittest, 2 | sugar, 3 | fp, 4 | json, 5 | boost/typeutils, 6 | boost/jsonserialize 7 | 8 | suite "std.json": 9 | 10 | let doc = """ 11 | { 12 | "int": 123, 13 | "str": "Hello!", 14 | "bool": true, 15 | "float": 1.2, 16 | "obj": { 17 | "int": 20 18 | } 19 | } 20 | """.parseJson 21 | 22 | test "mget": 23 | check: doc.mget("int").get.isDefined 24 | check: doc.mget("int2").get.isEmpty 25 | let v = doc.some.rightS >>= (mget("obj") >=> mget("int")) 26 | check: v.get.isDefined 27 | 28 | test "value": 29 | check: value(int, doc["int"]) == 123.rightS 30 | check: value(string, doc["int"]).isLeft 31 | check: value(string, doc["str"]) == "Hello!".rightS 32 | check: value(int, doc["str"]).isLeft 33 | check: value(float, doc["float"]) == 1.2.rightS 34 | check: doc.some.rightS >>= ( 35 | mget("obj") >=> 36 | mget("int") >=> 37 | mvalue(int) 38 | ) == 20.some.rightS 39 | check: value(bool, doc["bool"]) == true.rightS 40 | 41 | test "toJson": 42 | check: [ 43 | ("a", 1.mjson), 44 | ("b", "a".mjson), 45 | ("c", int64.none.mjson), 46 | ].asList.toJsonObject == %*{ "a": 1, "b": "a" } 47 | 48 | test "boost serialization test": 49 | data Test, json, show: 50 | a = "test".some 51 | b = asList(1, 2) 52 | c = asMap({"a": 1, "b": 2}) 53 | 54 | check: Test.fromJson(initTest().toJson()).a == "test".some 55 | check: Test.fromJson(initTest(a = string.none).toJson()).a == string.none 56 | check: Test.fromJson(initTest().toJson()).b == asList(1, 2) 57 | check: Test.fromJson(initTest().toJson()).c == asMap({"a": 1, "b": 2}) 58 | -------------------------------------------------------------------------------- /tests/fp/test_all.nim: -------------------------------------------------------------------------------- 1 | import ../src/fp, 2 | ./test_option, 3 | ./test_either, 4 | ./test_trym, 5 | ./test_list, 6 | ./test_map, 7 | ./test_function, 8 | ./test_stream, 9 | ./test_forcomp, 10 | ./test_forcomp_bind, 11 | ./test_futurem, 12 | ./test_mtransf, 13 | ./test_iterable, 14 | ./test_concurrent, 15 | 16 | ./std/test_jsonops 17 | -------------------------------------------------------------------------------- /tests/fp/test_concurrent.nim: -------------------------------------------------------------------------------- 1 | import sugar, 2 | fp, 3 | strutils, 4 | unittest, 5 | boost/types 6 | 7 | suite "Concurrent": 8 | 9 | test "Strategy": 10 | check sequentalStrategy(() => 1)() == 1 11 | check spawnStrategy(() => 1)() == 1 12 | check asyncStrategy(() => 1)() == 1 13 | 14 | test "Actor": 15 | let actor = newActor[string](asyncStrategy, dequeQueue) do(v: string) -> void: 16 | echo v 17 | actor ! "Hello, world!" 18 | actor ! "Hello, world 2!" 19 | 20 | test "Actor with state": 21 | proc actorF(state, v: string): string = 22 | result = state 23 | if v == "end": 24 | discard #echo state 25 | else: 26 | result &= v & "\n" 27 | let actor = newActorS[string, string](Strategy[Unit](spawnStrategy[Unit]), QueueImpl[string](channelQueue[string]), channelQueue, "", actorF) 28 | actor ! "Hello, world!" 29 | actor ! "Hello, world 2!" 30 | actor ! "end" 31 | 32 | test "Spawn stress test": 33 | #TODO: Use spawnStrategy when https://github.com/nim-lang/Nim/issues/5626 34 | # will be fixed 35 | let actor = newActor[int](sequentalStrategy, dequeQueue) do(v: int) -> void: 36 | discard 37 | for x in 0..10000: 38 | actor ! 1 39 | 40 | -------------------------------------------------------------------------------- /tests/fp/test_either.nim: -------------------------------------------------------------------------------- 1 | import ../../src/fp/list, 2 | ../../src/fp/either, 3 | ../../src/fp/option, 4 | unittest, 5 | sugar, 6 | boost/types, 7 | threadpool 8 | 9 | {.warning[SmallLshouldNotBeUsed]: off.} 10 | 11 | suite "Either ADT": 12 | 13 | let r = 10.rightS 14 | let l = "Error".left(int) 15 | 16 | test "Basic functions": 17 | let l1 = Left[int,string](10) 18 | let l2 = 10.left("") 19 | let l3 = 10.left(string) 20 | discard l3 21 | let r1 = Right[int,string]("test") 22 | let r2 = "test".right(0) 23 | let r3 = "test".right(int) 24 | 25 | check: l1 == l2 26 | check: l1 == l2 27 | 28 | check: r1 == r2 29 | check: r1 == r3 30 | 31 | check: r1 != l1 32 | check: r2 != l2 33 | 34 | check: 123.rightE == 123.rightE 35 | check: 123.rightS == 123.rightS 36 | 37 | check: $l1 == "Left(10)" 38 | check: $r1 == "Right(test)" 39 | 40 | check: r1.get == "test" 41 | expect(AssertionError): discard l1.get == "test" 42 | 43 | check: either.cond(true, 1, 0) == 1.right(int) 44 | check: either.cond(false, 1, 0) == 0.left(int) 45 | check: either.condF(true, () => 1, () => 0) == 1.right(int) 46 | check: either.condF(false, () => 1, () => 0) == 0.left(int) 47 | 48 | check: 1.some.asEither("nope") == 1.rightS 49 | check: 1.none.asEither("nope") == "nope".left(int) 50 | 51 | check: "Error".left(int).asEitherE.getLeft.msg == "Error" 52 | check: newException(Exception, "Error").left(int).asEitherS.getLeft == "Error" 53 | 54 | check: 1.rightS.asOption == 1.some 55 | check: ().left(int).asOption == none(int) 56 | 57 | check: 1.rightS.flip == 1.left(string) 58 | check: 1.left(string).flip == 1.rightS 59 | 60 | check: 1.rightS.toUnit == ().rightS 61 | 62 | check: 1.rightS.rightS.join == 1.rightS 63 | check: "error".left(1.rightS).join == "error".left(int) 64 | check: ("error".left(int)).rightS.join == "error".left(int) 65 | 66 | check: 1.point(EitherS[int]) == 1.rightS 67 | check: 1.point(EitherE[int]) == 1.rightE 68 | 69 | check: "0".left(int).fold(e => e, v => $v) == "0" 70 | check: 1.rightS.fold(e => e, v => $v) == "1" 71 | 72 | test "Map": 73 | check: r.map(x => x * 2) == 20.rightS 74 | check: l.map(x => x * 2) != 20.rightS 75 | check: l.map(x => x * 2) == l 76 | 77 | check: 1.rightS.mapLeft(_ => 404) == 1.right(int) 78 | check: "Error".left(int).mapLeft(_ => 404) == 404.left(int) 79 | 80 | check: r.flatMap((x: int) => (x * 2).rightS) == 20.rightS 81 | check: r.flatMap((x: int) => l) == l 82 | 83 | check: "Value".rightS.map2(10.rightS, (x: string, y: int) => x & $y) == "Value10".rightS 84 | check: "Error1".left(string).map2(10.rightS, (x: string, y: int) => x & $y) == "Error1".left(string) 85 | check: "Value".rightS.map2("Error2".left(int), (x: string, y: int) => x & $y) == "Error2".left(string) 86 | check: 10.rightS.map2("Error2".left(int), (x: int, y: int) => x + y) == "Error2".left(int) 87 | 88 | check: "Value".rightS.map2F(() => 10.rightS, (x: string, y: int) => x & $y) == "Value10".rightS 89 | check: "Error1".left(string).map2F(() => 10.rightS, (x: string, y: int) => x & $y) == "Error1".left(string) 90 | check: "Value".rightS.map2F(() => "Error2".left(int), (x: string, y: int) => x & $y) == "Error2".left(string) 91 | check: 10.rightS.map2F(() => "Error2".left(int), (x: int, y: int) => x + y) == "Error2".left(int) 92 | 93 | proc badEither(): EitherS[int] = raise newException(Exception, "Not lazy!") 94 | check: "err".left(string).map2F(badEither, (x, y) => x & $y) == "err".left(string) 95 | 96 | test "Getters": 97 | check: r.getOrElse(0) == 10 98 | check: r.getOrElse(() => 0) == 10 99 | 100 | check: l.getOrElse(0) == 0 101 | check: l.getOrElse(() => 0) == 0 102 | 103 | check: r.orElse(l) == r 104 | check: r.orElse(() => l) == r 105 | 106 | check: l.orElse(r) == r 107 | check: l.orElse(() => r) == r 108 | 109 | test "Safe exceptions": 110 | check: tryE(() => 2/4) == 0.5.rightE 111 | check: tryS(() => 2/4) == 0.5.rightS 112 | {.floatChecks: on.} 113 | var x = 2.0 114 | check: tryE(() => x / 0.0).isLeft == true 115 | check: tryS(() => x / 0.0).isLeft == true 116 | let f = () => ( 117 | result = 1; 118 | raise newException(Exception, "Test Error") 119 | ) 120 | let ex1 = tryS f 121 | let ex2 = tryE f 122 | check: ex1.errorMsg == "Test Error" 123 | check: ex2.errorMsg == "Test Error" 124 | 125 | let g1 = () => "Error 1".left(()); 126 | let g2 = () => ( 127 | result = ().rightE; 128 | raise newException(Exception, "Error 2") 129 | ) 130 | check: g1.flatTryS.errorMsg == "Error 1" 131 | check: g2.flatTryE.errorMsg == "Error 2" 132 | 133 | test "Transformations": 134 | let good = @[1, 2, 3, 4, 5].asList 135 | let goodE = @[1, 2, 3, 4, 5].asList.map(x => x.rightS) 136 | let badE = @[1, 2, 3, 4].asList.map(x => x.rightS) ++ @["Error".left(int)].asList 137 | 138 | check: good.traverse((x: int) => x.rightS) == good.rightS 139 | check: good.traverseU((x: int) => x.rightS) == ().rightS 140 | check: good.traverse((x: int) => (if x < 3: x.rightS else: "Error".left(type(x)))) == "Error".left(List[int]) 141 | check: good.traverseU((x: int) => (if x < 3: x.rightS else: "Error".left(type(x)))) == "Error".left(Unit) 142 | check: goodE.sequence == good.rightS 143 | check: goodE.sequenceU == ().rightS 144 | check: badE.sequence == "Error".left(List[int]) 145 | check: badE.sequenceU == "Error".left(Unit) 146 | 147 | # traverse should not call f after the first Left 148 | var cnt = 0 149 | let res = asList(1, 2, 3).traverse do (x: int) -> auto: 150 | inc cnt 151 | if x != 2: x.rightS 152 | else: "err".left(int) 153 | check: res == "err".left(List[int]) 154 | check: cnt == 2 155 | 156 | # Traverse with Option 157 | proc leftFunc(i: int): EitherS[bool] = "foo".left(bool) 158 | proc rightFunc(i: int): EitherS[bool] = true.rightS 159 | check: int.none.traverse(rightFunc) == bool.none.rightS 160 | check: int.none.traverseU(rightFunc) == ().rightS 161 | check: int.none.traverse(leftFunc) == bool.none.rightS 162 | check: int.none.traverseU(leftFunc) == ().rightS 163 | check: 1.some.traverse(rightFunc) == true.some.rightS 164 | check: 1.some.traverseU(rightFunc) == ().rightS 165 | check: 1.some.traverseU(leftFunc) == "foo".left(Unit) 166 | 167 | # sequence with Option 168 | check: 1.rightS.some.sequence == 1.some.rightS 169 | check: 1.rightS.some.sequenceU == ().rightS 170 | check: "foo".left(int).some.sequence == "foo".left(Option[int]) 171 | check: "foo".left(int).some.sequenceU == "foo".left(Unit) 172 | check: EitherS[int].none.sequence == int.none.rightS 173 | check: EitherS[int].none.sequenceU == ().rightS 174 | 175 | test "Traverse with List should allow to properly infer gcsafe": 176 | proc f(i: int): auto = i.rightS 177 | 178 | proc g(): auto {.gcsafe.} = 179 | asList(1, 2, 3).traverse(f) 180 | 181 | discard g() 182 | 183 | test "Control flow": 184 | check: whenF(true, () => "error".left(Unit)) == "error".left(Unit) 185 | check: whenF(false, () => "error".left(Unit)) == ().rightS 186 | 187 | let whileRes = whileM(10) do (v: int) -> auto: 188 | (v > 0).rightS 189 | do (v: int) -> auto: 190 | (v - 1).rightS 191 | check: whileRes == 0.rightS 192 | check: whileM("error", (v: string) => v.left(bool), (v: string) => v.rightS) == "error".left(string) 193 | 194 | # whileM - convenience wrapper 195 | var iters = 0 196 | let whileRes2 = whileM() do () -> auto: 197 | (iters < 3).rightS 198 | do () -> auto: 199 | iters.inc 200 | ().rightS 201 | check: whileRes2 == ().rightS 202 | check: iters == 3 203 | 204 | check: 1.rightE.run == 1 205 | expect(ValueError): 206 | discard newException(ValueError, "error").left(int).run 207 | 208 | const sres = "Hello, world!" 209 | let bres = bracket do () -> auto: 210 | cast[ptr array[100, char]](allocShared(100)).rightS 211 | do (s: ptr array[100, char]) -> auto: 212 | deallocShared(cast[pointer](s)) 213 | ().rightS 214 | do (s: ptr array[100, char]) -> auto: 215 | proc thr(p: pointer) {.thread.} = 216 | copyMem(p, sres.cstring, sres.len + 1) 217 | var t: Thread[pointer] 218 | createThread(t, thr, s) 219 | t.joinThread 220 | ($s.cstring).rightS 221 | 222 | check: bres.run == sres 223 | 224 | let eres = "Exception".left(int).catch do (s: string) -> auto: 225 | 1.rightE 226 | check: eres == 1.rightE 227 | 228 | test "Exception macros": 229 | check: 1.rightS == (tryST do: 230 | check: 1 == 1 231 | 1 232 | ) 233 | check: tryST(1.rightS) == 1.rightS 234 | check: tryET(1) == 1.rightE 235 | check: 1.rightE == (tryET do: 236 | check 2 == 2 237 | 1.rightE 238 | ) 239 | 240 | test "Traversable": 241 | check: asList(1.rightS) == asList(1) 242 | 243 | test "Kleisli ops": 244 | let f = (v: int) => (v + 1).rightS 245 | let g = (v: int) => (v * 100).rightS 246 | check: 4.rightS >>= (f >=> g) == 500.rightS 247 | 248 | -------------------------------------------------------------------------------- /tests/fp/test_forcomp.nim: -------------------------------------------------------------------------------- 1 | import sugar, 2 | unittest, 3 | macros, 4 | ../../src/fp/option, 5 | ../../src/fp/either, 6 | ../../src/fp/list, 7 | ../../src/fp/forcomp, 8 | ../../src/fp/trym, 9 | ../../src/fp/stream 10 | 11 | suite "ForComp": 12 | test "Option - manual": 13 | # for (x <- 1.some, y <- x + 3) yield y * 100 14 | check: 1.some.flatMap((x: int) => (x + 3).some).map(y => y * 100) == 400.some 15 | check: 1.some.flatMap((x: int) => (x + 3).some).flatMap((y: int) => (y * 100).some) == 400.some 16 | check: 1.some.flatMap((x: int) => 17 | (x + 3).some.flatMap((y: int) => 18 | (y * 100).some 19 | ) 20 | ) == 400.some 21 | # for (x <- 1.some, y <- 2.some, doSomething(), z <- y * 3) yield x + y + z 22 | let doSomething = () => 100500.some 23 | check: 1.some.flatMap((x: int) => 24 | 2.some.flatMap((y: int) => 25 | doSomething().flatMap((_: int) => 26 | (y * 3).some.flatMap((z: int) => 27 | (x + y + z).some 28 | ) 29 | ) 30 | ) 31 | ) == 9.some 32 | 33 | test "Option - fc macro": 34 | # for (x <- 1.some, y <- x + 3) yield y * 100 35 | check: fc[(y*100).some | ( 36 | (x: int) <- 1.some, 37 | (y: int) <- (x + 3).some 38 | )] == 400.some 39 | check: fc[(y*100).some | ( 40 | (x: int) <- int.none, 41 | (y: int) <- (x + 3).some 42 | )] == int.none 43 | 44 | test "Either - fc macro": 45 | # for (x <- 1.rightS, y <- x + 3) yield y * 100 46 | check: fc[(y*100).rightS | ( 47 | (x: int) <- 1.rightS, 48 | (y: int) <- (x + 3).rightS 49 | )] == 400.rightS 50 | 51 | check: fc[(y*100).rightS | ( 52 | (x: int) <- "Fail".left(int), 53 | (y: int) <- (x + 3).rightS 54 | )] == "Fail".left(int) 55 | 56 | test "Option - act macro": 57 | # for (x <- 1.some, y <- x + 3) yield y * 100 58 | let res = act: 59 | (x: int) <- 1.some 60 | (y: int) <- (x + 3).some 61 | (y*100).some 62 | check: res == 400.some 63 | let res2 = act: 64 | (x: int) <- int.none 65 | (y: int) <- (x + 3).some 66 | (y*100).some 67 | check: res2 == int.none 68 | 69 | test "Either - act macro": 70 | # for (x <- 1.rightS, y <- x + 3) yield y * 100 71 | let res = act: 72 | (x: int) <- 1.rightS 73 | (y: int) <- (x + 3).rightS 74 | (y*100).rightS 75 | check: res == 400.rightS 76 | let res2 = act: 77 | (x: int) <- "Fail".left(int) 78 | (y: int) <- (x + 3).rightS 79 | (y*100).rightS 80 | check: res2 == "Fail".left(int) 81 | 82 | test "``if`` example": 83 | proc testFunc(i: int): Option[int] = act: 84 | (x: int) <- (if i < 10: int.none else: i.some) 85 | (x * 100).some 86 | 87 | check: testFunc(1).isDefined == false 88 | check: testFunc(20) == 2000.some 89 | 90 | test "Lists": 91 | let res = act: 92 | (x: int) <- asList(1,2,3) 93 | (y: int) <- asList(1,2,3) 94 | asList((x, y)) 95 | echo res 96 | 97 | test "Type inference": 98 | let res = act: 99 | x <- asList(1,2,3) 100 | y <- asList("a", "b", "c") 101 | asList(y & $x) 102 | echo res 103 | 104 | let resO = act: 105 | x <- 1.some 106 | y <- (x * 2).some 107 | ().none 108 | ("res = " & $y).some 109 | echo resO 110 | 111 | test "Crash test": 112 | proc io1(): EitherS[int] = 113 | 1.rightS 114 | proc io2(i: int): EitherS[string] = 115 | ("From action 2: " & $i).rightS 116 | proc io3(i: int, s: string): EitherS[string] = 117 | ("Got " & $i & " from aсtion 1 and '" & s & "' from action 2").rightS 118 | let res = fc[io3(i, s) | (i <- io1(), (echo("i = ", i); ().rightS), s <- io2(i), (echo("s = ", s); ().rightS))] 119 | 120 | echo res 121 | 122 | test "Streams test": 123 | proc intStream(fr: int): Stream[int] = 124 | cons(() => fr, () => intStream(fr + 1)) 125 | let res = act: 126 | x <- intStream(1) 127 | asStream("S" & $x) 128 | check: res.take(10).asList == lc[i | (i <- 1..10), int].asList.map(v => "S" & $v) 129 | 130 | test "Custom types": 131 | proc flatMap[T](s: seq[T], f: T -> seq[T]): seq[T] = 132 | result = newSeq[T]() 133 | for v in s: 134 | result.add(f(v)) 135 | template elemType(s: seq): typedesc = 136 | type(s[0]) 137 | 138 | let res = act: 139 | x <- @[1, 2, 3] 140 | y <- @[100, 200, 300] 141 | z <- @[5, 7] 142 | @[x * y + z] 143 | 144 | check: res == @[105, 107, 205, 207, 305, 307, 205, 207, 405, 407, 605, 607, 305, 307, 605, 607, 905, 907] 145 | 146 | test "act in generics": 147 | # https://github.com/nim-lang/Nim/issues/4669 148 | proc foo[A](a: A): Option[A] = 149 | act: 150 | a0 <- a.some 151 | a0.some 152 | 153 | assert: foo("123") == "123".some 154 | 155 | test "Yield in do notation": 156 | let res = act: 157 | x <- 1.some 158 | y <- 2.some 159 | z <- 3.some 160 | yield x + y + z 161 | check: res == 6.some 162 | 163 | test "Misc syntax support": 164 | proc test(x: int): int = x 165 | let res = act: 166 | a <- tryM(test(1)) 167 | b <- tryM: test(3) 168 | c <- tryM do: 169 | let x = test(5) 170 | x 171 | d <- tryM test(7) 172 | tryM do: 173 | # void can also be used 174 | discard test(0) 175 | yield a + b + c + d 176 | check: res == success(16) 177 | 178 | test "AST change #1": 179 | proc pos(x: int): Option[int] = act: 180 | y <- (if x < 0: int.none else: x.some) 181 | z <- act: 182 | x <- (if y == 0: int.none else: y.some) 183 | yield x 184 | yield z 185 | 186 | check: pos(1) == 1.some 187 | 188 | test "AST change #2": 189 | let x = act: 190 | v <- tryS do () -> auto: 191 | 1 192 | yield v 193 | check: x == 1.rightS 194 | -------------------------------------------------------------------------------- /tests/fp/test_forcomp_bind.nim: -------------------------------------------------------------------------------- 1 | import unittest, fp/option 2 | 3 | from fp/forcomp import act 4 | 5 | ## make sure act macro doesn't require having fc in 6 | ## instantiation context (matters for templates and generics) 7 | 8 | suite "Symbol binding in act": 9 | test "ForComp - act should not require additional imports": 10 | let success = compiles: 11 | act: 12 | x <- 1.some 13 | x.some 14 | 15 | check success 16 | -------------------------------------------------------------------------------- /tests/fp/test_function.nim: -------------------------------------------------------------------------------- 1 | import unittest, 2 | sugar, 3 | fp/function, 4 | fp/list, 5 | fp/forcomp 6 | 7 | suite "Functions": 8 | 9 | test "Memoization": 10 | var x = 0 11 | let f = () => (inc x; x) 12 | let fm = f.memoize 13 | 14 | check: f() == 1 15 | check: f() == 2 16 | check: fm() == 3 17 | check: f() == 4 18 | check: fm() == 3 19 | 20 | test "Curried functions": 21 | let f2 = (x: int, y: int) => x + y 22 | check: f2.curried()(1)(2) == 3 23 | check: f2.curried().uncurried2()(1, 2) == 3 24 | check: asList(1,2,3,4).map(f2.curried()(1)) == asList(2,3,4,5) 25 | 26 | let f3 = (x: int, y: int, z: int) => x + y + z 27 | check: f3.curried()(1)(2)(3) == 6 28 | check: f3.curried1()(1)(2, 3) == 6 29 | check: f3.curried1()(1).curried()(2)(3) == 6 30 | check: f3.curried().uncurried2()(1, 2)(3) == 6 31 | check: f3.curried().uncurried3()(1, 2, 3) == 6 32 | 33 | let f4 = (a: int, b: int, c: int, d: int) => a + b + c + d 34 | check: f4.curried()(1)(2)(3)(4) == 10 35 | check: f4.curried1()(1)(2,3,4) == 10 36 | 37 | test "Composition": 38 | check: (((x: int) => x * 2) <<< ((x: int) => x + 1))(1) == 4 39 | check: (((x: int) => x * 2) >>> ((x: int) => x + 1))(1) == 3 40 | 41 | test "Flip": 42 | let f2 = (x: string, y: int) => x & $y 43 | let f3 = (x: string, y: int, z: string) => x & $y & z 44 | check: f2.flip()(1, "a") == "a1" 45 | check: f2.curried().flip()(1)("a") == "a1" 46 | # Now it's not working, need to invertigate why 47 | # check: f3.curried().flip()(1)("a")("b") == "a1b" 48 | # check: f3.curried()("a").flip()("b")(1) == "a1b" 49 | # check: f3.curried().flip().uncurried3()(1, "a", "b") == "a1b" 50 | 51 | test "Generics": 52 | proc mkList[T](v: T, i: int): List[T] = 53 | if i <= 0: Nil[T]() 54 | else: v ^^ mkList(v, i - 1) 55 | 56 | let ones = mkList[int].curried()(1) 57 | -------------------------------------------------------------------------------- /tests/fp/test_futurem.nim: -------------------------------------------------------------------------------- 1 | import unittest, 2 | fp, 3 | sugar, 4 | boost/types 5 | 6 | suite "Future": 7 | test "Initialization": 8 | let x1 = future do: 9 | 1 10 | discard run x1 11 | check: x1.value.isDefined 12 | check: x1.value.get.isSuccess 13 | check: x1.value.get.get == 1 14 | let x2 = newFuture do() -> auto: 15 | block: 16 | raise newException(Exception, "Oops") 17 | 1 18 | discard run x2 19 | check: x2.value.isDefined 20 | check: x2.value.get.isFailure 21 | check: x2.value.get.getErrorMessage == "Oops" 22 | 23 | let x3 = newFuture[void]() 24 | check: x3.value.isEmpty 25 | x3.complete 26 | check: x3.value.isDefined 27 | check: x3.value.get.isSuccess 28 | check: x3.value.get.get == () 29 | 30 | test "Do notation": 31 | let y1 = act: 32 | x <- future(3) 33 | yield x * 2 34 | let res1 = run y1 35 | check: res1.get == 6 36 | 37 | let y2 = act: 38 | x <- (newFuture do() -> auto: 39 | block: 40 | raise newException(Exception, "Oops") 41 | 3) 42 | yield x * 2 43 | expect(Exception): 44 | discard y2.run.get 45 | 46 | test "Future[void] support": 47 | check unit[void]().map(_ => 1).run.get == 1 48 | check unit[void]().flatMap((_: Unit) => unit(1)).run.get == 1 49 | let correctType = unit[void]().elemType is Unit 50 | check correctType 51 | 52 | test "Utilities": 53 | let x1 = future(future(1)) 54 | check: x1.join.run.get == 1 55 | 56 | let x2 = future(1.success) 57 | check: x2.flattenF.run.get == 1 58 | 59 | let x3 = future("Oops".failure(int)) 60 | check: x3.flattenF.run.getErrorMessage == "Oops" 61 | 62 | var res = 1 63 | var x4 = future(2) 64 | x4.onComplete((v: Try[int]) => (res = v.get)) 65 | discard x4.run 66 | check: res == 2 67 | 68 | check: sleepAsync(200).map(_ => 1).timeout(100).run.get == int.none 69 | check: sleepAsync(100).map(_ => 1).timeout(200).run.get == 1.some 70 | -------------------------------------------------------------------------------- /tests/fp/test_iterable.nim: -------------------------------------------------------------------------------- 1 | import unittest, 2 | fp 3 | 4 | suite "Iterable": 5 | test "flatten": 6 | check: [1.some, int.none, 2.some].asList.flatten == [1, 2].asList 7 | check: [1.rightS, "".left(int), 2.rightS].asList.flatten == [1, 2].asList 8 | check: [[1].asList, Nil[int](), [2].asList].asList.flatten == [1, 2].asList 9 | -------------------------------------------------------------------------------- /tests/fp/test_list.nim: -------------------------------------------------------------------------------- 1 | import ../../src/fp/list, ../../src/fp/option, unittest, sugar, boost/types 2 | 3 | suite "List ADT": 4 | 5 | test "Initialization and conversion": 6 | let lst = [1, 2, 3, 4, 5].asList 7 | 8 | check: lst.head == 1 9 | check: lst.headOption == some(1) 10 | check: lst.tail.asSeq == @[2, 3, 4, 5] 11 | check: Nil[int]().headOption == 1.none 12 | check: $lst == "List(1, 2, 3, 4, 5)" 13 | check: 1^^2^^3^^4^^5^^Nil[int]() == lst 14 | check: ["a", "b"].asList != ["a", "b", "c"].asList 15 | 16 | check: 1.point(List) == [1].asList 17 | 18 | test "Fold operations": 19 | let lst = lc[x|(x <- 1..4),int].asList 20 | 21 | check: lst.foldLeft(0, (x, y) => x + y) == 10 22 | check: lst.foldLeft(1, (x, y) => x * y) == 24 23 | 24 | check: lst.foldRight(0, (x, y) => x + y) == 10 25 | check: lst.foldRight(1, (x, y) => x * y) == 24 26 | 27 | check: lst.foldRightF(() => 0, (x: int, y: () -> int) => x + y()) == 10 28 | check: lst.foldRightF(() => 1, (x: int, y: () -> int) => x * y()) == 24 29 | 30 | proc badAcc(): int = raise newException(Exception, "Not lazy!") 31 | check: lst.foldRightF(badAcc, (x: int, y: () -> int) => x) == 1 32 | 33 | check: Nil[int]().foldRight(100, (x, y) => x + y) == 100 34 | 35 | test "Unfold operations": 36 | proc divmod10(n: int): Option[(int, int)] = 37 | if n == 0: none((int,int)) 38 | else: some(( (n mod 10).int, n div 10)) 39 | 40 | check: unfoldLeft(divmod10,12301230) == [1,2,3,0,1,2,3,0].asList 41 | check: unfoldRight(divmod10,12301230) == [0,3,2,1,0,3,2,1].asList 42 | 43 | proc unconsString(s: string): Option[(char, string)] = 44 | if s == "": none((char, string)) 45 | else: some((s[0], s[1..^1])) 46 | 47 | check: unfoldLeft(unconsString,"Success !") == ['!', ' ', 's', 's', 'e', 'c', 'c', 'u', 'S'].asList 48 | check: unfoldRight(unconsString,"Success !") == ['S', 'u', 'c', 'c', 'e', 's', 's', ' ', '!'].asList 49 | 50 | var global_count: int = 0 51 | proc divmod10_count(n: int): Option[(int, int)] = 52 | inc global_count 53 | if n == 0: none((int,int)) 54 | else: some(( (n mod 10).int, n div 10)) 55 | 56 | let _ = unfoldLeft(divmod10_count,12301230) 57 | check: global_count == 9 58 | let _ = unfoldRight(divmod10_count,12301230) 59 | check: global_count == 18 60 | 61 | test "Transformations": 62 | check: @[1, 2, 3].asList.traverse((x: int) => x.some) == @[1, 2, 3].asList.some 63 | check: @[1, 2, 3].asList.traverseU((x: int) => x.some) == ().some 64 | check: @[1, 2, 3].asList.traverse((x: int) => (if x > 2: x.none else: x.some)) == List[int].none 65 | check: @[1, 2, 3].asList.traverseU((x: int) => (if x > 2: x.none else: x.some)) == Unit.none 66 | 67 | # traverse should not call f after the first None 68 | var cnt = 0 69 | let res = asList(1, 2, 3).traverse do (x: int) -> auto: 70 | inc cnt 71 | if x != 2: x.some 72 | else: int.none 73 | check: res == List[int].none 74 | check: cnt == 2 75 | 76 | check: @[1.some, 2.some, 3.some].asList.sequence == @[1, 2, 3].asList.some 77 | check: @[1.some, 2.some, 3.some].asList.sequenceU == ().some 78 | check: @[1.some, 2.none, 3.some].asList.sequence == List[int].none 79 | check: @[1.some, 2.none, 3.some].asList.sequenceU == Unit.none 80 | 81 | test "Drop operations": 82 | let lst = lc[x|(x <- 1..100),int].asList 83 | 84 | check: lst.drop(99) == [100].asList 85 | check: lst.dropWhile((x: int) => x < 100) == [100].asList 86 | 87 | test "Misc functions": 88 | let lst = lc[$x | (x <- 'a'..'z'), string].asList 89 | check: lst.dup == lst 90 | check: lst.reverse == lc[$(('z'.int - x).char) | (x <- 0..('z'.int - 'a'.int)), string].asList 91 | check: asList(2, 4, 6, 8).forAll((x: int) => x mod 2 == 0) == true 92 | check: asList(2, 4, 6, 9).forAll((x: int) => x mod 2 == 0) == false 93 | check: asList(1, 2, 3).zip(asList('a', 'b', 'c')) == asList((1, 'a'), (2, 'b'), (3, 'c')) 94 | check: asList((1, 'a'), (2, 'b'), (3, 'c')).unzip == (asList(1, 2, 3), asList('a', 'b', 'c')) 95 | check: [1,2,3].asList.zipWithIndex(-1) == [(1, -1), (2, 0), (3, 1)].asList 96 | 97 | check: asList(1, 2, 3).contains(2) 98 | check: not asList(1, 2, 3).contains(4) 99 | 100 | check: asList((1, 'a'), (2, 'b'), (2, 'c')).lookup(1) == 'a'.some 101 | check: asList((1, 'a'), (2, 'b'), (2, 'c')).lookup(2) == 'b'.some 102 | check: asList((1, 'a'), (2, 'b'), (2, 'c')).lookup(3) == char.none 103 | 104 | check: asList(1, 2, 3).span((i: int) => i <= 2) == (asList(1, 2), asList(3)) 105 | check: asList(1, 2, 3).span((i: int) => i mod 2 == 1) == (asList(1), asList(2, 3)) 106 | check: asList(1, 2, 3).span((i: int) => true) == (asList(1, 2, 3), Nil[int]()) 107 | 108 | check: asList(1, 2, 3).partition((i: int) => i > 2) == (asList(3), asList(1, 2)) 109 | check: asList(1, 2, 3).partition((i: int) => i == 2) == (asList(2), asList(1, 3)) 110 | check: asList(1, 2, 3).partition((i: int) => i > 4) == (Nil[int](), asList(1, 2, 3)) 111 | check: asList(1, 2, 3).partition((i: int) => i > 0) == (asList(1, 2, 3), Nil[int]()) 112 | 113 | check: asList(3, 5, 2, 4, 1).sort == asList(1, 2, 3, 4, 5) 114 | 115 | test "Iterators": 116 | let lst1 = [1, 2, 3, 4, 5].asList 117 | var lst2 = Nil[int]() 118 | for x in lst1: 119 | lst2 = Cons(x, lst2) 120 | check: lst2 == lst1.reverse 121 | 122 | let lst3 = [1, 2, 3, 4, 5].asList 123 | for i, x in lst3: 124 | check: i == x.pred 125 | 126 | test "List - traverse with Option should allow to properly infer gcsafe": 127 | proc f(i: int): auto = i.some 128 | 129 | proc g(): auto {.gcsafe.} = 130 | asList(1, 2, 3).traverse(f) 131 | 132 | discard g() 133 | 134 | test "Traversable": 135 | check: asList(asList(1)) == asList(1) 136 | check: asList(1.some) == asList(1) 137 | check: asList([1.some].asList) == [1.some].asList 138 | when compiles(asList(1.some) == [1.some].asList): 139 | check: false 140 | 141 | test "Kleisli": 142 | let f = (v: int) => asList(v, v + 1, v + 2) 143 | let g = (v: int) => asList(v, v * 2, v * 3) 144 | check: 1.point(List) >>= (f >=> g) == asList(1, 2, 3, 2, 4, 6, 3, 6, 9) 145 | -------------------------------------------------------------------------------- /tests/fp/test_map.nim: -------------------------------------------------------------------------------- 1 | import ../../src/fp/option, ../../src/fp/map, ../../src/fp/list, unittest, sugar, strutils 2 | 3 | suite "Map ADT": 4 | let m = [(1, "1"), (2, "2"), (3, "3")].asMap 5 | 6 | test "Initialization": 7 | check: m.asList == [(1, "1"), (2, "2"), (3, "3")].asList 8 | check: $m == "Map(1 => 1, 2 => 2, 3 => 3)" 9 | check: m.get(1) == "1".some 10 | check: m.get(2) == "2".some 11 | check: m.get(3) == "3".some 12 | check: m.get(4) == "4".none 13 | 14 | test "Transformations": 15 | check: (m + (4, "4")).get(4) == "4".some 16 | check: (m + (1, "11")).get(1) == "11".some 17 | check: (m - 1).get(1) == "11".none 18 | 19 | let r = m.map((i: (int,string)) => ($i.key, i.value.parseInt)) 20 | check: r == [("3", 3), ("1", 1), ("2", 2)].asMap 21 | 22 | check: m.mapValues(v => v & v) == [(3, "33"), (1, "11"), (2, "22")].asMap 23 | 24 | test "Misc": 25 | var x = 0 26 | [(1, 100), (2, 200)].asMap.forEach((v: (int, int)) => (x += (x + v.key) * v.value)) 27 | check: x == 20500 28 | check: map.find(m, v => (v[0] mod 2 == 0)) == (2, "2").some 29 | check: m.filter(v => (v[0] mod 2 == 0)) == [(2, "2")].asMap 30 | check: m.remove(1).get(1) == string.none 31 | check: m.map(i => (i[0] * 2, i[1] & i[1])) == [(2, "11"), (4, "22"), (6, "33")].asMap 32 | check: m.forAll(i => $i[0] == i[1]) 33 | 34 | test "Equality": 35 | check: asMap((1, 2), (3, 4)) == asMap((3, 4), (1, 2)) 36 | check: asMap((1, 2), (3, 4)) != asMap((1, 2)) 37 | check: asMap((1, 2)) != asMap((1, 2), (3, 4)) 38 | check: asMap((1, 2), (3, 4)) != asMap((3, 4), (1, 5)) 39 | -------------------------------------------------------------------------------- /tests/fp/test_mtransf.nim: -------------------------------------------------------------------------------- 1 | import unittest, 2 | sugar, 3 | fp/option, 4 | fp/either, 5 | fp/list, 6 | fp/forcomp, 7 | fp/mtransf 8 | 9 | suite "Monad transformers": 10 | test "OptionTOption": 11 | let v = optionT(1.some.some) 12 | check: v.getOrElse(2) == 1.some 13 | check: v.map(v => $v).getOrElse("") == "1".some 14 | check: v.flatMapF((v: int) => ($v).some).getOrElse("") == "1".some 15 | check: v.flatMap((v: int) => optionT(($v).some.some)).getOrElse("") == "1".some 16 | 17 | proc getArticle(id: int): Option[Option[string]] = 18 | if id == 0: 19 | none(Option[string]) 20 | else: 21 | ($id).some.some 22 | let articles = act: 23 | a1 <- optionT(getArticle(1)) 24 | a2 <- optionT(getArticle(2)) 25 | a3 <- optionT(getArticle(3)) 26 | optionT((a1, a2, a3).some.some) 27 | check: articles.run == ("1", "2", "3").some.some 28 | let badArticles = act: 29 | a <- articles 30 | bad <- optionT(getArticle(0)) 31 | (a[0], a[1], a[2], bad).some.some.optionT 32 | check: badArticles.run == none(Option[(string, string, string, string)]) 33 | 34 | test "OptionTEither": 35 | let v = optionT(1.some.rightS) 36 | check: v.getOrElse(2) == 1.rightS 37 | check: v.map(v => $v).getOrElse("") == "1".rightS 38 | check: v.flatMapF((v: int) => ($v).rightS).getOrElse("") == "1".rightS 39 | check: v.flatMap((v: int) => optionT(($v).some.rightS)).getOrElse("") == "1".rightS 40 | 41 | proc getArticle(id: int): EitherS[Option[string]] = 42 | if id == 0: 43 | "Not found".left(Option[string]) 44 | else: 45 | ($id).some.rightS 46 | let articles = act: 47 | a1 <- optionT(getArticle(1)) 48 | a2 <- optionT(getArticle(2)) 49 | a3 <- optionT(getArticle(3)) 50 | optionT((a1, a2, a3).some.rightS) 51 | check: articles.run == ("1", "2", "3").some.rightS 52 | let badArticles = act: 53 | a <- articles 54 | bad <- optionT(getArticle(0)) 55 | (a[0], a[1], a[2], bad).some.rightS.optionT 56 | check: badArticles.run == "Not found".left(Option[(string, string, string, string)]) 57 | 58 | test "OptionTList": 59 | let v = optionT([1.some].asList) 60 | check: v.getOrElse(2) == [1].asList 61 | check: v.map(v => $v).getOrElse("") == ["1"].asList 62 | check: v.flatMapF((v: int) => [$v].asList).getOrElse("") == ["1"].asList 63 | check: v.flatMap((v: int) => optionT([($v).some].asList)).getOrElse("") == ["1"].asList 64 | 65 | proc getArticle(id: int): List[Option[string]] = 66 | if id == 0: 67 | Nil[Option[string]]() 68 | else: 69 | ($id).some.point(List) 70 | let articles = act: 71 | a1 <- optionT(getArticle(1)) 72 | a2 <- optionT(getArticle(2)) 73 | a3 <- optionT(getArticle(3)) 74 | optionT([(a1, a2, a3).some].asList) 75 | check: articles.run == [("1", "2", "3").some].asList 76 | let badArticles = act: 77 | a <- articles 78 | bad <- optionT(getArticle(0)) 79 | [[a[0], a[1], a[2], bad].asList.some].asList.optionT 80 | check: badArticles.run == Nil[Option[List[string]]]() 81 | 82 | test "Misc functions": 83 | check: string.none.some.optionT.getOrElse("1") == "1".some 84 | check: string.none.some.optionT.getOrElse(() => "1") == "1".some 85 | 86 | check: string.none.rightS.optionT.getOrElseF(() => "Error".left(string)) == "Error".left(string) 87 | -------------------------------------------------------------------------------- /tests/fp/test_option.nim: -------------------------------------------------------------------------------- 1 | import ../../src/fp/option, unittest, sugar 2 | 3 | suite "Option ADT": 4 | 5 | test "Basic functions": 6 | let s = "test".some 7 | let n = int.none 8 | 9 | check: $s == "Some(test)" 10 | check: $n == "None" 11 | check: s.isEmpty == false 12 | check: s.isDefined == true 13 | check: n.isDefined == false 14 | check: n.isEmpty == true 15 | check: s == Some("test") 16 | check: s != string.none 17 | check: n == None[int]() 18 | check: nil.cstring.some.notNil == cstring.none 19 | check: " 123 ".some.notEmpty == " 123 ".some 20 | check: " ".some.notEmpty == "".none 21 | check: "123".none.notEmpty == "".none 22 | 23 | check: (2 < 3).optionF(() => true).getOrElse(false) 24 | check: (2 < 3).option(true).getOrElse(false) 25 | 26 | check: s.get == "test" 27 | expect(AssertionError): discard n.get == 10 28 | 29 | check: 1.point(Option) == 1.some 30 | 31 | test "Map": 32 | let f = (x: int) => $x 33 | check: 100500.some.map(f) == some("100500") 34 | check: 100500.none.map(f) == string.none 35 | 36 | check: 100.some.map2("Test".some, (x, y) => y & $x) == "Test100".some 37 | check: 100.some.map2("Test".none, (x, y) => y & $x) == "".none 38 | 39 | check: 100.some.map2F(() => "Test".some, (x, y) => y & $x) == "Test100".some 40 | check: 100.some.map2F(() => "Test".none, (x, y) => y & $x) == "".none 41 | 42 | proc badOption(): Option[int] = raise newException(Exception, "Not lazy!") 43 | check: int.none.map2F(badOption, (x, y) => $x) == string.none 44 | 45 | check: "x".some.map(v => "\"" & v & "\"").getOrElse("y") == "\"x\"" 46 | check: "x".none.map(v => "\"" & v & "\"").getOrElse("y") == "y" 47 | 48 | test "Flat map": 49 | let f = (x: int) => some(x * 2) 50 | check: 2.some.flatMap(f) == some(4) 51 | check: 2.none.flatMap(f) == none(4) 52 | 53 | test "Join": 54 | check: 2.some.some.join == 2.some 55 | check: int.none.some.join == int.none 56 | check: Option[int].none.join == int.none 57 | 58 | test "Getters": 59 | check: 2.some.getOrElse(3) == 2 60 | check: 2.none.getOrElse(3) == 3 61 | 62 | check: 2.some.getOrElse(() => 4) == 2 63 | check: 2.none.getOrElse(() => 4) == 4 64 | 65 | check: 2.some.orElse(3.some) == 2.some 66 | check: 2.none.orElse(3.some) == 3.some 67 | 68 | check: 2.some.orElse(() => 4.some) == 2.some 69 | check: 2.none.orElse(() => 4.some) == 4.some 70 | 71 | test "Filter": 72 | let x = "123".some 73 | let y = "12345".some 74 | let n = "".none 75 | let p = (x: string) => x.len > 3 76 | proc `!`[T](f: T -> bool): T -> bool = (v: T) => not f(v) 77 | 78 | check: x.filter(p) == n 79 | check: x.filter(!p) == x 80 | check: y.filter(p) == y 81 | check: y.filter(!p) == n 82 | check: n.filter(p) == n 83 | check: n.filter(!p) == n 84 | 85 | test "Traverse": 86 | let a = @[1, 2, 3] 87 | 88 | let f1 = (t: int) => (t - 1).some 89 | check: traverse(a, f1) == @[0, 1, 2].some 90 | 91 | let f2 = (t: int) => (if (t < 3): t.some else: int.none) 92 | check: traverse(a, f2) == seq[int].none 93 | 94 | test "Misc": 95 | check: ((x: int) => "Value " & $x).liftO()(1.some) == "Value 1".some 96 | var b = true 97 | false.some.forEach((v: bool) => (b = v)) 98 | check: b == false 99 | true.some.forEach((v: bool) => (b = v)) 100 | check: b == true 101 | false.none.forEach((v: bool) => (b = v)) 102 | check: b == true 103 | check: true.some.forAll(v => v) == true 104 | check: false.some.forAll(v => v) == false 105 | check: false.none.forAll(v => v) == true 106 | 107 | check: 1.some.zip("foo".some) == (1, "foo").some 108 | check: 1.some.zip(string.none) == (int, string).none 109 | 110 | check: 1.some.asSeq == @[1] 111 | check: int.none.asSeq == newSeq[int]() 112 | 113 | check: int.none.fold(() => "", v => $v) == "" 114 | check: 1.some.fold(() => "", v => $v) == "1" 115 | 116 | test "Kleisli": 117 | let f = (v: int) => (v + 1).some 118 | let g = (v: int) => (v * 100).some 119 | check: 4.some >>= (f >=> g) == 500.some 120 | 121 | -------------------------------------------------------------------------------- /tests/fp/test_stream.nim: -------------------------------------------------------------------------------- 1 | import ../../src/fp/stream, ../../src/fp/option, sugar, unittest 2 | 3 | suite "Stream": 4 | 5 | test "Initialization": 6 | let sInt = [1, 2, 3, 4, 5].asStream 7 | 8 | check: $sInt == "Stream(1, 2, 3, 4, 5)" 9 | check: sInt.toSeq == lc[x | (x <- 1..5), int] 10 | check: sInt.isEmpty == false 11 | 12 | check: (() => 1).point(Stream).toSeq == @[1] 13 | 14 | test "Accessors": 15 | let sInt = [1, 2, 3, 4, 5].asStream 16 | 17 | check: sInt.head == 1 18 | check: sInt.headOption == 1.some 19 | check: sInt.tail == [2, 3, 4, 5].asStream 20 | 21 | test "Functions": 22 | let sInt = [1, 2, 3, 4, 5].asStream 23 | 24 | check: sInt.take(2) == [1, 2].asStream 25 | check: sInt.drop(2) == [3, 4, 5].asStream 26 | check: sInt.takeWhile(x => x < 3) == [1, 2].asStream 27 | check: sInt.dropWhile(x => x < 3) == [3, 4, 5].asStream 28 | check: sInt.forAll(x => x < 10) == true 29 | check: sInt.map(x => "Val" & $x) == lc[("Val" & $x) | (x <- 1..5), string].asStream 30 | check: sInt.filter(x => x mod 2 == 0) == [2, 4].asStream 31 | check: sInt.append(() => 100) == [1, 2, 3, 4, 5, 100].asStream 32 | check: sInt.flatMap((x: int) => cons(() => x, () => cons(() => x * 100, () => int.empty))) == [1, 100, 2, 200, 3, 300, 4, 400, 5, 500].asStream 33 | 34 | test "Check infinite streams": 35 | proc intStream(fr: int): Stream[int] = 36 | cons(() => fr, () => intStream(fr + 1)) 37 | 38 | check: intStream(1).take(100) == lc[x | (x <- 1..100), int].asStream 39 | check: intStream(1).drop(100).take(100) == lc[x | (x <- 101..200), int].asStream 40 | -------------------------------------------------------------------------------- /tests/fp/test_trym.nim: -------------------------------------------------------------------------------- 1 | import unittest, 2 | fp/trym, 3 | fp/either, 4 | sugar 5 | 6 | suite "Try type": 7 | test "Initialization": 8 | check: 1.success.isSuccess == true 9 | check: newException(Exception, "Oops").failure(int).isFailure == true 10 | check: "Oops".failure(int).isFailure == true 11 | 12 | test "Conversion": 13 | let x1 = fromEither(tryST do: 14 | "test" 15 | ) 16 | check: x1.isSuccess and x1.get == "test" 17 | let x2 = fromEither(tryST do: 18 | "test".left(string) 19 | ) 20 | check: x2.isFailure and x2.getErrorMessage == "test" 21 | proc testProc: int = 22 | raise newException(Exception, "test") 23 | let x3 = fromEither(tryET do: 24 | testProc() 25 | ) 26 | check: x3.isFailure and x3.getErrorMessage == "test" 27 | 28 | test "Recover": 29 | check: "test".failure(string).recover(e => e.msg) == "test".success 30 | let x = "test".failure(string).recoverWith(e => (e.msg & "1").failure(string)) 31 | check: x.isFailure and x.getErrorMessage == "test1" 32 | 33 | test "Control structures": 34 | let x1 = tryM do: 35 | 1 36 | check: x1.isSuccess and x1.get == 1 37 | let x2 = tryM do: 38 | discard 39 | check: x2.isSuccess and x2.get == () 40 | let x3 = join(tryM do: 41 | 1.success 42 | ) 43 | check: x3.isSuccess and x3.get == 1 44 | let x4 = join(tryM do: 45 | "Oops".failure(int) 46 | ) 47 | check: x4.isFailure and x4.getErrorMessage == "Oops" 48 | 49 | test "Utilities": 50 | let x = "Oops".failure(int) 51 | let y = x.mapErrorMessage(v => "Error: " & v) 52 | check: y.isFailure 53 | check: y.getErrorMessage == "Error: Oops" 54 | check: x.getErrorStack == y.getErrorStack 55 | echo y.getErrorStack 56 | --------------------------------------------------------------------------------