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

making option (and more) useable with yield! in computation expressions #1293

Open
5 of 6 tasks
smoothdeveloper opened this issue Jul 21, 2023 · 3 comments
Open
5 of 6 tasks

Comments

@smoothdeveloper
Copy link
Contributor

type Data = {
  a: string
  b: string option
  c: string option
}

let values data =
  seq {
    data.a
    yield! data.b
    yield! data.c
  }

I'd like usage of yield! in values to be syntax sugar (0 cost abstraction) for

let values data =
  seq {
    data.a
    match data.b with
    | None -> ()
    | Some b -> b
    match data.c with
    | None -> ()
    | Some c -> c
  }

I'd also like to figure if this can be made possible on types without them going to extent of implementing iterator patterns, applying to System.Nullable,ValueOption, Result would make sense in terms of FSharp.Core / compiler implementation if the suggestion is appealing.

It could be something that is in coding guidelines for types that implement map, where the semantic makes sense, if the suggestion can be taken from standpoint of enabling this on adhoc type (which would be great).

The existing way of approaching this problem in F# is ...

  • custom Computation Expression optionSeq (and others for other data structures) with overloads
  • changing Option (and other relevant value containers) type to implement IEnumerable

I'm not sure the alternatives can bring it to a 0 cost abstraction.

Pros and Cons

The advantages of making this adjustment to F# are:

  • makes it much terser to deconstruct option (or value container like) types in context of yielding values in a computation expression
  • more 0 cost abstraction

The disadvantages of making this adjustment to F# are:

  • introducing of concept that doesn't align with how yield! currently works (only with IEnumerable AFAIU)
  • may look surprising if the construct is not documented in coding guidelines nor around contents explaining yield!
  • may imply it can be used in for construct (which shouldn't be the case in context of this sole feature and Option should implement Seq #716)

Extra information

Estimated cost (XS, S, M, L, XL, XXL): between S and L depending on wether we just want this for option/voption/nullable and not supporting the same on adhoc types.

Related suggestions: (#716)

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
  • I am willing to contribute to adjusting relevant coding guidelines if this gets into an approved and implemented state
@SchlenkR
Copy link

SchlenkR commented Jul 21, 2023

An idea how this could be implemented in a library if we had a SeqBuilder:

// Hint on yield!
// ---------------
// The usage of yield! looks non-idiomatic,
// since it usually is used to yield the computation's base type
// (e.g. seq<'a>), and now it's intention is extended to 
// yield a "seq-like" value option<'a>.

// Why yield! has to be used in any case:
// ------------------------------------------
// yield can't be used here (so I used yield!, even though
// it might seem unfamiliar in the first place - see comment above)
// because of ambiguities in overloads.
// In other words: All yielded values must match exactly
// one overload, which would not be the case having 2 yield-overloads
// with 'a and 'a option (both would match yielding an 'a option,
// even though one is more specific that the other).

// Why this sample can't work with the `seq` implementation:
// -----------------------------------------------------------------
// As far as I know, there's no SeqBuilder in F# core that could
// be used to augment (it's baked into the compiler),
// so this is a non-optimized custom SeqBuilder for
// demo purposes only.

type SeqBuilder() =
    member _.For(m: seq<_>, f) = Seq.collect f m
    member _.Yield(v) = Seq.singleton v
    member _.Zero<'x>() = Seq.empty<'x>
    member _.Combine(a, delayedB) = Seq.append a (delayedB ())
    member _.Delay(f) = f
    member _.Run(f) = f()
    
    // here we go...
    member _.YieldFrom(m: option<_>) =
        match m with
        | Some v -> Seq.singleton v
        | None -> Seq.empty

let seq = SeqBuilder()


// -------------------------------------------


type Data = {
    a: string
    b: string option
    c: string option
}

let getValues data =
    seq {
        data.a
        yield! data.b
        yield! data.c
    }

getValues { a = "a"; b = Some "b"; c = None }
// val it: seq ["a"; "b"]

A way of approaching a production ready solution could be to implement a performant seq builder utilizing "resumable code", or by perf-tuning a "normal" builder with InlineIfLambda and other techniques, then making that builder available in a library and shadow the seq keyword.

@charlesroddie
Copy link

Is there a reason to have yield! but not for ... in?

I was thinking of creating an issue for ValueOption<'t> to support IEnumerable<'t> (which doesn't have the null problem supporting it that Option<'t> has). But the main use cases would be for ... in and yield! so if those are implemented then it wouldn't be needed.

@MaxWilson
Copy link

Isn't

yield! Option.toList data.b

already a way of doing this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants