Open kfish opened 10 years ago
This might be an omission in the runtime for serializing the fay monad, i'll look into it!
As a side note, calling the FFI from the strictness wrapper, pretty funky stuff :) But I think it'll work.
I added that test case, but I also tested this one which doesn't work, it may be the same issue:
module StrictWrapper where
clog2 :: Int -> Double -> Fay ()
clog2 = ffi "(function () { console.log(%1, %2); })()"
main :: Fay ()
main = (ffi "Strict.StrictWrapper.clog2(234)" :: Double -> Fay ()) 345
It might be the same as faylang/fay#267 too.
Could you try making a smaller reproduction of this please?
The following 3 files are intended to generate a minimal valid angular controller that is initialized via a function call. It fails wtih
Error: Argument 'ConstCtrl' is not a function, got Fay$$Monad
AngularConst.hs (build with "fay --package fay-text AngularConst.hs --strict AngularConst"):
module AngularConst where
import FFI
import Prelude
constController :: Int -> Fay ()
constController = ffi "\
\ (function($scope) { \
\ $scope.val = %1; \
\ console.log($scope.val); \
\ $scope.getVal = function () { return $scope.val }; \
\ })"
const.js:
ConstCtrl = Strict.AngularConst.constController(7);
index.html:
<!doctype html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
<script src="AngularConst.js"></script>
<script src="const.js"></script>
</head>
<body>
<h2>Constant</h2>
<div ng-controller="ConstCtrl">
<span>Initialized with {{val}}, returns {{getVal()}}</span>
</div>
</body>
</html>
The generated code is:
AngularConst.constController = function($p1){
return new Fay$$$(function(){
return new Fay$$Monad(Fay$$jsToFay(["unknown"], (function($scope) { $scope.val = Fay$$fayToJs_int($p1); console.log($scope.val); $scope.getVal = function () { return $scope.val }; })));
});
};
By comparison, a working javascript version of const.js is:
mkConstController = function (val) {
return (function ($scope) {
$scope.val = val;
console.log($scope.val);
$scope.getVal = function () { return $scope.val };
})
}
ConstCtrl = mkConstController(7);
Try the second commit, it seems to do the trick. I have the same fay module as you:
module AngularConst where
import FFI
import Prelude
constController :: Int -> Fay ()
constController = ffi "\
\ (function($scope) { \
\ $scope.val = %1; \
\ console.log($scope.val); \
\ $scope.getVal = function () { return $scope.val }; \
\ })"
This html:
<!doctype html>
<html>
<head>
<script src="AngularConst.js"></script>
<script>
var c = Strict.AngularConst.constController(7);
var o = {};
var r = c(o);
</script>
</head>
<body>
</body>
</html>
Compiling with fay --pretty --Wall --strict AngularConst --library AngularConst.hs --runtime-path js/runtime.js
(--runtime-path
is just so you don't have to recompile fay when you modify runtime.js)
This logs 7
, o.val => 7
, o.getVal() => 7
Yes, that does load without errors, and if I modify the generated constController to log when it is called, it gets called.
When loaded with Angular though, the generated code does not seem to be recognized as an Angular controller. The generated code is:
> console.log(ConstCtrl)
function (){
var fayFunc = fayObj;
var return_type = args[args.length-1];
var len = args.length;
// If some arguments.
if (len > 1) {
// Apply to all the arguments.
fayFunc = Fay$$_(fayFunc,true);
// TODO: Perhaps we should throw an error when JS
// passes more arguments than Haskell accepts.
// Unserialize the JS values to Fay for the Fay callback.
if (args == "automatic_function")
{
for (var i = 0; i < arguments.length; i++) {
fayFunc = Fay$$fayToJs(["automatic"], Fay$$_(fayFunc(Fay$$jsToFay(["automatic"],arguments[i])),true));
}
return fayFunc;
}
for (var i = 0, len = len; i < len - 1 && fayFunc instanceof Function; i++) {
fayFunc = Fay$$_(fayFunc(Fay$$jsToFay(args[i],arguments[i])),true);
}
// Finally, serialize the Fay return value back to JS.
var return_base = return_type[0];
var return_args = return_type[1];
// If it's a monadic return value, get the value instead.
if(return_base == "action") {
return Fay$$fayToJs(return_args[0],fayFunc.value);
}
// Otherwise just serialize the value direct.
else {
return Fay$$fayToJs(return_type,fayFunc);
}
} else {
throw new Error("Nullary function?");
}
}
This doesn't look like the function($scope) {..} that Angular is expecting. Also I see no mention of the captured 7, but I don't really understand what is going on :)
That's the jsToFay code for functions, it partially applies the arguments it gets to the original fay function you are serializing. It's supposed to be called as a normal function. Can you tell why angular doesn't accept it?
c
is the same thing in my example by the way. The runtime has to do this wrapping since it doesn't know how many arguments the fay function expects.
My guess is that Angular expects a function argument called exactly "$scope"; if I modify a javascript Angular controller and rename the $scope argument, Angular fails to load the controller.
@btford is this the case, or do you have any suggestions about how we should interface with Angular?
Ehm, i'm not sure what to think of this :) http://stackoverflow.com/a/12108723/182603
Oh, I forgot about this detail:
Strict.AngularConst.constController(7)()
=>
function ($scope) { $scope.val = Fay$$fayToJs_int($p1); console.log($scope.val); $scope.getVal = function () { return $scope.val }; }
though i think this won't work in general. I have to think more about serializing actions before I know what the implementation should really be.
@kfish Angular infers the name based on the function parameters, unless you add annotations. This is convenient when developing, because it frees you from having to maintain two lists of dependencies for a controller and keep them in sync. However, you probably want to avoid that in a case like this. :)
function MyCtrl ($scope) {
// ...
}
Is the same as:
function MyCtrl (foo) {
// $scope aliased to foo
}.$inject = ['$scope'];
More info here: http://docs.angularjs.org/guide/di#dependency-injection_dependency-annotation_-annotation
My guess is that Angular expects a function argument called exactly\ "$scope"
that's exactly right. that can be mitigated when using explicit dependency injection. see "dependency annotation" in http://docs.angularjs.org/guide/di for two ways to pass them as strings (which fay should not mangle and which are nice for minification as well).
Thanks for the input! Happy to see that there are other ways of doing it :)
I'm not sure if the other approaches will make much difference, what we have are functions generated dynamically by fay so we can't force structure on it (as in naming arguments or assigning properties to them). But like I said, I need to experiment some more with the serialization to see what it should actually look like.
@kfish you can use what i wrote above for now at least.
@bergmark wow yes, just adding an extra () in const.js works; the serialized function is generated, including the verbatim "$scope" as needed. I'll try to modify the StateController similarly ...
@btford @ibotty thanks for your advice!
I thought a bit more about it, this only works here because we have the function as an ffi string. I'm not sure what your plans are, but if you try to create these controllers from normal fay code you will end up with unnamed functions and this won't work. We'd need to be able to tell angular "yes this is really a controller" somehow, or wrap the fay action through js or the ffi before passing it to angular.
In commit 2956a4ba (branch example-templating) I'm trying to generate Angular controller code via Fay ffi,
In src/Angular.hs there is:
which is called in examples/todo/todo.js:
However this fails with:
The goal is to generate all the Angular code required for a Controller (and later, for a Directive etc.) from a Haskell specification, so fay-angular would need to generate various javascript classes/functions that are structured in the required way, with argument name $scope etc. Any suggestions about where the best place to implement this would be? is it possible from within a fay library, or will it require some modification to the fay compiler itself?