From 2951ef4f90aad8a5a002e9a31c09fb39e636a888 Mon Sep 17 00:00:00 2001 From: hlaaftana Date: Sat, 13 Feb 2021 15:54:28 +0300 Subject: [PATCH] modularize, clean up, 0.3.0 --- README.md | 11 ++-- applicates.nimble | 4 +- src/applicates.nim | 120 +++------------------------------- src/applicates/internals.nim | 104 +++++++++++++++++++++++++++++ src/applicates/operators.nim | 7 ++ tests/test_basic.nim | 4 +- tests/test_call_operator.nim | 2 + tests/test_iterator.nim | 2 +- tests/test_iterator_typed.nim | 66 +++++++++---------- 9 files changed, 166 insertions(+), 154 deletions(-) diff --git a/README.md b/README.md index ef56f6f..2d02699 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,18 @@ Would have preferred not using a cache to do this, but for now it should do the ```nim import applicates +# optional operators: +import applicates/operators proc map[T](s: seq[T], f: ApplicateArg): seq[T] = result.newSeq(s.len) for i in 0.. f # does not do above, and injects x into right hand side result[i] = \f(x) result[i] = \x.f result[i] = f(x) # when experimental callOperator is enabled @@ -23,11 +26,11 @@ proc map[T](s: seq[T], f: ApplicateArg): seq[T] = # `applicate do` here generates an anonymous template, so `x - 1` is inlined at AST level: doAssert @[1, 2, 3, 4, 5].map(applicate do (x): x - 1) == @[0, 1, 2, 3, 4] doAssert @[1, 2, 3, 4, 5].map(fromSymbol(succ)) == @[2, 3, 4, 5, 6] -# sugar for `applicate do` syntax (again, best I could come up with): +# optional operators: doAssert @[1, 2, 3, 4, 5].map(x !=> x * 2) == @[2, 4, 6, 8, 10] doAssert @[1, 2, 3, 4, 5].map(x \=> x * 2) == @[2, 4, 6, 8, 10] ``` See tests for more example uses of this library. Tests are ran and docs are built with [nimbleutils](https://github.com/hlaaftana/nimbleutils). -Note: Since `Applicate` is implemented as `distinct int` or `distinct string` and is also usually used as `static Applicate` (for which `ApplicateArg` is an alias), you might have a fair bit of trouble/bugs with the type system. This is unfortunate as it limits the possibilities for type annotated functional programming using applicates. The messiness of Nim's error system when dealing with macros also does not help in this regard. +Note: Since `Applicate` is implemented as `distinct ApplicateKey` and is also usually used as `static Applicate` (for which `ApplicateArg` is an alias), this library fairly pushes Nim's type system, so annotating applicates with types can be difficult. Nim macro errors in general are also not great. diff --git a/applicates.nimble b/applicates.nimble index 2b2d0a0..03780f4 100644 --- a/applicates.nimble +++ b/applicates.nimble @@ -1,6 +1,6 @@ # Package -version = "0.2.1" +version = "0.3.0" author = "hlaaftana" description = "instantiated \"pointers\" to cached AST" license = "MIT" @@ -25,7 +25,7 @@ task tests, "run tests for multiple backends and defines": when declared(runTests): runTests( backends = {c, #[js]#}, - optionCombos = @["", #["-d:applicatesUseMacroCache", "-d:applicatesCacheUseTable"]#] + #optionCombos = @["", "-d:applicatesUseMacroCache", "-d:applicatesCacheUseTable"] ) else: echo "tests task not implemented, need nimbleutils" diff --git a/src/applicates.nim b/src/applicates.nim index 79c5ea6..b63269a 100644 --- a/src/applicates.nim +++ b/src/applicates.nim @@ -1,49 +1,7 @@ import macros -const cacheUseTable = defined(applicatesCacheUseTable) and not defined(nimdoc) -const useCache = defined(applicatesUseMacroCache) and not defined(nimdoc) - -when cacheUseTable: - type ApplicateKey* = string -else: - type ApplicateKey* = int - ## "pointer to" (index of) applicate AST. if you define - ## ``applicatesCacheUseTable`` this will be a string - -type - Applicate* = distinct ApplicateKey - ## distinct version of ApplicateKey - ApplicateArg* = static Applicate - ## `static Applicate` to use for types of arguments - -when useCache: - import macrocache - const applicateCache* = - when cacheUseTable: - CacheTable "applicates.applicates.table" - else: - CacheSeq "applicates.applicates" -elif cacheUseTable: - import tables - var applicateCache* {.compileTime.}: Table[string, NimNode] -else: - var applicateCache* {.compileTime.}: seq[NimNode] - ## the cache containing the routine definition nodes of - ## each applicate. can be indexed by the ID of an applicate to receive - ## its routine node, which is meant to be put in user code and invoked - ## - ## uses a compileTime seq by default, if you define `applicatesUseMacroCache` - ## then it will use Nim's `macrocache` types, if you define - ## `applicatesCacheUseTable` then it will use a `CacheTable` with - ## unique strings - -proc registerApplicate*(node: NimNode): ApplicateKey = - let num = len(applicateCache) - result = when cacheUseTable: $num else: num - when cacheUseTable: - applicateCache[result] = node - else: - add(applicateCache, node) +import applicates/internals +export ApplicateKey, Applicate, ApplicateArg macro makeApplicate*(body): untyped = ## Registers given routine definitions as applicates and @@ -61,9 +19,7 @@ macro makeApplicate*(body): untyped = result = newNimNode(nnkStmtList, body) for st in body: result.add(getAst(makeApplicate(st))) of RoutineNodes: - let num = len(applicateCache) - #let key = when cacheUseTable: $num else: num - # ^ is this even going to work in the future + let num = applicateCount() let b = newNimNode( if body.kind in {nnkDo, nnkLambda}: nnkProcDef @@ -81,7 +37,7 @@ macro makeApplicate*(body): untyped = of nnkMethodDef: nskMethod of nnkFuncDef: nskFunc else: nskTemplate, "appl" & $num) - let key = registerApplicate(b) + let key = registerApplicate(b, num) result = newCall(bindSym"Applicate", newLit(key)) if body[0].kind != nnkEmpty: result = newConstStmt( @@ -89,10 +45,6 @@ macro makeApplicate*(body): untyped = ident repr body[0] else: body[0], result) - #[when cacheUseTable: - applicateCache[key] = b - else: - add(applicateCache, b)]# else: error("cannot turn non-routine into applicate, given kind " & $body.kind, body) @@ -118,7 +70,9 @@ macro makeApplicateFromTyped*(body: typed): untyped = error("cannot turn non-routine into applicate, given kind " & $body.kind, body) macro makeTypedApplicate*(body: untyped): untyped = - ## Injects `used` pragma into `body` and calls `makeApplicateFromTyped`. + ## Calls `makeApplicateFromTyped` without giving an unused warning. + ## + ## Accomplishes this by injecting {.used.}. runnableExamples: makeTypedApplicate: template useIt = @@ -317,35 +271,6 @@ macro toUntyped*(sym: untyped, arity: static int): Applicate = call.add(temp) result = getAst(applicate(params, call)) -proc inferArity*(sym: NimNode): int = - ## infers arity of symbol - ## - ## -1 if sym is not a symbol, -2 if implementation - ## of a symbol was nil, -3 if symbol choice arities - ## do not match - case sym.kind - of nnkSym: - let impl = sym.getImpl - if impl.isNil: return -2 # symbol impl nil - let fparams = impl[3] - for i in 1.. x + y) == 2 - doAssert arity(a !=> a) == 1 - doAssert arity(!=> 3) == 0 - let n = appl.node - case n.kind - of RoutineNodes: - let fparams = n[3] - var res = 0 - for i in 1.. x + 1, incr) + instantiateAs(fromSymbol(system.succ), incr) doAssert incr(1) == 2 proc foo[T](x, y: T): T {.makeApplicate.} = x + y proc bar(x, y: string): string {.makeApplicate.} = x & y @@ -439,7 +340,7 @@ macro apply*(appl: ApplicateArg, args: varargs[untyped]): untyped = ## applies the applicate by injecting the applicate routine ## (if not in scope already) then calling it with the given arguments runnableExamples: - const incr = x !=> x + 1 + const incr = fromSymbol(system.succ) doAssert incr.apply(1) == 2 let a = appl.node case a.kind @@ -481,6 +382,3 @@ macro forceApply*(appl: ApplicateArg, args: varargs[untyped]): untyped = else: result = newCall(a) for arg in args: result.add(arg) - -import applicates/operators -export operators diff --git a/src/applicates/internals.nim b/src/applicates/internals.nim index e69de29..84aef64 100644 --- a/src/applicates/internals.nim +++ b/src/applicates/internals.nim @@ -0,0 +1,104 @@ +import macros + +const cacheUseTable = defined(applicatesCacheUseTable) and not defined(nimdoc) +const useCache = defined(applicatesUseMacroCache) and not defined(nimdoc) + +when cacheUseTable: + type ApplicateKey* = string +else: + type ApplicateKey* = int + ## "pointer to" (index of) applicate AST. if you define + ## ``applicatesCacheUseTable`` this will be a string + +type + Applicate* = distinct ApplicateKey + ## distinct version of ApplicateKey + ApplicateArg* = static Applicate + ## `static Applicate` to use for types of arguments + +when useCache: + import macrocache + const applicateCache* = + when cacheUseTable: + CacheTable "applicates.applicates.table" + else: + CacheSeq "applicates.applicates" +elif cacheUseTable: + import tables + var applicateCache* {.compileTime.}: Table[string, NimNode] +else: + var applicateCache* {.compileTime.}: seq[NimNode] + ## the cache containing the routine definition nodes of + ## each applicate. can be indexed by the ID of an applicate to receive + ## its routine node, which is meant to be put in user code and invoked + ## + ## uses a compileTime seq by default, if you define `applicatesUseMacroCache` + ## then it will use Nim's `macrocache` types, if you define + ## `applicatesCacheUseTable` then it will use a `CacheTable` with + ## unique strings + +template applicateCount*(): int = # this breaks when `proc {.compileTime.}` + ## total number of registered applicates + len(applicateCache) + +proc registerApplicate*(node: NimNode, num: int = applicateCount()): ApplicateKey {.compileTime.} = + result = when cacheUseTable: $num else: num + # ^ is this even going to work in the future + when cacheUseTable: + applicateCache[result] = copy node + else: + add(applicateCache, copy node) + +proc inferArity*(sym: NimNode): int = + ## infers arity of symbol + ## + ## -1 if sym is not a symbol, -2 if implementation + ## of a symbol was nil, -3 if symbol choice arities + ## do not match + case sym.kind + of nnkSym: + let impl = sym.getImpl + if impl.isNil: return -2 # symbol impl nil + let fparams = impl[3] + for i in 1.. x + y)) == 2 + doAssert static(arity(a !=> a)) == 1 + doAssert static(arity(!=> 3)) == 0 + let n = appl.node + case n.kind + of RoutineNodes: + let fparams = n[3] + var res = 0 + for i in 1..`*(params, body): untyped = ## infix version of `applicate`, same parameter syntax runnableExamples: + import ../applicates doAssert (x !=> x + 1).apply(2) == 3 const foo = (a, b) !=> a + b doAssert foo.apply(1, 2) == 3 @@ -15,6 +16,7 @@ template `!=>`*(body): untyped = template `\=>`*(params, body): untyped = ## alias for `!=>` runnableExamples: + import ../applicates doAssert (x \=> x + 1).apply(2) == 3 const foo = (a, b) \=> a + b doAssert foo.apply(1, 2) == 3 @@ -44,6 +46,7 @@ macro `|`*(appl: ApplicateArg, arg: untyped): untyped = ## ## note that you can undefine operators you don't want via ``import except`` runnableExamples: + import ../applicates doAssert (x !=> x + 1) | 1 == 2 const foo = x !=> x + 1 doAssert foo | 1 == 2 @@ -106,6 +109,8 @@ macro `|>`*(value, call): untyped = ## ## if call is a dot call, it is not modified, so value becomes the second argument in the call runnableExamples: + import ../applicates + const incr = fromSymbol(system.succ) const multiply = fromSymbol(`*`) const divide = fromSymbol(`/`) @@ -120,6 +125,8 @@ macro `|>`*(value, call): untyped = macro chain*(initial, calls): untyped = ## statement list chained version of `|>` runnableExamples: + import ../applicates + const incr = fromSymbol(system.succ) const multiply = fromSymbol(`*`) const divide = fromSymbol(`/`) diff --git a/tests/test_basic.nim b/tests/test_basic.nim index 07b32af..fe976a2 100644 --- a/tests/test_basic.nim +++ b/tests/test_basic.nim @@ -52,7 +52,9 @@ test "applicate macro and apply": named.apply(upperA, char, 65) doAssert upperA == 'A' - + +import applicates/operators + test "operators": check (x !=> x[^1]) | "abc" == 'c' check \((name, value) \=> (let name = value; name))(a, 3) == 3 diff --git a/tests/test_call_operator.nim b/tests/test_call_operator.nim index d5e8abe..e636286 100644 --- a/tests/test_call_operator.nim +++ b/tests/test_call_operator.nim @@ -2,6 +2,8 @@ import unittest, applicates +from applicates/operators import `()` + test "call operator works": applicate double do (x): x * 2 check double(3) == 6 diff --git a/tests/test_iterator.nim b/tests/test_iterator.nim index 8b81f0b..1f1d947 100644 --- a/tests/test_iterator.nim +++ b/tests/test_iterator.nim @@ -1,4 +1,4 @@ -import unittest, applicates +import unittest, applicates, applicates/operators applicate iter(iter: untyped): applicate (agg: static Applicate): diff --git a/tests/test_iterator_typed.nim b/tests/test_iterator_typed.nim index 337dbaa..304b598 100644 --- a/tests/test_iterator_typed.nim +++ b/tests/test_iterator_typed.nim @@ -1,4 +1,5 @@ import unittest, applicates, macros +from applicates/operators import chain type Iterator = distinct Applicate @@ -17,36 +18,34 @@ const iter = applicate do (iter: untyped) -> Iterator: # iter here is Iterable const filter = applicate do (iter: static Iterator, f: static Applicate) -> Iterator: itr do (agg: static Applicate): - iter.propagate(x \=> (block: + iter.propagate(applicate do (x): let x1 = x if f.apply(x1): - agg.apply(x1))) + agg.apply(x1)) const map = applicate do (iter: static Iterator, f: static Applicate) -> Iterator: itr do (agg: static Applicate): - iter.propagate(x \=> agg.apply(f.apply(x))) + iter.propagate(applicate do (x): agg.apply(f.apply(x))) const each = applicate do (iter: static Iterator, f: static Applicate): iter.propagate(f) test "single map": var s: seq[int] - map.apply(iter.apply(0..4), x \=> x + 1).propagate(x \=> s.add(x)) + map.apply(iter.apply(0..4), applicate do (x): x + 1).propagate(applicate do (x): s.add(x)) check s == @[1, 2, 3, 4, 5] test "filter map use": var s: seq[int] - const goo = map.apply(filter.apply(iter.apply(-7..11), x \=> x mod 3 == 0), x \=> x + 2) - goo.apply(x \=> s.add(x)) + const goo = map.apply(filter.apply(iter.apply(-7..11), applicate do (x): x mod 3 == 0), applicate do (x): x + 2) + goo.apply(applicate do (x): s.add(x)) check s == @[-4, -1, 2, 5, 8, 11] var s2: seq[int] - (( - ( - (-7..11) |< iter, - x \=> x mod 3 == 0 - ) |< filter, - x \=> x + 2 - ) |< map).propagate(x \=> s2.add(x)) + chain -7..11: + iter + filter(applicate do (x): x mod 3 == 0) + map(applicate do (x): x + 2) + each(applicate do (x): s2.add(x)) check s2 == s import macros @@ -58,10 +57,10 @@ macro iterate(init, st): untyped = test "iterate": var s: seq[int] iterate -7..11: - filter(x \=> x mod 3 == 0) - map(x \=> x + 2) - filter(x \=> x > 0) - each(x \=> s.add(x)) + filter(applicate do (x): x mod 3 == 0) + map(applicate do (x): x + 2) + filter(applicate do (x): x > 0) + each(applicate do (x): s.add(x)) check s == @[2, 5, 8, 11] const collect = applicate do (iter: static Iterator): @@ -70,52 +69,49 @@ const collect = applicate do (iter: static Iterator): result = newEmptyNode() elemType = ty if false: - iter.apply(x \=> storeElemType(typeof(x))) + iter.apply(applicate do (x): storeElemType(typeof(x))) macro getElemType(): untyped {.gensym.} = result = elemType var s: seq[getElemType()] - iter.propagate(x \=> s.add(x)) + iter.propagate(applicate do (x): s.add(x)) s const enumerate = applicate do (iter: static Iterator): itr do (agg: static Applicate): var i = 0 - iter.apply: - applicate do (x): - agg.apply((i, x)) - inc i + iter.apply(applicate do (x): + agg.apply((i, x)) + inc i) from algorithm import reversed test "collect and fold": let s = iterate -7..11: - filter(x \=> x mod 3 == 0) - map(x \=> x + 2) + filter(applicate do (x): x mod 3 == 0) + map(applicate do (x): x + 2) enumerate - filter(x \=> x[1] > 0) - map(x \=> (x[0], $x[1])) + filter(applicate do (x): x[1] > 0) + map(applicate do (x): (x[0], $x[1])) enumerate collect check s == @[(0, (2, "2")), (1, (3, "5")), (2, (4, "8")), (3, (5, "11"))] const fold = applicate do (iter: static Iterator, init: untyped, op: static Applicate): var a = init - iter.propagate(x \=> (a = op.apply((a, x)))) + iter.propagate(applicate do (x): (a = op.apply((a, x)))) a let s2 = iterate reversed(s): - map(x \=> x[1][1]) - fold("", x \=> x[0] & x[1]) + map(applicate do (x): x[1][1]) + fold("", applicate do (x): x[0] & x[1]) check s2 == "11852" test "yield": iterator foo[T](x: T): auto = iterate x: - filter(x \=> x mod 3 == 0) - map(x \=> x + 2) - each: - \=> x: - yield x + filter(applicate do (x): x mod 3 == 0) + map(applicate do (x): x + 2) + each(applicate do (x): yield x) var s: seq[int] for x in foo(-7..11):