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

Allow use bindings in class scope with primary constructor #561

Closed
4 of 6 tasks
chkn opened this issue Apr 16, 2017 · 3 comments
Closed
4 of 6 tasks

Allow use bindings in class scope with primary constructor #561

chkn opened this issue Apr 16, 2017 · 3 comments

Comments

@chkn
Copy link

chkn commented Apr 16, 2017

Allow use bindings in class scope with primary constructor

Often, you may find yourself creating an object type that owns some disposable resource. In turn, you make your type disposable as well:

open System
open System.IO

type Foo() =
    let wr = new StringWriter()
    interface IDisposable with
        member __.Dispose() = wr.Dispose()

This ends up being a lot of boilerplate code that does not add much value. I propose we enable use bindings at the class level when there is a primary constructor, so the above could be written thusly:

type Foo() =
    use wr = new StringWriter()

This syntax would:

  1. Cause the class to implicitly implement the IDisposable interface.
  2. Emit an implicit implementation of IDisposable.Dispose that in turn calls dispose on all instances bound with class-level use bindings in reverse order that they are declared.

For instance, this:

type Foo() =
    use a = new StringWriter()
    use b = new StringWriter()
    use c = new StringWriter()

would cause the compiler to emit the equivalent to:

type Foo() =
    let a = new StringWriter()
    let b = new StringWriter()
    let c = new StringWriter()
    interface IDisposable with
        member __.Dispose() =
            c.Dispose()
            b.Dispose()
            a.Dispose()

use-bound values are disposed in the opposite order that they are declared because they may have dependencies on previously-declared values.

Pros and Cons

The advantages of making this adjustment to F# is:

  • Less boilerplate code in this common object-oriented scenario.
  • F# strives to interop with .NET, and a lot of .NET types are disposable.

The disadvantages of making this adjustment to F# are:

  • More complexity in the compiler.
  • Implicit behavior that may not be obvious to those who are not familiar with this feature.

The use binding is already used for this purpose within functions and methods, so I believe this is a natural extension of that syntax. It is currently a compiler error to use it at the class level, so I do not believe there is any chance of this change breaking existing code.

Open Questions

  1. When use bindings are used at the class level, would there be any way to add code to the destructor? What would the syntax be?

Affadavit (must be submitted)

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. (Found this after posing (sorry!).. though it does say we may reconsider 😄 Allow use on let bindings in classes #312)
  • 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 would be willing to help implement and/or test this
  • I or my company would be willing to help crowdfund F# Software Foundation members to work on this
@rmunn
Copy link

rmunn commented Apr 17, 2017

While I like the idea of an automatic IDisposable.Dispose implementation in principle, I have a couple of concerns:

  1. I'm not a huge fan of the idea that although the class now implements IDisposable, the term IDisposable appears nowhere in the code. This could result in the following scenario:

    1. Carl Coder writes a Foo class in F# that uses this feature. He includes it in a shared library for which he publishes a NuGet package.
    2. Paul Programmer uses Carl's shared library in his C# program. Since he's a good C# developer, he wants to call the .Dispose() function on any class that needs it. He's not familiar with F#, but looking through the source code for Carl's library, he's pretty sure he understands what's going on: he can see interface ICollection with ... (and so on) all over the place, so he knows what classes are implementing which interface. Since Carl's Foo class doesn't have interface IDisposable with ... in it, Paul doesn't call .Dispose().
    3. Paul's code has intermittent crashes, due to Carl's class not being disposed properly, and he ends up blaming Carl's library for his crashes. Clearly, he thinks, F# is not ready for prime-time: it has this use statement that doesn't dispose of IDisposable resources properly!
  2. In many C# classes I've written where a Dispose() function was needed, I needed to do something more than simply dispose of any resources acquired in the constructor. I.e., the F# class might need to do something like this:

    type Foo() =
        let a = new StringWriter()
        let b = new StringWriter()
        let c = new StringWriter()
        interface IDisposable with
            member __.Dispose() =
                logger.debug (eventX "Resource {res} going away..." >> setField "res" "c")
                c.Dispose()
                logger.debug (eventX "Resource {res} going away..." >> setField "res" "b")
                b.Dispose()
                logger.debug (eventX "Resource {res} going away..." >> setField "res" "a")
                a.Dispose()

    If the .Dispose() implementation needs to do anything besides dispose of the acquired resources, should it override the default, implicit implementation? Should it be able to call the default implementation in some way, so that it can look like defaultDispose(); printfn "Extra step after disposing"? (And what should the syntax be for calling the default implementation?)

This is a nice idea, and I like it in general, but we'd need to nail down some specifics before I'd feel comfortable giving it a 👍.

@chkn
Copy link
Author

chkn commented Apr 17, 2017

@rmunn Thanks for reading the proposal and for your thoughtful response.

  1. I think you raise a good point. Even an F# programmer might miss the difference between let and use when reading through source code. However, in the F# case, if they try to create the type without the new keyword, the compiler warning should alert them that the type is disposable. For C#, I think the code analysis messages are getting more advanced, and may provide a message as well in some cases. In any case, even an explicit implementation could be overlooked, and if disposal of an object is that critical, then there should probably be comments, documentation, etc regardless of how Dispose is implemented.

  2. I think the implicit IDisposable implementation you'd get with the use binding is analogous to the compiler-provided equality implementations for unions and records. These remove the boilerplate code for the trivial cases, while retaining the ability to provide explicit implementations when needed.

@dsyme
Copy link
Collaborator

dsyme commented Nov 16, 2017

Duplicate of #312 which was rejected

@dsyme dsyme closed this as completed Nov 16, 2017
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

3 participants