Skip to content

new: sugar.chainOn, chainEval #13245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@

- Added `os.isRelativeTo` to tell whether a path is relative to another

- Added `sugar.chainOn` and `sugar.chainEval` for easy function chaining that's available
everywhere, there is no need to concern your APIs with returning the first argument
to enable "chaining" (as in the builder pattern), instead use this.

## Library changes

- `asyncdispatch.drain` now properly takes into account `selector.hasPendingOperations`
Expand Down
70 changes: 70 additions & 0 deletions lib/pure/sugar.nim
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,73 @@ when (NimMajor, NimMinor) >= (1, 1):
of "bird": "word"
else: d
assert z == @["word", "word"]


macro chainOn*(lhs: typed, calls: varargs[untyped]) {.since: (1, 1).} =
## This macro provides the `chaining`:idx: of function calls.
## It does so by patching every call in `calls` to use `x` as the first argument.
## This advantageously replaces the "builder pattern" seen in other languages.
## **This evaluates `x` multiple times!**
## See also `chainEval` for an outplace version.
runnableExamples:
import std/strutils
var x = "aa"
chainOn x:
add "bb"
addQuoted "cc"
add 'd'
doAssert x == """aabb"cc"d"""
var a = 44
a.chainOn:
+= 4
-= 5
doAssert a == 43
proc mult2[T](b: var T) = b *= 2
# can also pass as varargs:
a.chainOn(+= 10, mult2(), *= 100)
doAssert a == (43+10)*2*100
a.chainOn(*= 0)
doAssert a == 0

type Foo = object
x1, x2: int
var foo: Foo
foo.chainOn(x1 = 1, x1 += 3, x2 = 5)
doAssert foo == Foo(x1: 1+3, x2: 5)

result = newStmtList()
# non-recursive processing because that's exactly what we need here:
let calls2 = if calls.len == 1 and calls[0].kind == nnkStmtList: calls[0] else: calls

for y in calls2:
var call: NimNode
case y.kind
of nnkExprEqExpr: # x1 = expr => _.x1 = expr
call = newTree(nnkAsgn)
call.add newTree(nnkDotExpr, [lhs, y[0]])
call.add y[1]
of nnkInfix: # x1 *= expr => _.x1 *= expr
call = copyNimTree(y)
call[1] = newTree(nnkDotExpr, [lhs, y[1]])
else: # *= expr => _ *= expr
expectKind y, nnkCallKinds
var kind2 = y.kind
if kind2 == nnkPrefix: kind2 = nnkInfix
call = newTree(kind2, [y[0], lhs])
for j in 1..<y.len: call.add y[j]
result.add call

since (1, 1):
template chainEval*(lhs: typed, calls: varargs[untyped]): untyped =
## evaluates `lhs` (once), applies each consecutive call in `calls` in a
## similar way as `chainOn`, and returns the result
runnableExamples:
doAssert (5+5).chainEval(*= 2, += 100) == ((5+5)*2) + 100
## examples showing method call syntax chain mixed with chainEval
doAssert 10.chainEval(*= 2).float.chainEval(*= 3.5) == float(10*2)*3.5
import std/[strutils, algorithm]
doAssert 10.chainEval(*= 2).`$`.chainEval(add("cba"), sort()) == "02abc"
block:
var x = lhs
chainOn(x, calls)
x