Replies: 1 comment 1 reply
-
@cartermp Does moving this issue to Discussions make it more likely to forget about or lose it? It looks like a possible good improvement. |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
I was discussing this with @TIHan today and we want to record this issue, this is just a sketch.
For code like this:
the F# type checker generalises the inner function:
In the absence of other optimizations, and to avoid code duplication, and to make compilation localised, F# often emits
let g<'T> = ...
as a "first class function value" (FSharpTypeFunc
) and then immediately specialises that. For example the most naive code might be a bunch of closures roughly like this:Here
clo_g
is almost always completely superfluous, it's immediately speciailzed to two types.But really, we should basically never (or almost never) emit FSharpTypeFunc because, as can be seen above, these things are always immediately applied to actual type arguments,
Now, IlxGen actually has a complex code path to emit "local type function closures" in the case where
T
is constrained (when 'T is constrained we can't use FSharpTypeFunc because that only represents unconstrained first class type functions - we only realised this late in F# 0.x when we fully connected F# to .NET generics)We should either :
always emit "local" type functions (no FSharpTypeFunc), simply by deleting this one single line, or
still emit ILX closures and adjust the emit of
FSharpTypeFunc
and subsequent closures to avoid calls to FSharpTypeFunc.Specialize and go straight to the closure class its cody would return (clo_g2
)Notes
Note the currently emitted code is particularly bad when multiple generic parameters are involved, when there can be several calls to Specialize.
Note also that a lot of this code gets generated in debug mode, and it's possible the debugging experience degrades in some ways because of that. It would likely become better if we make the generated code simpler, but should be checked
The call to Specialize is ultimately built here
The FSharpTypeFunc closure class is emitted here
Background and relation to
TType_forall
andExpr.TyLambda
The existence of FSharpTypeFunc at all is a relic from the 1.x era of F# when we thought the language would have to support "first class forall types", e.g.
let f (x: FORALL<'T>. 'T -> 'T)
. These things are however easily representable in .NET programming by classes or interfaces that have generic vitrual methods, and code generall becomes much clearer when such a subtle contract is given a name.However we never actually removed forall types or type lambdas from the F# TypedTree and pickled format. For example, you might imagine that a
Val
stores its information like this:but actually what happens is that the type of
f
when used as a first class value is stored inVal
is more like this:and the body matches this:
The information in
Val
gets "split and folded back together" repeatedly in routines like GetTopValTypeInFSharpForm.Thus the TypedTree and pickled format include
Expr.TyLambda
(in Expressions) andTType_forall
(in types). Both are a nasty relic. Also some optimization phases may, I believe, give rise to further instances of these that are not eliminated or in a neat normal form.As an aside, the
argInfos
inVal
only gets stored for top level (class, module) function definitions and members, and not expression-level function definitions. This even shows in F# tooling where inner function definitions do not show metadata information like argument names.As an aside, this whole story really stems from the early implementation of F# as a lambda-calculus-like-thing over-emphasises the view that function definitions result in first class function values.
Beta Was this translation helpful? Give feedback.
All reactions