Hi!
First let me state that I'm entirely new to Nimrod, and so far I like it a lot.
I'm toying with a high-level interface to python, over here: https://github.com/micklat/NimBorg It build upon the C api bindings that Araq made, and tries to provide a nicer interface.
So far it doesn't do much. Here's an example program that works:
https://github.com/micklat/NimBorg/blob/master/test/test_py.nim
As you can see in this example, it's possible to import python modules, access objects and call functions. The error handling is very basic - any time you get an exception on the python side, you should get some print-out to stdout and an exception on the Nimrod side.
Currently, the nimrod compiler fails with an internal error on one of the test programs. This is reported as https://github.com/Araq/Nimrod/issues/888 .
Although it's currently too limited for serious work, I show it to the list because I'd like to get some suggestions about the member access syntax. Currently, I use the syntax x->y to get member y of object x. The biggest problem with this syntax is that -> has lower precedence than function invocation, so intead of doing something like:
let os = py_import("os")
let cwd = os->getcwd() # doesn't work because () binds stronger than ->
I have to write:
let os = py_import("os")
let cwd = (os->getcwd)()
Which is displeasing.
I would have preferred to overload the dot operator, but that doesn't seem to be possible.
Any ideas?
Also, if anyone would like to provide general feedback on this toy (my first Nimrod project), I'd be glad to receive it.
Cheers, Micky
No ideas about ->. On the other hand, since you are already using Python and will require it at runtime, I wonder if using its introspection might be possible to run a python script which given a python module scans it for useful functions (with dir) and generates a .nim module which can be imported.
This way you can get rid of the -> requirement, since you could presumably be doing stuff like:
import pylib/os
let cwd = getcwd()
In theory you could also generate docstrings, and would get completion from IDEs like Aporia.You should look into the delegator pragma. http://build.nimrod-lang.org/docs/manual.html#delegator-pragma
It's intended exactly for use cases like interfacing with dynamic languages and it will allow you to use the preferred dot syntax. Keep in mind that it's still quite experimental and in the future it may be repackaged as direct overloading of the dot operator.
Thanks for the excellent advice, gradha & zahary.
I'm trying the delegator approach first because it seems like an excellent fit, and sounds much simpler to implement than the other approach. However, the compiler repels my advances. It accepts a definition such as:
proc `()`*(field: string, obj: PPyRef, args: varargs[PPyRef]): PPyRef = echo field, " in argful delegator" let args_tup = to_tuple(args) PyObject_CallObject(getattr(obj.p, field).p, args_tup.p)
But if I try to add the pragma
{.delegator.}
just before the equality, then I get a compiler error saying:
py2.nim(97, 9) Error: identifier expected, but found '`()`*'
... which seems a bit dishonest on the compiler's part. Is this a compiler bug?
The whole file can be seen here: https://gist.github.com/micklat/8904320
let math = py_import("math") ~my_list.append(math.sin(x))
and the little "snake" in front of "my_list" causes it to be rewritten as:
getattr(my_list, "append")(getattr(math, "sin")(x))
and it works.
proc `.`*(obj: PPyRef, field: string, args: varargs[PPyRef]): PPyRef =
...
I wasn't around when it was decided that the delegator pragma should handle both member access and parens. May I ask why the two should be coupled (rather than overloading . and () in separate?)
update: presumably it's because a.b might be either member access or b(a).
Well, the key difference is that with the dot operator, you get an additional param indicating the field (or proc) name. Overloading () is also supported, but it works in more traditional fashion allowing you to define some kind of callable object.
When interfacing with a dynamic language, it may be useful to treat a.x and a.x() differently, so I'll think about ways to differentiate between these.
I'm happy to announce that the delegator pragma now graduates to a more official syntax using the dot operator and supports couple of new features such as intercepting assignments to missing fields and discriminating between x.y and x.y().
You'll find the relevant documentation here:
https://github.com/Araq/Nimrod/commit/435cb1abddcdfe388b34df8ae30acecee1ecbe0e#diff-4
Working example code can be found in the test suite here:
https://github.com/Araq/Nimrod/blob/devel/tests/specialops/tdotops.nim
It's great having my library's needs met like this. However, here's a case that does not seem to work:
type
W = object
s: string
proc `.`(x: W, s: string): W =
W(s: x.s & s)
proc `()`(x: W, i: int): W = W(s: x.s[0..i-1])
var
a = W(s: "a")
const wannaFail = true
when wannaFail:
echo a.q(2).b.g # Error: type mismatch: got (string, int literal(1))
else: # same thing, spelled-out
let aq = a.q
let aq1 = aq(2)
echo aq1.b.g # works correctly
Let's get to the Python's and Lua's specifics here.
a.q(2) will be transformed to `.`(a, "q", 2). I think it's better if you provide a varargs handler that will directly place the call, instead of going through an intermediate object storing the "q" function. With such intermediate references, you always have to worry about lifetime issues and you'll end up keeping external refcounts for the script's objects.
With that said, I'll think about potential ways to add more control for this "field vs method call" ambiguity.
In the general case, I'd suggest that when an .() operator is not defined, a.q(2) should be treated as (a.q)(2).
Regarding python and lua, there's no need to worry about the refcounts, they are handled automatically as usual. I've defined a .() operator to overcome the problem in my previous message, and most things seem to work. There are still a few problems with the compiler's behaviour, but they are apparently independent of the dot operators.