Open helmutkian opened 9 years ago
I think we do not have anything like this.
We should provide this and some other helper functions. They can be defined in src/ffi.lisp
. I would like to discuss the API it should have, but having a few of functions would be a good starting point.
So, if you want you can create a pull request for it.
I will also try to create a jscl/ffi
package for that, so we do not need to use cl::
.
That sounds good. I can start by contributing a few functions I would find useful.
I have a few questions to help me along the way.
Is there a way to treat JavaScript functions as data and pass them to higher order functions?
Why does this work:
(funcall #j:alert "test")
But this doesn't
(funcall #j:console:log "test")
Furthermore, is there a way to capture the implicit JavaScript variable this
? Using (cl::%js-vref "this")
always captures window
.
The following also does not work, if I define a function in JavaScript:
function f() { return this.a; }
(defvar *o* (cl::new))
(setf (cl::oget *o* "a") 22)
(setf (cl::oget *o* "g") #j:f)
(funcall (cl::oget *o* "g"))
=> NIL ;; Should return 22
Is there a way to merge this issue with #6 ?
Is there a way to treat JavaScript functions as data and pass them to higher order functions?
What you did is correct. The problem is the capture of this
as you said. So this will also fail (chromium at least)
var x = console.log;
x("test")
The method call syntax (#j:console:log "test")
does work, however, because we take care of preserving this
. If you want to bind the this, you can call call
or apply
method, just like in Javascript:
CL-USER> (lambda (x) (#j:console:log:call #j:console x))
CL-USER> (funcall * "test")
Of course, this is ugly.
So we should decide how we want the FFI to work:
this
automatically, so (#j:obj:method ...)
is always equivalent to (funcall #j:obj:method ...)
? Does it have any bad consequence for Javascript interoperability? If we want to go this way, we should provide a mechanism to override this.this
, providing a bind
macro, like: (bind #j:console:log)
. And it could also be extended to bind parameters,Thoughts?
I like the second option because leverages bind
already in JavaScript.
(funcall (#j:console:log:bind #j:console) "test")
Similarly my example from above works with this mechanism too
function f() { return this.a; }
(defvar *o* (cl::new))
(setf (cl::oget *o* "a") 22)
(setf (cl::oget *o* "g") #j:f)
(funcall ((cl::oget *o* "g" "bind") *o*))
=> 22
Now this works too
(defun log-test (log-fn)
(funcall log-fn "test"))
(log-test (#j:console:log:bind #j:console))
(log-test #'print)
All we then need is a bind
macro, as you said, to simplify this syntax so the previous could be rewritten
(funcall (bind (cl::oget *o* "g")))
(log-test (bind #j:console:log))
There is finally, one last thing: how to capture this
when defining a function in JSCL? I would like to be able to write this in JSCL:
(defun f ()
;;; Currently no mechanism for capturing implicit this
(cl::oget this "a"))
(defvar *o* (cl::new))
(setf (cl::oget *o* "a") 22
(cl::oget *o* "g") #'f)
((cl::oget *o* "g"))
=> 22
Better would be to provide something akin to ClojureScripts this-as
macro (though perhaps with a better name):
(defun f ()
(this-as (self)
(cl::oget self "a")))
I am running into one wrinkle in the implementation though when matching the test case
(function (x) { return x; }).bind(window)(1);
(funcall ((cl::oget (lambda (x) x) "bind") cl::*root*) 1)
=> ERROR[!]: too few arguments
However passing just one additional argument works:
(funcall ((cl::oget (lambda (x) x) "bind") cl::*root*) 'whatever 1)
=> 1
Is this a bug or a case of JSCL lambda
not translating exactly down to JS anonymous function
?
There is finally, one last thing: how to capture this when defining a function in JSCL?
This is tricker, because JSCL introduces many internal functions right now. To convert everything in an expression, which will be fixed sometime soon, and to define proper variable scope, which ES6's let
would help to clean.
We could hack it into lambda
, so it saves this
into an ordinary variable, and provide a special this
symbol, or as-this
macro as you said to access the value.
Done! :-) With this change in the branch https://github.com/davazp/jscl/compare/ffi-improvements I just created, this
is captured.
An example:
var y = {};
(defun f () cl::this)
(setf (cl::oget cl::*root* "y" "test") #'f)
and then
y.test() // => Object { } , actually y
Very cool. It's working great so far!
I've begun pushing to my fork of the ffi-improvements
branch.
https://github.com/helmutkian/jscl/commit/409add7cbb83a095f15036c4c4879e9c14c768e5
There's basic implementations of the bind
macro, a macro for creating anonymous JavaScript objects, an iteration macro and function for JavaScript objects, and a few functions for converting from JavaScript objects to Common Lisp association LISTs and HASH-TABLEs.
Still needs more work, unit tests, and documentation before pull request.
Is there a way to create an initialized or anonymous Javascript object akin to the results of using the object literal syntax in Javascript?
I've been searching around in the source and haven't found anything suitable yet. I have been using the following code as a work around for now
So that
yields a Javascript object equivilent to
{ 'a': 1, 'b': 2 }