Partially applied function as a record field is incorrectly curried when the first argument is unit
See original GitHub issue(boy, that title is a mouthful)
Description
Another one of those weird partial application bugs, here we go!
Consider the following snippet that works just fine:
let fn (x: unit) (y: string) = y.Length
let result = fn () "hello"
printfn "result = %d" result // result = 5
let partiallyApplied = fn ()
let secondResult = partiallyApplied "hello"
printfn "secondResult = %d" secondResult // secondResult = 5
is compiled to the following (using REPL):
export function fn(x, y) {
return y.length | 0;
}
export const result = fn(null, "hello");
toConsole(printf("result = %d"))(result);
export const partiallyApplied = CurriedLambda(function (y) {
return fn(null, y);
});
export const secondResult = partiallyApplied("hello");
toConsole(printf("secondResult = %d"))(secondResult);
Carefully notice the part where the partiallyApplied
function is defined, it correctly passes null
where unit
is expected:
export const partiallyApplied = CurriedLambda(function (y) {
return fn(null, y);
});
So far so good, nothing to worry about. Now put that same function inside a record:
type Rec = { fn : unit -> string -> int }
let fn (x: unit) (y: string) = y.Length
let record = { fn = fn }
let recordResult = record.fn () "hello"
printfn "recordResult = %d" recordResult // recordResult = 5
let recordPartiallyApplied = record.fn ()
// HERE COMES THE ERROR: TypeError: Cannot read property 'length' of undefined
let recordSecondResult = recordPartiallyApplied "hello"
printfn "recordSecondResult = %d" recordSecondResult
This is because the recordPartiallyApplied
function is defined incorrectly:
Actuall
export const recordPartiallyApplied = CurriedLambda(CurriedLambda(record.fn)());
where I would expect that it is defined the same way without a record:
Expected
export const recordPartiallyApplied = CurriedLambda(CurriedLambda(record.fn)(null));
// OR
export const recordPartiallyApplied = CurriedLambda(function (y) {
return record.fn(null, y);
});
// OR
export const recordPartiallyApplied = function (y) {
return record.fn(null, y);
};
Notice that this will work fine if the argument was not unit, if it was int
the example works just fine and it also works when unit
is the second argument (hence, the title) where the first string parameter is passed correctly as well
The full compiled JS code from the snippet with records
import { setType } from "fable-core/Symbol";
import _Symbol from "fable-core/Symbol";
import { Unit, Function as _Function } from "fable-core/Util";
import { printf, toConsole } from "fable-core/String";
import CurriedLambda from "fable-core/CurriedLambda";
export class Rec {
constructor(fn) {
this.fn = fn;
}
[_Symbol.reflection]() {
return {
type: "Test.Rec",
interfaces: ["FSharpRecord"],
properties: {
fn: _Function([Unit, "string", "number"])
}
};
}
}
setType("Test.Rec", Rec);
export function fn(x, y) {
return y.length | 0;
}
export const record = new Rec(function (x, y) {
return fn(null, y);
});
export const recordResult = record.fn(null, "hello");
toConsole(printf("recordResult = %d"))(recordResult);
export const recordPartiallyApplied = CurriedLambda(CurriedLambda(record.fn)());
export const recordSecondResult = recordPartiallyApplied("hello");
toConsole(printf("recordSecondResult = %d"))(recordSecondResult);
Related information
- Fable version (
dotnet fable --version
): 1.3.10 and REPL - Operating system: Ubuntu 17.0
Issue Analytics
- State:
- Created 5 years ago
- Reactions:3
- Comments:6 (6 by maintainers)
Awesome, you are fast ❤️
A bit of side note, since there is a lot of refactoring going on, it would be nice to add some comments / docs to the compiler functions in this early stage, especially for modules from the Transform directory. I tried to go through the code to understand where this problem could be originating from but it is a bit hopeless (for me at least) to understand what exactly is going on from just pattern matching.
I agree that adding docs to existing code might too much of a challenge for now, it is however very much appreciated for newly created functions, patterns etc.
Sounds like a good idea, actually issues would work too so we can always reference back to the conversation for that time. Another idea that I got is to use more active patterns with very descriptive names for specific patterns that are not so obvious by just looking at them