Addition of InlineIfLambda attribute in F# 6 opened the way for high-performance computation expressions (imagine one for zero allocation ZString builder). But we can do better. I propose we change translation rules to allow optimisations of for-loops in CE.
Consider following code using list.fs builder:
listc { for i = 0 to 10 do i*i }
According to F# spec compiler uses following rules to translate this expression:
T(for x = e1 to e2 do ce, V, C, q) = T(for x in e1 .. e2 do ce, V, C, q) (*)
T(for x in e do ce, V, C, q) = T(ce, V ⊕ {x}, λv.C(b.For(src(e), fun x -> v)), q) (**)
where e1 .. e2 (*) is range operator from standard library that creates sequence of values and For (**) is defined as:
member inline b.For(
sequence: seq<'TElement>,
[<InlineIfLambda>] body: 'TElement -> ListBuilderCode<'T>)
: ListBuilderCode<'T> =
b.Using (sequence.GetEnumerator(),
(fun e -> b.While((fun () -> e.MoveNext()), (fun sm -> (body e.Current).Invoke &sm))))
So simple for-loop allocates sequence and calls Using method that might be undesirable in high-performance code. I propose to add new translation rule:
T(for x in e1 .. e2 do ce, V, C, q) = T(for x in range(e1, e2) do ce, V, C, q)
where range denotes b.Range(e1, e2) if builder b contains Range method. Otherwise, range(e1, e2) denotes e1 .. e2.
It allows to implement same optimisation compiler does for loops where
for x in 1..10 do
printfn $"{x}"
compiles into effective while loop.
Let's draft it! First add Range method:
member inline b.Range(e1: int, e1: int) = Range(Index(e1), Index(e2))
Then, add For overload:
member inline b.For(
range: Range,
[<InlineIfLambda>] body: int -> ListBuilderCode<'T>) =
ListBuilderCode<_>(fun sm ->
for i = range.Start to range.End do
(body i).Invoke &sm)
The existing way of approaching this problem in F# is override range operator (..). Surprisingly CE uses operator from the context!
Pros and Cons
The advantage of making this adjustment to F# is allowing more high-performance scenarios for CE.
The disadvantages of making this adjustment to F# are increasing complexity of translations rules and compiler.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): L
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
For Readers
If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.
Addition of
InlineIfLambdaattribute in F# 6 opened the way for high-performance computation expressions (imagine one for zero allocation ZString builder). But we can do better. I propose we change translation rules to allow optimisations of for-loops in CE.Consider following code using list.fs builder:
According to F# spec compiler uses following rules to translate this expression:
where
e1 .. e2 (*)is range operator from standard library that creates sequence of values andFor (**)is defined as:So simple for-loop allocates sequence and calls
Usingmethod that might be undesirable in high-performance code. I propose to add new translation rule:where
rangedenotesb.Range(e1, e2)if builderbcontainsRangemethod. Otherwise,range(e1, e2)denotese1 .. e2.It allows to implement same optimisation compiler does for loops where
compiles into effective
whileloop.Let's draft it! First add
Rangemethod:Then, add
Foroverload:The existing way of approaching this problem in F# is override range operator
(..). Surprisingly CE uses operator from the context!Pros and Cons
The advantage of making this adjustment to F# is allowing more high-performance scenarios for CE.
The disadvantages of making this adjustment to F# are increasing complexity of translations rules and compiler.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): L
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
For Readers
If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.