We’re excited to share updates about changes to F# and F# tools which shipped with the Visual Studio 2017 version 15.7 release. Let’s dive in!
Type Providers now support .NET Standard
For those who aren’t familiar with Type Providers, they are a feature of F# which allow you to get IntelliSense for data. When pointed at a data source, a Type Provider can produce an object model for that data. This object model is read by the F# compiler, which can then be used to power IntelliSense.
Type Providers can now be .NET Standard components. What this means is that you can use Type Providers in .NET Core apps, .NET Standard libraries, and .NET Framework apps without depending on specific packages for each target!
One example Type provider targeting .NET Standard is the HtmlProvider from FSharp.Data. Getting started is very easy in Visual Studio:
- File | New Project
- Select .NET Core application (or .NET Standard Class Library)
- Add a NuGet package reference to FSharp.Data 3.0.0-beta (if you use the NuGet UI, check “Incude Prerelease”)
Type Providers are extremely useful for data access, data exploration, and more. For example, here is how you can easily find information about the filmography of Donald Duck. The code sample is very simple:
Note that IntelliSense names have been generated based on the actual HTML data being read! When run, the application prints out all of the data selected:
Although the above screenshots show Windows and Visual Studio, you can use Type Providers on any OS where .NET Core runs, and with any editor tooling you like.
We are very excited about this and look forward to the coming months when all of the Type Providers in the F# ecosystem upgrade to support .NET Standard.
ASP.NET Core, Docker, and Azure tooling support
Starting with version 15.7 of Visual Studio 2017, F# officially supports creating ASP.NET Core projects via the same UI that C# supports. Additionally, all tooling for Docker, CI/CD with Visual Studio Team Services, and publishing to Azure App Service is supported.
When you use File | New Project to create a new F# project, you’ll notice a new tab called Web under Visual F#. The template there is also present under .NET Core:
When you create a project with this template, it will launch the ASP.NET dialog with F# template options, including scaffolding Docker support:
You can always add Docker support later with Right Click > Add > Docker Support on the project node in the UI.
This will work for all F# projects, not just ASP.NET ones:
Additionally, if you are inclined to use the Visual Studio UI to publish applications or set up CI/CD with Visual Studio Team Services, you can do so with any F# project which uses the Microsoft.NET.Sdk.Web attribute.
For example, you can add Docker support to an existing Giraffe project and use the UI to publish it to a container registry of your choosing.
- Right-click the project node and select Add > Docker Support.
- Rick-click the project node and select Publish.
- Select the Container Registry tab.
- Select the registry you prefer (Azure Container Registry, Docker Hub, or Custom).
- Fill out the form data and publish to a container registry!
We’re very excited about this tooling support, especially because it makes it so much easier to get started with F# for server and cloud programming in Visual Studio.
The 15.7 update for Visual Studio 2017 comes with significant performance and responsiveness improvements. This work involved major contributions from Microsoft Mobile Tools (who provide support for F# in Xamarin and Visual Studio for Mac) and the F# community.
Reduction in data structure sizes
Visual Studio uses the F# Compiler Service, which is an API that exposes different layers of the F# compiler, including some of its data structures. What’s interesting about this is that performance characteristics of these data structures and functions are different when compiling F# code versus using F# tooling.
Many data structures used by the F# Compiler Service contained data that was rarely used and were kept in memory for a longer time than necessary. The data structures still exist (otherwise needless re-computations are done), but the size of the data structures has been reduced by splitting them up into “necessary” and “optional” data.
What this means is that in many situations, usage of the tools will consume less memory that before because the optional data in these data structures won’t be filled. There can still be some situations where optional data is computed and stored in memory, but generally speaking, usage patterns in the F# tools will require less memory.
This work was entirely a collaboration between Microsoft and the F# community after a series of discussions about the kinds of data structures which could be reduced in size. In particular, one of our long-time contributors, Avi Avni, contributed significant work. We’re extremely grateful for his contributions and are excited to continue working with him.
Moving metadata into virtual memory
One of the largest contributors to memory usage for F# tooling has been in what is called a ByteFile, which is a data structure that holds data in memory when it is read from referenced assemblies. If you are working in a project with a significant amount of dll references (project to project references, NuGet package references, etc.), then this could significantly increase memory usage in previous Visual Studio releases. This increased memory usage could reach a threshold in large solutions where the F# compiler service entered a “loop of doom”: caches were flushed, then filled, then flushed, then filled . . . all while the GC churned endlessly, rendering Visual Studio unusable.
Starting with the 15.7 update, we now store this data in virtual memory, backed by the Visual Studio Metadata Reference Manager that C# and VB use. This significantly reduces memory usage over time for large F# projects in Visual Studio, or projects which read a lot of data from assembly references. As metadata is read into virtual memory and used, memory usage will increase. But rather than stay in memory indefinitely, the data will be written to the backing memory-mapped file and incur a significant reduction in memory usage after some time. When using the 15.7 release against the Visual F# compiler and tools codebase, we observe significant memory reductions and no longer experience a “loop of doom”. We hope that this can be true for the many large F# codebases out there as well.
Adding a priority to document diagnostic analyzers
With Visual Studio 2017, F# tools use a Roslyn mechanism to analyze documents when they change. There are multiple analyzers which run for F#, depending on what you have turned on:
- Unused open statement analysis
- Unused declaration analysis
- Name simplification analysis
- Error diagnostic analysis
In past releases, these were run serially and in an arbitrary order. This means that unused open statement analysis may run before error diagnostics, thus forcing you to wait longer for an error to appear. We now assign a priority to each analyzer, with error diagnostics set at the highest priority. This means that error messages will appear consistently from a timing perspective. We have also made the unused opens analyzer significant faster than in previous releases.
Additionally, the Unused open statement analyzer has been modified to cache results more efficiently.
We’re quite happy with this change, as it addresses an issue with editor responsiveness that people have been experiencing ever since the release of Visual Studio 2017.
Overall, the 15.7 release of Visual Studio 2017 represents one of the largest performance improvements to F# tooling in Visual Studio in a long time. We are very excited about this because it is the number one issue people have when they use F# for large solutions. We’re looking forward to spending more time on tooling performance in the next year, and doing so not only for Visual Studio, but in the F# Compiler and F# Compiler Service so that all F# tooling in any editor can benefit.