I would like to have a function behaving like an object, so I could overload an operator on that function. I need the following to be true:
assert 1.fun ^ 2 == fun ^ 2
I know I could use fun() with a default argument but that's that syntax isn't acceptable in this context.
Can I make an object callable by any means?
You can get this behaviour by overloading the experimental () operator on an object type, like so:
{. experimental .}
import math
type
Callable = object
val: int
# Make the thing callable. Needs {. experimental .}
proc `()`(c: Callable; i: int): int = c.val * i
# Make the thing exponentiable.
proc `^`(c: Callable; i: int): int = c.val ^ i
let c = Callable(val: 1)
echo c ^ 2 == 1.c ^ 2 # true!
You could probably object-ify any given function to behave this way using a macro.
EDIT: Here is a macro to automate the above:
{. experimental .}
import macros
macro objfun*(data, function: untyped): untyped =
## Attaches value data to a proc, making that proc
## behave as both a value of the data's type and a
## proc. The data attached to a proc is available
## using the magic identifier `self` inside the proc
## body. Usage as follows, only at the top level,
## because of the converter:
##
## .. code-block:: nim
## proc fun(x: int): int {. objfun: 42 .} = self * x
## doAssert 2 * fun + 1 == 2.fun + 1
var
funcName = function[0].copyNimTree()
objFunType = gensym(nskType)
self = ident"self"
funcBody = function.copyNimTree()
funcBody[0] = ident"()"
funcBody[3].insert(1, newTree(nnkIdentDefs,
self,
objFunType,
newEmptyNode()
))
result = quote do:
type
`objFunType` = object
d: type(`data`)
converter toData(oft: `objFunType`): type(`data`) = oft.d
var `funcName` = `objFunType`(d: `data`)
result.add funcBody
when isMainModule:
import math
proc fun(x: int): int {. objfun: 22 .} = self * x
# This is necessary, as the checks math.`^` does require
# the availability of int to type(fun) conversion.
proc `^`(x: type fun; y: Natural): type y = x.d ^ y
echo fun ^ 2 == 1.fun ^ 2 # true!