Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better improvement for cexpr "do!" translation rule #592

Closed
4 of 5 tasks
kekyo opened this issue Jul 5, 2017 · 4 comments
Closed
4 of 5 tasks

Better improvement for cexpr "do!" translation rule #592

kekyo opened this issue Jul 5, 2017 · 4 comments

Comments

@kekyo
Copy link

kekyo commented Jul 5, 2017

Better improvement for cexpr "do!" translation rule

We can improve cexpr "do!" translation rule.
Existing F#'s do! with no continuation rule is:

T(do! e;, V, C, q) = T(let! () = src(e) in b.Return(), V, C, q)

This rule contains "expr has Return()".
But it has to Zero() than Return().

T(do! e;, V, C, q) = T(let! () = src(e) in b.Zero(), V, C, q)

I'm thinking about how to full (naturally) statements support inside of cexpr block,
existing F#'s rule has different and weak both normal expr and cexpr.

Details 1 of 3

It simple physical way tested for:

type OptBuilder() =
    member __.Bind(opt, binder) =
        match opt with
        | Some value ->
            printfn "Bind: %A" value
            binder value
        | None ->
            printfn "Bind: None"
            None
    member __.Return(value) =
        printfn "Return: %A" value
        Some value
    member __.Zero() =
        printfn "Zero"
        None

let opt = OptBuilder()

let result = opt {
    do! Some ()  // last of do! (no continuation)
}

Output: I saw along the rules.

Bind: <null>
Return: <null>

Details 2 of 3

If OptBuilder can capable while?
Difficulty of while implementation is how exit local blocks by "return" and "return!".

I read Tomas's article good demonstrate how to implement imperative cexpr builder.
The imperative builder indicates "is exiting now" marked as "Option.Some".

My sample code contains partial result record and has exiting flag likely Tomas's:

type PartialResult<'T> = {
    Value: 'T option
    Exiting: bool   // is exiting now?
}

type OptBuilder() =
    member __.Source(opt: 'T option) =
        printfn "Source: %A" opt
        { Value = opt; Exiting = false }
    member __.Return(value: 'T) =
        printfn "Return: %A" value
        { Value = Some value; Exiting = true }  // Now exiting
    member __.Bind(partial, binder) =
        match partial.Value with
        | Some value ->
            printfn "Bind: %A" value
            binder value
        | None ->
            printfn "Bind: None"
            { Value = None; Exiting = false }
    member __.Zero() =
        printfn "Zero"
        { Value = None; Exiting = false }

let result = opt {
    do! Some ()
    //Console.WriteLine("Got it")     // Will enable comment below
}

Output: I saw along the rules too.

Source: Some ()
Bind: <null>
Return: <null>

Or do! with continuation translation rule applied (Results with Zero()) enable comment line:

Source: Some ()
Bind: <null>
Got it
Zero

That's OK.

Details 3 of 3

But if I append while support in this code:

    // ...
    member this.While(guard, body: unit -> PartialResult<'T>) =
        if not (guard()) then
            printfn "While: not guard [0]"
            this.Zero()
        else
            let rec whileBody () =
                printfn "While: body"
                let result = body()
                if result.Exiting then
                    printfn "While: exiting"
                    result 
                elif not (guard()) then
                    printfn "While: not guard [n]"
                    result 
                else
                    whileBody ()
            whileBody()
    member __.Delay(expr: unit -> PartialResult<'T>) = expr
    member __.Run(expr: unit -> PartialResult<'T>) =
        expr().Value

let result = opt {
    let mutable remains = 10
    while remains > 0 do
        remains <- remains - 1
        do! Some ()
        //Console.WriteLine("Got it")     // Will enable comment below
}

Output: Oh I surprised why applied only once?!? I don't returned inside cexpr block?!

While: body
Source: Some ()
Bind: <null>
Return: <null>
While: exiting

But if enable WriteLine:

While: body
Source: Some ()
Bind: <null>
Got it
Zero
While: body
Source: Some ()
Bind: <null>
Got it
Zero
While: body
Source: Some ()
Bind: <null>
Got it

8<----8<----8<---- snip

Zero
While: body
Source: Some ()
Bind: <null>
Got it
Zero
While: body
Source: Some ()
Bind: <null>
Got it
Zero
While: not guard [n]

This is OK. I feel a sense of incompatibility... it's really natural behavior than before's.

Thinking why cexpr translation rule cause this problem:

What's or How the "return" behavior, I think long time but not clear...
@bleis-tift gives me cexpr rule complementary problem hints:

let do
let () = x in zero do x; zero
let! () = x in zero do! x; ()

Only do! means "return ()".
I feel fix problem simply if change F#'s cexpr do! non-continuation rule.

I propose how to fix this problem:

  • Change cexpr rule (silently future release)
    • This is breaking change.
    • But maybe don't cause critical problem (?)
    • Effective impls are all cexpr builders? (and not many of code?)
  • Can select enable or disable for append compilation switch.
  • Can select enable or disable by applied type attribute. ex: [<ZeroBehavior>]
  • Others...

Pros and Cons

The advantages of making this adjustment to F# are:

  • Improvement consistency of both expr and cexpr.
  • We can implement fully naturally cexpr support.

The disadvantages of making this adjustment to F# are ...

  • Cause breaking change.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): S?

AsyncBuilder cannot contains return behavior (Hmm, I don't know until just now...)

let asy = async {
    let mutable remains = 10
    while remains > 0 do
        remains <- remains - 1
        return remains  // This expression was expected to have type 'unit' but here has type 'int'
    printfn "Done!"
    return remains
}

Related suggestions:

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • I or my company would be willing to help implement and/or test this
@kekyo
Copy link
Author

kekyo commented Jul 5, 2017

@tpetricek I have poor english so please help me at this issue :)

Off topic:
I'm just writing for new F#'s async cexpr infrastructure and found this problem. (likely TaskBuilder, already read issue #581. This code contains structure-based awaitable flexible Async, Task, Task<T> and ValueTask<T> support)
I want to send PR to visualfsharp if I finished :)

@mexx
Copy link

mexx commented Jul 6, 2017

I kind a stumbled upon the same problem some time ago. My observations were kind of different back than. Please see the UserVoice from that time.

As far as I can see your use case is a sequential CE, can you try to remove the Return method. By this return keyword will not be available and Zero will be used instead like you want.

@kekyo
Copy link
Author

kekyo commented Jul 6, 2017

Thanks @mexx . I read #19 and UserVoice, shame my builder not related seq CE (Async, TaskBuilder like) :(
I know it problem will be fixed using "yield" instead "return", but if this case:

I want:

let result = opt {
    let mutable remains = 10
    while remains > 0 do
        remains <- remains - 1
        return 123   // I want to exit (But cause compilation error)

But I have to writing:

let result = opt {
    let mutable remains = 10
    while remains > 0 do
        remains <- remains - 1
        yield 123    // must use yield

This code fragment very misreading by "yield" because yield means "iterate value and continue (next continuation)". I feel bad code complementary....

@dsyme
Copy link
Collaborator

dsyme commented Jan 10, 2023

I'm going to close this suggestion, it hasn't seen many comments requesting similar things since first created.

@dsyme dsyme closed this as completed Jan 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants