Reputation: 1643
I understand the benefit of nesting functions when you want to return a closure. But when a function is never used outside of the function it is defined in, is there a cost to keeping it nested?
Consider:
let private validate number =
number > 100
let validateAndPrint (i : int) =
printfn "%i Greater than 100: %s" i ((validate i).ToString())
Versus:
let validateAndPrint (i : int) =
let validate number =
number > 100
printfn "%i Greater than 100: %s" i ((validate i).ToString())
The concern here is usually when I have a few of these inner functions and I only need to call one of them based on an execution path (it's usually not prohibitive to keep these functions private or nested and test the permutations).
validateAndPrint
could also be rewritten more tdd-friendly to take in the validate function:
let validateAndPrint validate i = ...
..but if validation
's signature contains an internal or private type then there is the consideration of making everything public and your parameter list blowing up.
Any crucial differences?
Upvotes: 4
Views: 193
Reputation: 6382
In Release
mode, IL is identical and both versions of validate
are inlined in this particular example.
You cannot rely on inlining always, e.g. if a function is "big" then compiler could choose not to inline it (this is true for methods, I am not 100% sure this is true for F# nested functions. It is not possible to mark them inline
and force inlining, - need to consult F# specs if such functions behave like methods or are always inlined in Release). In the nested case in Debug mode, there is additional FSharpFunc allocation and callvirt
. Without nesting it is just a static call without allocations, which is the cheapest of all possible implementations.
So in general, if nesting is truly optional it is safer to avoid it, because even if a function is not inlined it is always called as a static method. However, it matters only when you call this code several millions times per second.
Not nested, static call on IL_0019:
.method public static
void validateAndPrint (
int32 i
) cil managed
{
// Method begins at RVA 0x2104
// Code size 51 (0x33)
.maxstack 5
.locals init (
[0] class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>,
[1] bool
)
IL_0000: ldstr "%i Greater than 100: %s"
IL_0005: newobj instance void class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`5<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>, class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [mscorlib]System.Tuple`2<int32, string>>::.ctor(string)
IL_000a: call !!0 [FSharp.Core]Microsoft.FSharp.Core.ExtraTopLevelOperators::PrintFormatLine<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>>(class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<!!0, class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>)
IL_000f: stloc.0
IL_0010: nop
IL_0011: ldloc.0
IL_0012: newobj instance void FSSO.Test/validateAndPrint@8::.ctor(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>)
IL_0017: ldarg.0
IL_0018: ldarg.0
IL_0019: call bool FSSO.Test::validate(int32)
IL_001e: stloc.1
IL_001f: ldloca.s 1
IL_0021: constrained. [mscorlib]System.Boolean
IL_0027: callvirt instance string [mscorlib]System.Object::ToString()
IL_002c: call !!0 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, string>::InvokeFast<class [FSharp.Core]Microsoft.FSharp.Core.Unit>(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!0, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!1, !!0>>, !0, !1)
IL_0031: pop
IL_0032: ret
} // end of method Test::validateAndPrint
Nested, allocation on IL_0018 and callvirt on IL_0020:
.method public static
void validateAndPrint (
int32 i
) cil managed
{
// Method begins at RVA 0x2050
// Code size 58 (0x3a)
.maxstack 6
.locals init (
[0] class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, bool> validate,
[1] class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>,
[2] bool
)
IL_0000: newobj instance void FSSO.Test2/validate@13::.ctor()
IL_0005: stloc.0
IL_0006: ldstr "%i Greater than 100: %s"
IL_000b: newobj instance void class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`5<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>, class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [mscorlib]System.Tuple`2<int32, string>>::.ctor(string)
IL_0010: call !!0 [FSharp.Core]Microsoft.FSharp.Core.ExtraTopLevelOperators::PrintFormatLine<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>>(class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<!!0, class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>)
IL_0015: stloc.1
IL_0016: nop
IL_0017: ldloc.1
IL_0018: newobj instance void FSSO.Test2/'validateAndPrint@14-2'::.ctor(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>)
IL_001d: ldarg.0
IL_001e: ldloc.0
IL_001f: ldarg.0
IL_0020: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, bool>::Invoke(!0)
IL_0025: stloc.2
IL_0026: ldloca.s 2
IL_0028: constrained. [mscorlib]System.Boolean
IL_002e: callvirt instance string [mscorlib]System.Object::ToString()
IL_0033: call !!0 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, string>::InvokeFast<class [FSharp.Core]Microsoft.FSharp.Core.Unit>(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!0, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!1, !!0>>, !0, !1)
IL_0038: pop
IL_0039: ret
} // end of method Test2::validateAndPrint
Release mode, inlined comparison on IL_0020:
.method public static
void validateAndPrint (
int32 i
) cil managed
{
// Method begins at RVA 0x2050
// Code size 57 (0x39)
.maxstack 6
.locals init (
[0] class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>,
[1] class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>, class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>,
[2] bool
)
IL_0000: ldstr "%i Greater than 100: %s"
IL_0005: newobj instance void class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`5<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>, class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [mscorlib]System.Tuple`2<int32, string>>::.ctor(string)
IL_000a: stloc.1
IL_000b: call class [mscorlib]System.IO.TextWriter [mscorlib]System.Console::get_Out()
IL_0010: ldloc.1
IL_0011: call !!0 [FSharp.Core]Microsoft.FSharp.Core.PrintfModule::PrintFormatLineToTextWriter<class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>>(class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4<!!0, class [mscorlib]System.IO.TextWriter, class [FSharp.Core]Microsoft.FSharp.Core.Unit, class [FSharp.Core]Microsoft.FSharp.Core.Unit>)
IL_0016: stloc.0
IL_0017: nop
IL_0018: ldloc.0
IL_0019: newobj instance void FSSO.Test2/'validateAndPrint@14-2'::.ctor(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<string, class [FSharp.Core]Microsoft.FSharp.Core.Unit>>)
IL_001e: ldarg.0
IL_001f: ldarg.0
IL_0020: ldc.i4.s 100
IL_0022: cgt
IL_0024: stloc.2
IL_0025: ldloca.s 2
IL_0027: constrained. [mscorlib]System.Boolean
IL_002d: callvirt instance string [mscorlib]System.Object::ToString()
IL_0032: call !!0 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32, string>::InvokeFast<class [FSharp.Core]Microsoft.FSharp.Core.Unit>(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!0, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!1, !!0>>, !0, !1)
IL_0037: pop
IL_0038: ret
} // end of method Test2::validateAndPrint
Upvotes: 8