-
Notifications
You must be signed in to change notification settings - Fork 783
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
[RFC FS-1063] Applicative CEs via and! and BindReturn #7756
Conversation
I’m going to put some work in to try to get this into F# 5.0, though it's by no means certain. I’m particularly interested in the use case for adaptive data in https://github.com/fsprojects/FSharp.Data.Adaptive/ but I need to check that the tight restrictions on applicatives aren’t going to be a killer problem. So I’d just like to quiz you on your design choices and understand if they are going to be a problem @TD5 thanks for writing such an incredibly complete RFC, it’s really impressive. Specifically, FSharp.Data.Adaptive defines builders
all of which at a high level feel like they should be able to support let-bang/and-bang (I think…). However I’m concerned that the restriction to monads and not monoids will rule out the last three. I know there’s an extensive discussion of this in the RFC, I will try to understand it better. I’ll also try to understand the implementation a little better 😊 Help appreciated in proofing this for those who have the inclination |
@dsyme Some of the restrictions are in place just because I was trying to keep the scope fairly tight, and so there's no technical reason that they couldn't be reworked or extended in later RFCs to admit more interesting computations. FWIW, I did have incremental computations in mind when I was designing this at the time, although a year on it's a bit hazy 😅 Things are a busy for me at the moment, but I am in principle happy to discuss the details with you.
I am not sure if you're referring to a restriction from the Conceptually, the relationship between monoids and applicatives has been explored before (e.g. in Haskell's Alternatives and related types and discussions), so I am confident we can make any tweaks necessary to get the features you want. In fact, I am keen to see that aspect of the proposal refined a bit more if there is a general consensus that it wouldn't make the overall change too big. I think I'll need to review FSharp.Data.Adaptive in order to say anything more concrete at this stage. |
@TD5 Thanks so much for replying quickly, given how long it's taken me to look at this properly
Yes, I meant traditional applicatives, not monads |
@TD5 I'm still getting a hang of this, so let me ask some things about the design. The Did you consider a design that's based on an overloaded n-ary
There are three obvious downsides to this, notably
Putting these aside, I'm wondering if there are advantages in that the translation becomes more local and the interactions with other features becomes clearer? It's just that when I look purely syntactically at
and wonder to myself "what would this translate to given the rest of the F# design", I wouldn't be totally surprised if the answer was "it becomes a call to If the Map RFC is implemented, then it would become
etc. I'm not sure what I'd expect uses of Note this is looking from a syntactic point of view (and computation expressions are largely a syntactic device). |
A sketch of what it would be to add An addition of just |
I never considered overloading utilising overloading. That's presumably just because my personal F# style avoids overloading, so that'd be a valid and interesting design space to explore. Aside from overloading per se, I think there are two other points raised here: Pulling the Pulling I am definitely open to that. At one point I think I was keen on this, but we discussed it and decided to leave that as an extension/separate proposal to keep this one simple(r) and to defer a decision on whether to invoke more efficient rewrites (e.g. avoiding Using I remember going down the route of defining the applicative CEs in terms of
i.e. I think the choice was fairly subjective. Looking at the RFC now, I find it interesting that all of the examples utilise I suppose one other consideration is how often we expect the function to be already wrapped vs. not wrapped (i.e. the As an aside, when considering support for monoids, the current formulation for |
@TD5 Cool, thanks for those notes. Another design option: instead of
and then
becomes
with automatic nesting for |
hey @dsyme the
which would only require a minimal change to the way things currently are and allow for arbitrarily long |
just saw that 😳 |
Btw. I came up with a sketch allowing to use Nonetheless if someone's interested: https://gist.github.com/krauthaufen/afa861e988ca1a21570febf6d02a214c |
Regarding
That seems to assume Part of the motivation, in my mind, for Additionally (and this is really just a presumption on my part), some operations may be more efficient in terms of apply rather than bind, because we know we don't need to go through the step of generating the continuation of the computation. Then there's an argument to be had around the value of an So going back to |
that would simply be using I think it would nicely separate multi-binds from dealing with non-monadic types. So non-monadic builders would certainly need the proposed |
Ah yes, good spot! That's what I knew as |
Hey, applicatives could (even without the type Applicative() =
member x.Return v = v
member x.CombineSources(a : option<'a>, b : option<'b>) =
(a,b) ||> Option.map2 (fun a b -> (a,b))
member x.Bind(m : option<'a>, mapping : 'a -> 'b) =
m |> Option.map mapping
let test (a : option<int>) (b : option<int>) (c : option<int>) =
let app = Applicative()
app {
// let! a = a
// and! b = b
// and! c = c
let! ((a, b), c) = app.CombineSources(app.CombineSources(a, b), c)
let x = c * c + a
return a * b + x
}
|
Are there CEs where it makes sense to have MergeSources, Map and some of the other constructs such as TryFinally or TryWith? |
I am thinking about async {
try
let! a = computeA
and! b = computeB
return a + b
finally
printfn "done"
} |
I've pushed a trial implementation where everything is simplified to just an overloaded Some tests have been added and the others adjusted. Caveats
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs a parseState.LexBuffer.SupportsFeature
inside the pars.fsy file, just like our other features that added/changed syntax in 4.7
Failures:
due to some difference in error recovery in parsing bindings due to changes in parser |
@dsyme I don't have a particular use of |
The feature Map for computation expressions has been incorporated into this RFC/PR - though A quick summary of the relevant new builder methods:
|
The dependency graph sample is here: https://github.com/dotnet/fsharp/pull/7756/files#diff-e77ad28a19cb44b6c0d896f17fac41f6 This gives a measure of the way
|
Hey @dsyme I've started implementing the new builder methods in FSharp.Data.Adaptive and I stumbled upon two questions:
|
@dsyme did you consider also adding The idea would be to use aset {
for a in set do
yield 2 * a
} so the signature would need to be something like |
* implemented new builder methods as proposed in dotnet/fsharp#7756 * implemented `AVal.bind3`
I did think about this on my commute home and it's good to see you think of it too, and that it is relevant for FSharp.Data.Adaptive I suppose there is another feature lurking here |
(note I'm not entirely sure it's worth adding - would people understand the requirements for successful use? And for collections people seem more likely to happily use |
Thanks, I'll adjust the implementations in FDA eventually... |
I personally prefer the combinator approach in most cases but there are some where it can get a little tedious like: AList.append
(AList.single a)
(AList.append (AList.map foo bar) (AList.single b)) Nonetheless I don't think the Regarding the Cheers |
Zip's a great example of something that can be expressed as an applicative but not a monad 😁 |
This is basically done. I've removed the |
@dsyme , can we target this to fsharp5, please. |
c90e78a
to
e1ccdb6
Compare
@cartermp I've resolved all the outstanding issues with the testing and fixed an issue in the interaction with custom operators (discovered during testing) I'll now revise the RFC, but this feature is basically now ready for "preview" status when reviewed etc. |
47ce4f3
to
95a5b53
Compare
Merge master to feature/and-bang
Merge master to feature/and-bang
Merge master to feature/and-bang
Merge master to feature/and-bang
Merge master to feature/and-bang
Merge master to feature/and-bang
Merge master to feature/and-bang
Merge master to feature/and-bang
should the feature branch get deleted? |
Co-authored-by: Kevin Ransom (msft) <codecutter.fsharp@hotmail.com>
Implementation of F# RFC FS-1063
and!
andBindReturn
for more efficient computation expressionsContinues #5696
TODO:
Put the interaction of these new features with custom operators under test
check langversion checks being applied correctly
add tests for langversion not set