dotnet/fsharp

Hot Reload

Happypig375 opened this issue ยท 54 comments

Is your feature request related to a problem? Please describe.

C# is getting Hot Reload for .NET 6. Will F# receive the same experience? Roslyn has had work done to support this. The F# compiler doesn't seem to have any equivalent work on this.

Describe the solution you'd like

F# can use hot reload just like C#.

Describe alternatives you've considered

Staying behind in terms of tooling again.

Additional context

If the issue is about:

  • improving a compiler error message: No.
  • improving/adjusting the parser: No.

Add any other context or screenshots about the feature request here.
dotnet/core#5510

https://devblogs.microsoft.com/dotnet/introducing-net-hot-reload/

F# is currently not supported in .NET 6 but we are planning to support in a future release based on customer feedback.

Of course F# is staying behind in terms of tooling again. ๐Ÿ˜ž

What's the current status if:

  • You run a C# project that supports edit and continue/hot reload
  • This depends on an F# project
  • After making a change in the F# project, you build it.

Is hot reload able to support that currently? I.e. loading the updated assembly.

It'll happen eventually here, but hot reload is a very new technology that is likely to undergo several changes well after .NET 6. We'll plug into it when it's more stable.

@happypig, @charlesroddie --
what do you want hot reload to do for you? Just asking for a feature name is probably not going to get anything moving. We don't have plenty of other C# features also, and we don't plan on going down the VB route of moaning whenever we don't get some new C# feature.

forki commented

From the linked thread:

dotnet/core#5510 (comment) How fast developers can make code changes and see the resulting impact in their apps is directly proportional to how productive they can be.

Most important is a quick development loop when changing bits of UI (likely to be MAUI with WinUI as primary development platform). For non-UI code I don't need to run code very often, sometimes only once after finishing a work item to check that it works. But for UI code a lot of tweaks and adjustments are important and need to be visualized by running the code.

Hot reload would be perfect for this but does look like a very large task for F# to support Edit and Continue which it is based on.

A typical UI tweak doesn't affect the API of an assembly so reference assemblies will speed up the loop.

Hypothetically if it were possible, while running an application, to edit a dependent project, build it, and switch in the dll, it might capture most of the benefit of hot restart. But this may not be possible in dotnet?

Would support for generating files for FSI scripting, like the one provided by the F# Power Pack back in the day, be an alternative in this case? Together with script debugging, of course.
I can do it in VS 2015 with the plugin for something like winforms, for example. It's more clunky than have it done automatically, for sure, but also closer to home.

I am using blazor with fsharp. Hot reload will speed up the poductivity a lot.

This is a critical tooling enhancement for my usage of F#.

Is there some way I can contribute to move up the timeline?
(I don't currently have a great feel for the scope of work needed)

This is a critical tooling enhancement for my usage of F#.

Is there some way I can contribute to move up the timeline?
(I don't currently have a great feel for the scope of work needed)

I don't think we have a full understanding of what has to be done yet.

I decided to spelunk and get a better idea of what it takes to implement hot reload.

Full notes are in this gist https://gist.github.com/farlee2121/24915fb0518ad4ca6631bc33d0ff401c

Finds so far

Oustanding investigations

  • TODO: Looking into F# partial compilation capabilities
  • TODO: Look into support for solutions with projects in multiple languages
  • TODO: understand where application state is preserved

A quick update. I made an experimental fork.

It's pretty basic so far

  • Handles project changes
  • Handles new files
    • There is a gotcha with VisualStudio. The build fails because the VS implicitly modifies the project file, but doesn't save it right away
  • Rebuilds on any change to any .fs file

The running program will show changes, and much faster than the existing dotnet watch. The main issue is that debug artifacts are not updated with this strategy.

A more accurate description is that rebuilding tries to output both dlls and pdbs, but those resources are already in use by the debugger process.

@farlee2121 Don't be shy, post a GIF!

Here's a demonstration of the most basic scenario, changing a file

fsharp-watch

Sorry for my ignorance, but what exactly is that demonstrating? Watch will recompile and relaunch an application today without hot reload.

Correct.

Here is the described state of my experiment I was asked to gif.

It's pretty basic so far

  • Handles project changes
  • Handles new files
    • There is a gotcha with VisualStudio. The build fails because the VS implicitly modifies the project file, but doesn't save it right away
  • Rebuilds on any change to any .fs file

I also believe it to be faster, but my sample is only one project.

While the above requirements are met with the DotNetWatcher, they also have to be implemented for F# in HotReloadDotNetWatcher.
You could interpret this gif as demonstrating the most basic F# reloading, but working in the Hot Reload implementation of dotnet watch.

I'm still working on demonstrating updates to F# code using existing delta appliers (true hot reload).

I see, thank you.

An update on my efforts.

  • I started some new consulting projects, so my time for this has declined and progress has slowed.
  • I've had to back-fill a lot of knowledge about compiled artifacts (dlls, pdbs)
  • I thought I had figured out the data format needed for live updates applied by IDeltaApplier by digging through the roslyn implementation. However, my test implementation didn't work.

A few other conclusions likely obvious to the FSharp team, but I had to dig for

  • It appears FSharp Compiler Services has the underlying tooling we need to generate incremental updates
  • Incremental updates can be created using FSharpChecker then diffing the compiled outputs, but it seems we could make smarter updates with access to internal-only API methods. Thus, we'll likely want to add hot reload binary patch /delta generation to the compiler services and simply invoke it from the appropriate place in dotnet-watch

Things I'll probably need help on if I can demonstrate a successful reload

  • Project cracking is very confusing, I traced a lineage of several deprecated project crackers, we probably don't want core tooling to depend on the current ionide cracker. How are we supposed to build a project graph?
  • Feedback on where the hot reload delta generation should live in compiler services
  • Understanding live update limitations (rude edits)

I suppose @farlee2121 needs someone from Roslyn/HotReload team to join this thread

That would certainly help

I got a bit more work in tonight.
Specifically, I traced how the tests were generating deltas, and it comes back to a utility based on the same Roslyn method I was looking at before . The issue is that it passes around a bunch of mutable byte streams and I'm having a hard time tracking with what all happens to those mutable streams.

I also set up more experiments to understand what behaviors hot reload handles for C# now. I found the results more fickle than I expected. Asp.net works fine, but I've yet to get blazor wasm working, and the console app only updates if I stop at a breakpoint.

Issue Scope

My updates have been a bit hodge-podge, so here's a summary in light of the original question: what would it take to implement hot reload for F#.

I'm more confident than ever that the DeltaAppliers should be reusable for F#. "Starting in 6.0, the runtime (.NET Core and Mono) expose APIs to patch a running app". This should be language-agnostic, and addresses the complexities of different runtime conditions.

My sdk fork demonstrates how to plug F# into the hot reload hierarchy. I can provide more details if needed. It's basically the single FSharpCompilationHandler class.

The main work to be done is an F# compiler api method that takes changed files in a project and outputs assembly patches in a format mappable to System.Reflection.Metadata.MetadataUpdater. We still need to understand that format properly. This api method should also notify when a "rude edit" that can't be handled at runtime is made so hot reload can prompt for a restart.

When it's all done, we add <ProjectCapability Include="SupportsHotReload" /> to the Fsharp targets file

Any progress so far?

I started a new job and haven't had time for this lately.
I'll probably need to reach out to the roslyn team to better understand the delta formats

@dsyme can you please help on this cool feature? Do you know any Roslyn team members who can help here?

F# support for this would be so much appreciated. I created a small repository to illustrate the problem for the new readers: https://github.com/PiotrJustyna/binoculars What I'm after is for the PID not to change as the code hot-reloads. It does work perfectly for C# ๐Ÿ‘Œ

I plan to use it for work where, in order to speed up deployments, we could hot swap certain F# components in our C# and F# hosts. Eagerly observing this issue ๐Ÿ‘€

askpt commented

This feature would improve our team daily workflow. I don't understand why is not being planned to introduce for F# language. It will leave the language as a second class citizen when compared with C# and even VB.NET.

It can improve bolero developments. I am also making a library Fun.Blazor, looking forward to hot reload for a long time. I hope F# team can take this as soon as possible, they and Roslyn team are all in Microsoft right? They can discuss the C# implementation detail.
Hope it will happen soon...

The lack of hot reload is also holding me back from using Bolero with F# instead of Blazor with C#.

This missing feature is really a productive killer and will not help to spread the usage of F#. If you gain initial benefits from using F# and then later your productivity falls back behind using C# what is the rationale to use F# outside of domains like Data Science ?

Even if you have F# type references in your C# assembly, hot reload seems not to work even for C# most of the time. This is even more frustrating.

I understand that rolling out new features cannot be done always in a holistic way through the .NET framework. But if evertyime of such a feature, F# user has to wait a couple of years until they might be able to benefit also, F# will never get more momentum, the IDEs will always be considerable less productive for F# devs in general. So if F# should be a first citizen in .NET please think about improving this situation in the future.

badgh commented

F# can be transpiled to JavaScript (now also Python) via Fable and thus it uses hot reload feature from JavaScript. If F# could also be transpiled to C# via Fable, does it mean that F# would get C#'s hot reload feature for free?

It does not make sense as it is already the .NET and needs just a piece of logic to be plugged in

Converting F# to C# is doable with existing tools. For example, you could use something like ILSpy to decompile from IL.

However, this isn't likely to enable a practical hot reload. We'd end up compiling twice and decompiling once. Much of it would likely be hard to do incrementally. It's unlikely such a pipeline would be efficient enough to beat restarting the app. It could be worth an experiment though.

F# already has incremental compile capabilities. The problem is translating them into the delta format expected by the HotReload. That the delta format is not well documented right now, and not all changes are supported. We'd have to analyze the compiled code for unsupported edits.

EDIT

I was thinking in existing tools, which probably wouldn't work. But A bit more thought, and direct transpilation to C# might not be such a bad idea. We'd have to take a look at Fabel to see how much work it is to map the AST. If we can preserve incremental compiles and feed those to a MSBuildWorkspace, which could also detect incremental changes.... it might be fast enough and offload concern for rude edit detection

badgh commented

I also mean not using decompile from IL, but using the same logic as they use in incremental F# to JavaScript Fable compilation.

Giraffe is built on top of ASP.NET Core, and so it receives all updates made in ASP.NET Core for free (like 50-100% speed increase in .NET 5). In the same way, if F# was also built on top of C#, so to speak (i.e. compiled to C#), as a side experiment, it ัould receive hot reload and any other potential benefits which C# will definitely receive in future. These are just thoughts, they may or may not be feasible...

@farlee2121 Hey! I know it's a lot of time since you've touched this topic but I'd like to ask you anyway. In net6 dotnet watch was working for C# projects that reference F# projects.

For examlpe: new blazor server project -> reference to new F# class library project. Run dotnet watch, change somethig in C#/razor markup -> hot reload applies. Change something in F# -> rebuild. So it's okay'ish.

In net7 they somehow changed workspace thingy so in the same situation any change to cs/razor files now trigger full rebuild.

Did you have to implement specific F# project loader and friends? I think I will have to check out your fork because it became quite sad to use C# + F# combos.

@dsyme @vzarytovskii Any plans doing this in Net8? Would be cool to have an option to at least change methods/let functions compiled to methods. UI frameworks are usually based on methods.

@dsyme @vzarytovskii Any plans doing this in Net8? Would be cool to have an option to at least change methods/let functions compiled to methods. UI frameworks are usually based on methods.

No, no concrete plans as for now, we would certainly like to get this working, but it will need to get some proper planning and framing.

As for now it's unknown what will be involved.

I suspect a bunch of work will intersect with expression evaluator.

@En3Tho I didn't get so far as a special project loader. The biggest outstanding risk was generating the deltas. Delta generation ended up a much larger and more opaque problem than expected, so I dropped the project.

The Ionide proj-info project is probably your best starting place. The F# team explicitly chose not to own a project system. There are some Roslyn workspace integrations as part of the VS tooling, but I think those were getting project trees handed in from an external source I couldn't trace. It's been a while though.

That's as best as I remember. There may be more project system research info in my notes if you're willing to dig

I'm using Blazor C# with F# driving the business logic, in fact, F# accounts for roughly 75% of the product. Because of the existence of fsproj, it puts the hot reload into rude edit mode, which I pick 'always' to restart the application whenever I get a rude edit. At that point, it'll just refresh the page whenever I change my code. When editing the F# code, I have to stop everything and restart the application. To move on and get the benefits of hot-reload, I made a Vue project using Vue CLI, where I created a component library. Each time I approve a component in Vue, I convert it to the Blazor application. The components are nearly identical, except for the programming nuance. I was going to get on the Bolero train, but don't ask me why I'm not using it... it's a good project, though.

I'm using Blazor C# with F# driving the business logic, in fact, F# accounts for roughly 75% of the product. Because of the existence of fsproj, it puts the hot reload into rude edit mode, which I pick 'always' to restart the application whenever I get a rude edit. At that point, it'll just refresh the page whenever I change my code. When editing the F# code, I have to stop everything and restart the application. To move on and get the benefits of hot-reload, I made a Vue project using Vue CLI, where I created a component library. Each time I approve a component in Vue, I convert it to the Blazor application. The components are nearly identical, except for the programming nuance. I was going to get on the Bolero train, but don't ask me why I'm not using it... it's a good project, though.

Hot reload is still on the radar, however it showed to be much harder than anticipated.

@sksallaj82 presence of F# forcing rude edits is partly a .Net 7 regression. Before this C# code could be easily edited w/o rude edits even while having a dependency on F# project. dotnet/sdk#28998

One of ways to work around this thing is to fork sdk repo and do some tweaks as I suggested in this issue

Other is obviously wait for F# team to implement workspaces properly as first step to hot reload

Please leave a message in that issue so it won't be a one man problem

@vzarytovskii I know that hot reload is considered to be a huge effort, but is there anything the community can do to move things forward? @farlee2121 was able to make (what appears to be) at least a bit of progress. I'm not sure if there have been many conversations on what is required to complete this feature, or if new tasks have come to light. Some questions I have as a (relative) layperson:

  1. How is project loading related to the hot reload effort?
  2. How is the F# EE related to hot reload? In a previous comment you had said you suspected there would be overlap with the EE and hot reload, has that been confirmed?
  3. Hot reload is still on the radar, however it showed to be much harder than anticipated. What are the major blockers that you are aware of?
  4. Does this work intersect with the transparent compiler initiative at all?

I'm really eager to be able to use this feature, especially as it would massively improve some workflows related UI programming (a focus of mine at the moment ๐Ÿ˜… ).

Thank you!

@vzarytovskii I know that hot reload is considered to be a huge effort, but is there anything the community can do to move things forward? @farlee2121 was able to make (what appears to be) at least a bit of progress. I'm not sure if there have been many conversations on what is required to complete this feature, or if new tasks have come to light.

I guess any help with figuring out all the components involved in C#'s hot reload will be helpful. Will allow us to frame things better.

Some questions I have as a (relative) layperson:

  1. How is project loading related to the hot reload effort?

Hot reload is coupled with MSBuild, workspaces and project evaluation FWIU. Maybe @baronfel has more context here?

  1. How is the F# EE related to hot reload? In a previous comment you had said you suspected there would be overlap with the EE and hot reload, has that been confirmed?

To be able to properly reload running project, we need to either:

a. Know how to compile changed piece of code and inject it into running process/patch IL (kind of like what C# does when we evaluating expressions in debugger right now).
b. Fully reload and restart everything from scratch (in this case, I wouldn't consider it a hot reload, but just restart).

  1. Hot reload is still on the radar, however it showed to be much harder than anticipated. What are the major blockers that you are aware of?

I guess the main blocker is that we don't really know/understand the full picture of what's involved in this work. We have bits and pieces.

  1. Does this work intersect with the transparent compiler initiative at all?

No, they are not related at all. Transparent compiler is mostly about new compiler/checker pipeline, not really related to codegen.

I'm using Blazor C# with F# driving the business logic, in fact, F# accounts for roughly 75% of the product. Because of the existence of fsproj, it puts the hot reload into rude edit mode, which I pick 'always' to restart the application whenever I get a rude edit. At that point, it'll just refresh the page whenever I change my code. When editing the F# code, I have to stop everything and restart the application. To move on and get the benefits of hot-reload, I made a Vue project using Vue CLI, where I created a component library. Each time I approve a component in Vue, I convert it to the Blazor application. The components are nearly identical, except for the programming nuance. I was going to get on the Bolero train, but don't ask me why I'm not using it... it's a good project, though.

Hot reload is still on the radar, however it showed to be much harder than anticipated.

I am creating some HTML reports with Giraffe in Visual Studio and just realized hot module reloading wasn't working, which led me here.
Are there any updates on F# getting this goodness?

@tmat , Do you happen to have any additional insight on what steps we can take to add hot reload support to F#?

tmat commented

@NatElkins I think it'd be best to follow the code paths in Roslyn.

You can start with the entry-point, which is EditAndContinueLanguageService. This is called by the debugger via IManagedHotReloadLanguageService interface. We are actively working on these interfaces to simplify and unify code paths between VS and C# DevKit, so expect some changes. Placing breakpoints and debugging through it in VS should give you some idea what's going on though.

From there we call EditAndContinueService in our Roslyn server ("out-of-proc") when running in VS.
This service tracks active DebuggingSession which in turn tracks the current EditSession. EditSession handles a lot of heavy lifting around finding out which documents in the solution changed when compared to the source code the baseline was built from. A lot of complexity here due to #line directives and handling project system quirks.

Once we know what the changed documents are we diff them and analyze the textual changes to find out which symbols are affected by the change and how. This is rather sophisticated analysis and entirely language specific. See AbstractEditAndContinueAnalyzer for the implementation. This analysis also reports rude edits. We have another entry point here that LSP pull diagnostic system calls to report rude edits while the user is typing in the editor.

Once the set of semantic changes is determined and if there are no rude edits or other issues EditSession.EmitSolutionUpdateAsync calls into the compiler to emit IL+metadata+PDB deltas using Compilation.EmitDifference API.

The compiler finds the symbols that need to be recompiled and emits them. There is a lot of sophistication here as well that's specific to language syntax, semantics and how each language construct maps to the IL and metadata. When emitting IL and metadata updates it is often desirable (more so for EnC then for Hot Reload) to emit matching metadata names/signatures for compiler-generated metadata entities or emit local variables of updated methods to matching slots. For example, if the code registers a lambda in an event handler and the user updates the body of the lambda we need to update the existing IL method body generated for the lambda, not generate a new one. This is because the delegate created for the original lambda wouldn't know about the new method, so it would keep invoking the original one.

Once the compiler produces lowered representation of the changes that have the proper matching signatures, slots, etc. it needs to emit it to the EnC delta metadata format. Here we need to assign tokens to the entities that match the previous metadata entity of the same signature. We also need to keep track of deleted entities (a method can be deleted, added back and deleted again).

Any plan for this in 2025?

Now we got a lot of frameworks for fsharp to develop web UI targeted to dotnet like Falco, Giraffe, Fun.Blazor etc. If the hot-reload can be supported then the dev experience will be at the top layer. The hot-reload is really needed, I think it should be put at the top priority.

It is a huge pain that putts all this stuff away of F# developers. As without the hot reload developing frontend in F#/.NET is not economically efficient โ˜น๏ธ. Only struggles and disappointment

Now we got a lot of frameworks for fsharp to develop web UI targeted to dotnet like Falco, Giraffe, Fun.Blazor etc. If the hot-reload can be supported then the dev experience will be at the top layer. The hot-reload is really needed, I think it should be put at the top priority.

โž•๐Ÿ’ฏ

Note that you can also use vite-plugin-fable to develop web apps with Fable and Viteโ€™s integrated (JavaScript) hot reload system.

I know fable is there, but a lot of frameworks are targeting to dotnet.

I do very much envy Fable's compile times when I'm working on server side or desktop stuff.

Here's a recent presentation from JetBrains' .NET Days that goes over some hot reload internals: https://www.youtube.com/watch?v=MGUPW9sK2Vg

Here's another one with some internal details: https://www.youtube.com/watch?v=tVmABHwxuqE

Cool! now we know who we need to ping and ask questions