I have the following piece of F#:
let followsLensLaws lens outer inner dummy f = prop {
if inner = outer then
do! Assert.pass
do! Assert.pass
do! Assert.pass
do! Assert.pass
else
do! getSetIdentity lens outer
do! setGetSymmetry lens outer inner
do! setSetOrderDependence lens outer inner dummy
do! getSetMapCorrespondence lens f outer
}
which when I generate JS from it does something wrong. I'm not entirely sure what exactly it does wrong yet as the output is fairly unreadable, but what I do know is that when getSetMapCorrespondence is called, f is undefined, whereas when it was passed into the followsLensLaws it was not. The output JS looks like this:
const followsLensLaws = __exports.followsLensLaws = function (lens_0, lens_1, outer, inner, dummy, f) {
const lens = [lens_0, lens_1];
return function (builder_) {
return (0, _Util.equals)(inner, outer) ? builder_.Bind_1(_assert.Assert.pass, function () {
return builder_.Bind_1(_assert.Assert.pass, function () {
return builder_.Bind_1(_assert.Assert.pass, function () {
return builder_.Bind_1(_assert.Assert.pass, function () {
return builder_.Return();
});
});
});
}) : builder_.Bind_0(function (tupledArg) {
const lens_2 = [tupledArg[0], tupledArg[1]];
const f_1 = function (lensGet, lensSet, outer_1) {
return getSetIdentityWith(lensGet, lensSet, outer_1);
};
const lens_3 = [lens_2[0], lens_2[1]];
return (($var1, $var2) => $var3 => f_1($var1, $var2, $var3))(get(lens_3[0], lens_3[1]), set(lens_3[0], lens_3[1]));
}(lens)(outer), function () {
return builder_.Bind_0(function (tupledArg_1) {
const lens_4 = [tupledArg_1[0], tupledArg_1[1]];
const f_2 = function (lensGet_1, lensSet_1, outer_2, inner_1) {
return setGetSymmetryWith(lensGet_1, lensSet_1, outer_2, inner_1);
};
const lens_5 = [lens_4[0], lens_4[1]];
return (($var4, $var5) => ($var6, $var7) => f_2($var4, $var5, $var6, $var7))(get(lens_5[0], lens_5[1]), set(lens_5[0], lens_5[1]));
}(lens)(outer, inner), function () {
return builder_.Bind_0(function (tupledArg_2) {
const lens_6 = [tupledArg_2[0], tupledArg_2[1]];
const lensSet_2 = set(lens_6[0], lens_6[1]);
return function (outer_3, inner_2, dummy_1) {
return setSetOrderDependenceWith(lensSet_2, outer_3, inner_2, dummy_1);
};
}(lens)(outer, inner, dummy), function () {
return builder_.Bind_0(function (tupledArg_3) {
const lens_7 = [tupledArg_3[0], tupledArg_3[1]];
return function (f_3, tupledArg_4) {
const lens_8 = [tupledArg_4[0], tupledArg_4[1]];
return (($var8, $var9) => ($var10, $var11, $var12) => f_3($var8, $var9, $var10, $var11, $var12))(get(lens_8[0], lens_8[1]), set(lens_8[0], lens_8[1]));
}(function (lensGet_2, lensSet_3, lensMap, f_4, outer_4) {
return getSetMapCorrespondenceWith(lensGet_2, lensSet_3, lensMap, f_4, outer_4);
}, lens_7)(function (f_5, o) {
return map(lens_7[0], lens_7[1], f_5, o);
});
}(lens)(f, outer), function () {
return builder_.Return();
});
});
});
});
}(_testcheck.SpecBuilderInst.prop);
};
I'm trying to trace what happens to the f variable through this, but it's somewhat hard considering the output is difficult to follow.
Do you have the full code published somewhere? I am wondering why there are 6 parameters in the javascript while you have only 5 parameters in the function followsLensLaws
I've changed the code slightly trying to track this down, though most of what I did was change some inline functions to be not inline, and it did not help with the problem. I also renamed f to mapFn so as to easier distinguish it from the other fs.
@Alxandr Could you please include the instructions to reproduce the error? If possible also how to run the code in .NET or at least which would be the expected result.
The code can't be run in .NET. You would end up attempting to evaluate jsNatives.
As for reproducing it, me and @Zaid-Ajaj had a somewhat lengthy talk yesterday on gitter which ended up with him producing the same result. To repeat what we figured out here:
Prerequisites:
Building the easy (and slow) way. You likely need to do this once first before the fast way can be used:
build.sh or build.bat script in the root directory of the project.Building the fast way (might need the slow way having been used once):
FABLE_SERVER_PORT=<port> node start in the root directory of the project on a bash environment or similar. On CMD on windows this syntax doesn't work and you have to either set the environment variable the way you set environment variables on windows (sorry, I don't know the syntax for this), or modify the build.js script here with the correct port number.Building the really fast way (file watching):
build.js. If you copy build.js from here you can follow the steps from "Building the fast way", but instead run FABLE_SERVER_PORT=<port> node start watch. This will rebuild automatically any fs file you change while it's running.Trying to pin down the problem here, I see this call stack:
|
getSetMapCorrespondence
|
getSetMapCorrespondenceWith
|
end
followsLensLaws in the first place providing mapFn?I am guessing you are using the TestCheck.js library combined with ava-check which is SOMEHOW generating a function mapFn for you.
Looking at this:
/// Creates a Generator which will always generate the provided value.
[<Import("gen", from="ava-check"); Emit("$0.return($1)")>]
let ret (x: 'a): NativeGenerator<'a> = jsNative
which you call from:
let bind f (Generator g) = g.Then (f >> unpack) |> Generator
let unit x = Generator <| NativeGen.ret x
let map f g = bind (f >> unit) g
which is then used here:
[<RequireQualifiedAccess>]
module Prop =
let create (name: string) (g: Generator<'a>) (fn: 'a -> SyncSpec<unit>) =
Hijack.hijack (Check.check (Generator.unpack g, System.Func<_,_,_> (fun t a -> SyncSpec.run (fn a) (SpecContext t))))
|> Fable.Import.Ava.Test.create name
Did you test that this actually works before using it from Aether? (does it affect Aether in the first place?)
I can't find anything helpful here :(
can you make the issue a bit more specific? There are a lot of place where it could have gone wrong, my hunch says that wrong js generation is the least probable cause
Ok, found the caller of followsLensLaws
So, I drastically simplified the file in question down to this:
No computational expressions, no randomised parameters etc. Still same error though.
Ok, so I've found how to fix it, yet I don't know yet what's causing it.
The following code works:
let getSetMapCorrespondence lens =
let unwrappedFn = unwrapLens getSetMapCorrespondenceWith lens
unwrappedFn (map lens)
However, if I get rid of the temp variable and turn it into this:
let getSetMapCorrespondence lens =
unwrapLens getSetMapCorrespondenceWith lens (map lens)
Presto, we have a crash.
Here is working JS:
const getSetMapCorrespondence = __exports.getSetMapCorrespondence = function (lens_0, lens_1) {
const lens = [lens_0, lens_1];
let unwrappedFn;
const f = function (lensGet, lensSet, lensMap, mapFn, outer) {
return getSetMapCorrespondenceWith(lensGet, lensSet, lensMap, mapFn, outer);
};
unwrappedFn = unwrapLens(($var1, $var2) => ($var3, $var4, $var5) => f($var1, $var2, $var3, $var4, $var5), lens[0], lens[1]);
return ($var6, $var7) => unwrappedFn(function (tranform, graph) {
return map(lens[0], lens[1], tranform, graph);
}, $var6, $var7);
};
and non-working js
const getSetMapCorrespondence = __exports.getSetMapCorrespondence = function (lens_0, lens_1) {
const lens = [lens_0, lens_1];
return function (f, tupledArg) {
return unwrapLens(($var1, $var2) => ($var3, $var4, $var5) => f($var1, $var2, $var3, $var4, $var5), tupledArg[0], tupledArg[1]);
}(function (lensGet, lensSet, lensMap, mapFn, outer) {
return getSetMapCorrespondenceWith(lensGet, lensSet, lensMap, mapFn, outer);
}, lens)(function (tranform, graph) {
return map(lens[0], lens[1], tranform, graph);
});
};
@Alxandr Alriigghht!! we are getting somewhere :smile:
in the non-working js version it seems that getSetMapCorrespondenceWith is not called correctly:
followsLensLaws args
{ '0': [Function],
'1': [Function],
'2': 0,
'3': 1,
'4': 2,
'5': [Function] }
getSetMapCorrespondence args:
{ '0': [Function], '1': [Function] }
unwrapLends args:
{ '0': [Function], '1': [Function], '2': [Function] }
getSetMapCorrespondenceWith args:
{ '0': [Function],
'1': [Function],
'2': [Function],
'3': undefined, // <---- mapFn
'4': undefined // <---- outer
}
@alfonsogarciacaro this looks suspeciuosly like closure-optimizing bug. getSetMapCorrespondenceWith should have been called with 5 paremeters but instead it was called with 3 (in the non-working js verison)
Yeah, figured it was something like that. I tried to reduce the case further, but this code is so generic (I just stole it from Aether) that my mind spins just trying to figure out what is calling what.
Btw; this also works:
let inline getSetMapCorrespondence lens = unwrapLens getSetMapCorrespondenceWith lens <| (map lens)
whereas if I remove the <| it fails.
So, thanks to @ed-ilyin I managed to get a better handle on what's actually the issue here, and it's way worse than I actually though. I've created a rather simple showcase which will likely help in debugging:
let curry (fn: 'a -> 'b -> 'c) =
let first = fun (a: 'a) ->
let second = fun (b: 'b) ->
let result = fn a b
result
second
first
let fn1 a b = printfn "%s %s" a b
let cfn1 = curry fn1
fn1 "a" "b"
cfn1 "a" "b"
This should print the string a b twice, yet does not. It seems fable just assumes that all functions has been uncurried without actually having the type information available, which can lead to incredibly hard to debug issues. For instance, @ed-ilyin had a typical ap2 function, something like this:
module List =
// The apply function for lists
// [f;g] apply [x;y] becomes [f x; f y; g x; g y]
let apply (fList: ('a->'b) list) (xList: 'a list) =
[ for f in fList do
for x in xList do
yield f x ]
let curry (fn: 'a -> 'b -> 'c) =
let first = fun (a: 'a) ->
let second = fun (b: 'b) ->
let result = fn a b
result
second
first
let fns = [curry (+)]
let ap1 = List.apply fns [1]
let ap2 = List.apply ap1 [2]
printfn "%A" ap2
If you remove the curry function from the code above it will crash. With it it's working just fine.
@Alxandr You're right, it seems to generate at call site cfn1("a", "b") instead of cfn1("a")("b")
@ncave Yep. I just ran into the issue again, and this one I don't think I can code around. This really has to be fixed :(
Is this still not solved? I can't use fable if there is a chance that it calls my functions wrong just because I curry them.
@Alxandr The edge cases we came across so far are solved, please update to latest fable and try again.
Closing as this should have been solved by #980, please feel free to reopen if there is still some failing reproducible code :+1: