Showing posts with label F#. Show all posts
Showing posts with label F#. Show all posts

Thursday, July 11, 2024

F# and OpenSilver v3.0

An update to the previous series of posts.

Having updated to the OpenSilver3.0 .vsix, the process of porting the standard astroclock test app from 2.1 to a newly minted 3.0 base solution was

  1. Add a global.json to keep the dotnet 9.0 pv SDK from being selected by default
  2. Copy the appliction source and resource files from the 2.1 project
  3. Add the extra files to the project - in this case, Computation.fs and textured_paper.png
  4. Adjust the app namespace in the browser and simulator projects
  5. And that's it.

The process was entirely transparent from the app and user experience point of view.

Only the one niggle - an obsolete code warning in the generated browser project persists from previous releases

Severity	Code	Description	Project	File	Line	Suppression State	Details
Warning (active)	CS0618	'WebAssemblyJSRuntime.InvokeUnmarshalled<T0, TResult>(string, T0)' is obsolete: 'This method is obsolete. Use JSImportAttribute instead.'	astroclock.opensilver3._0.Browser	D:\Github\astroclock\astroclock.opensilver3.0\astroclock.opensilver3.0\astroclock.opensilver3._0.Browser\Interop\UnmarshalledJavaScriptExecutionHandler.cs	19		

The code lives here.

Now when life quietens down, I shall have to port my other work-in-progress project, that has been languishing for several months now, from 2.1 to 3.0 as well.

Saturday, February 10, 2024

F# and OpenSilver v2.1

An update to the previous series of posts, just three months after the last one.

Compared to previous iterations, "it just works". The only changes from the original Silverlight code are

  • The XAML is precompiled where it stands in a new F# App project, rather than being loaded at runtime
  • The Y-coordinate related changes in the XAML and F# from 2.0 need to be applied
  • The background image is compiled as Content, and in the XAML, referred to by ImageSource="ms-appx://textured_paper.png", but is otherwise as before (note, I've change the background texture on my website in the last 15 years)

In particular, no messing about with IntermediateOutputPath, or having to create stubs for anything; as I said, "it just works".

Now it's all in one place, I've deployed a live version too.

Wednesday, November 08, 2023

F# and OpenSilver v2.0 (Continued)

An update to the previous series of posts.

Building while NuGetting

While I stand by my assertion that the previous post showed the simplest way to build (by not using NuGet to get the package, but referencing the key assembly the old-fashioned way) here is how to build with OpenSilver 2.0.1 as a NuGet reference. First, add properties

    <SkipXamlPreprocessor>true</SkipXamlPreprocessor>
    <OpenSilverGenerateAssemblyInfo>false</OpenSilverGenerateAssemblyInfo>

then if there isn't already a $(ProjectName).OpenSilver.XamlDesigner.fs file in the root of the project (generated there by the OpenSilver compiler taking some untested path before it was switched off), it should look like this:

// <auto-generated>
//     Generated by the FSharp WriteCodeFragment class.
// </auto-generated>
namespace FSharp

open System
open System.Reflection


[<assembly: System.Runtime.CompilerServices.InternalsVisibleTo("XamlDesignerBackground")>]
do()

added with Compile Action of None, and then see it's copied into the appropriate place by

  <Target Name="FixUp" BeforeTargets="BeforeCompile;CoreCompile">
    <ItemGroup>
        <MisgeneratedFiles Include="$(ProjectName).AssemblyInfo.OpenSilver.XamlDesigner.fs"/>
    </ItemGroup>
    
    <Copy SourceFiles="@(MisgeneratedFiles)"
          DestinationFolder="$(IntermediateOutputPath)"
        />
  </Target>

Publishing less

In the browser project, rather than enabling AOT, which saves time, and not space, have properties

    <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
    <BlazorEnableCompression>false</BlazorEnableCompression>
    <!-- Uncomment to enable AOT compilation when publishing -->
    <!--<RunAOTCompilation>true</RunAOTCompilation>-->

and it doesn't hurt to switch of satellites in the Presentation project, either.

That takes us from

to

Thursday, November 02, 2023

F# and OpenSilver v2.0

An update to the previous series of posts following the v2.0 stable release of OpenSilver.

The good news is that proper F# support in promised in the pipeline, but for now we still have to do some work.

Proceed as before, but in the F# project that inherits from the abstract C# w/XAML, a NuGet based reference to OpenSilver 2.x invokes the XAML preprocessor, and adds a file in the intermediate output directory to the compilation list. I've not found any simpler way to suppress this than by side-stepping NuGet entirely for this project

    <Reference Include="OpenSilver">
      <HintPath>$(NuGetPackageRoot)opensilver\2.0.1\lib\netstandard2.0\OpenSilver.dll</HintPath>
    </Reference>

and having to manage the version.

The other thing I noticed in 2.0 was that the sense of the Y-axis seems to have changed wrt render transforms, e.g. to draw a clock face with a series of lines radiating from a centre of (120,120) like this, rotations incrementing 30 degrees at a time, the negative Y values from 1.1 needed to be positive

        <Line
      Name="t1"
      X1="0" Y1="-108"
      X2="0" Y2="-100"
      Stroke="White"
      StrokeThickness="2">
          <Line.RenderTransform>
            <CompositeTransform Rotation="30" TranslateX="120" TranslateY="120" />
          </Line.RenderTransform>
        </Line>

and for the clock hands, add an extra 180 degrees as well as negating the Y values (that the unlabelled 1 o'clock and 7 o'clock lines have swapped makes no odds, but the hands are important).

The resulting code for running with v2.0 can be found here. The published code comes to ~130Mb, which is a lot chunkier than 25kb of JavaScript needed to do the job, but I guess it could be possible for many apps to share the same library files.

Wednesday, November 16, 2022

Hacking Fake.Build for net7.0

It's too early yet to do the AltCover after-action for net7.0 like I did for net5.0; but it is proving a slog like net5.0 rather than being too transparent to mention like net6.0 was.

While the dotnet 7.0.100 build has its own problems, the Fake build system currently (5.23.x, 6.0 alpha) doesn't handle it at all - the former may work for limited scenarios, but more complicated ones fall over with library version mismatches; the latter politely askes for a v6 runtime and stops.

So, you have a build process based on a Fake script, but want to run with v7.0.100 as SDK, through your project global.json. What do?

Well, the Fake documentation gives some alternatives - turn the script into a net7.0 project that links against the Fake libraries; but that imposes some restrictions on how you set up targets, because you can only define such things (as opposed to the functions that define the target's intended action) after setting up a build context. Or you can run as a fsi script, but the documentation then leads into how to hack paket into the mix.

However, since net5.0, scripts are able to reference nuget packages directly, so rather than having a paket.dependencies, with a #r "paket: groupref [whatever] //", and a call out to paket on the command line, your scripts can be self-contained, with a block of #r "nuget: [library][, optional version]" lines at the top, and a command line that is just dotnet fsi [my fake script.fsx]

And doing it this way, you can build on the net7.0 runtime.

Friday, October 07, 2022

F# and OpenSilver v1.1

An update to the previous series of posts following the v1.1 stable release of OpenSilver.

The recipe given as before just works, when all packages are updated and the browser project build is done with net6.0. One warning is emitted using the latest (6.0.401) SDK, about the IntermediateOutputPath override. The fix is to create a Directory.Build.props in the browser project directory, and move the two lines added to the project previously to the new file

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <IntermediateOutputPath>shorter file path here</IntermediateOutputPath>
    <BaseIntermediateOutputPath>$(IntermediateOutputPath)</BaseIntermediateOutputPath>
  </PropertyGroup>
</Project>

While the System.Windows.Application.LoadComponent API now exists, it requires build-time XAML compilation, and that the named class which it decorates exists in the same assembly, so at this time would only make an adjustment to the C# assembly, replacing the constructor InitializeComponent call with one to LoadComponent, but leaving the same inheritance in the F# layer.

It is possible that the 2.0 release will allow XAML compilation in the F# layer, but on-the-fly compilation seems not to be on the cards.

Also, in language independent mode, the ImageBrush and ImageSource APIs used by the original Silverlight AstroClock are now present. Alas, while that means uncommenting that XAML clause can be done without compile or runtime error, the interesting bits are marked NotImplemented, and live in a "work in progress" source folder, so there is currently no point in doing so.

The resulting code for running with v1.1 can be found here.

Friday, May 13, 2022

F# under the covers XIX -- `with` clauses

Once again, the half-year update has included some interesting changes in code generation, this time around try/with clauses.

Two cases to show this, one simple, one more complex. As always, decompilations are for debug (unoptimized) builds, though in this case, the release builds differ only in details of the branch instructions.

      try
        ...
      with
      | :? FormatException -> Default
      try
        ...
      with
      | :? ArgumentException as a -> a |> (logException store)
      | :? NotSupportedException as n -> n |> (logException store)
      | :? IOException as i -> i |> (logException store)
      | :? System.Security.SecurityException as s -> s |> (logException store)
      | :? UnauthorizedAccessException as u -> u |> (logException store)

Before the 6.0.300 SDK update, the simple case was entirely direct in its handling of the exception

	.try
	{
	...
		IL_006d: leave.s IL_0098
	} // end .try
	catch [netstandard]System.Object
	{
		IL_006f: castclass [netstandard]System.Exception
		IL_0074: stloc.s 8
		IL_0076: ldloc.s 8
		IL_0078: isinst [netstandard]System.FormatException

but afterwards it becomes much fancier, with duplication of effort in the handled case, and only makes sense if the exit from the filter in the unhandled case is faster than from a catch.

	.try
	{
        ...
		IL_006e: leave.s IL_00b5
	} // end .try
	filter
	{
		IL_0070: castclass [netstandard]System.Exception
		IL_0075: stloc.s 8
		IL_0077: ldloc.s 8
		IL_0079: isinst [netstandard]System.FormatException
		IL_007e: stloc.s 9
                ...
	} // end filter
	catch
	{
		IL_008c: castclass [netstandard]System.Exception
		IL_0091: stloc.s 10
		IL_0093: ldloc.s 10
		IL_0095: isinst [netstandard]System.FormatException
		IL_009a: stloc.s 11
                ...
	} // end handler

the new code being equivalent to C# catch (object obj2) when ((((Exception)obj2) is FormatException) ? true : false)

The more complex case was like

	catch [netstandard]System.Object
	{
		IL_0010: castclass [netstandard]System.Exception
		IL_0015: stloc.1
		// ArgumentException ex2 = ex as ArgumentException;
		IL_0016: ldloc.1
		IL_0017: isinst [netstandard]System.ArgumentException
		IL_001c: stloc.2
		// if (ex2 == null)
		IL_001d: ldloc.2
		IL_001e: brfalse.s IL_0022
        ...

or, decompiled

	catch (object obj)
	{
		Exception ex = (Exception)obj;
		ArgumentException ex2 = ex as ArgumentException;
		if (ex2 == null)
		{
			NotSupportedException ex3 = ex as NotSupportedException;
			if (ex3 == null)
			{
			// ... throw on exhaustion
			}
			NotSupportedException j = ex3;
			Exception e4 = j;
			logException(store, e4);
			return result;
		}
		ArgumentException a = ex2;
		Exception e5 = a;
		logException(store, e5);
		return result;
	}

but now it looks like

	catch [netstandard]System.Object
	{
		IL_0010: castclass [netstandard]System.Exception
		IL_0015: stloc.1
		// object obj2 = ex;
		IL_0016: ldloc.1
		IL_0017: stloc.2
		// if (!(obj2 is ArgumentException))
		IL_0018: ldloc.2
		IL_0019: isinst [netstandard]System.ArgumentException
		IL_001e: ldnull // omitted in release
		IL_001f: cgt.un // omitted in release
		IL_0021: brtrue.s IL_0065
...
		IL_0065: ldloc.1
		IL_0066: unbox.any [netstandard]System.ArgumentException
		IL_006b: stloc.s 7

or in C#

	catch (object obj)
	{
		Exception ex = (Exception)obj;
		object obj2 = ex;
		if (!(obj2 is ArgumentException))
		{
			object obj3 = ex;
			if (!(obj3 is NotSupportedException))
			{
			// ... throw on exhaustion
			}
			NotSupportedException j = (NotSupportedException)(object)ex;
			NotSupportedException ex5 = j;
			bool store5 = store;
			NotSupportedException e4 = ex5;
			logException(store5, e4);
			return result;
		}
		ArgumentException a = (ArgumentException)(object)ex;
		ArgumentException ex6 = a;
		bool store6 = store;
		ArgumentException e5 = ex6;
		logException(store6, e5);
		return result;
	}

which discards the results of the isinst instructions, then does an unbox.any for any case that actually matches; which could provide a marginal space improvement, in the case where no clause matches -- i.e. another possible optimization the unhandled exception scenario; which in both cases are the "I don't care, this has just failed completely" route.

Wednesday, October 13, 2021

F# and OpenSilver v1.0

An update to the previous series of posts following the first stable release of OpenSilver.

While the details of which lines of code in the Browser and Simulator projects need to be redirected at the F# types have changed since the earlier posts at alpha-7 release, the principle remains the same -- where the generated code refers to the C#/XAML App type, replace that with the derived F# type. The F# (logic) and C# (just the XAML, reified as abstract base types) are set up exactly as before.

If you're porting an old solution, from a pre-release OpenSilver, unless you've been staying on the bleeding edge, it's simpler to start by creating a new one, copying the F# project and the XAML project from old to overwrite the new, and otherwise starting from scratch, remembering to check in all the as-generated files before making the edits.

At this point, provided that all the repointing to the F# project and types is done correctly, it should all "just work".

There is one gotcha, though, that has appeared in the updates for publishing the Browser WASM since my alpha-7 exploration, in that some potentially very long file paths can now be invoked that point deep under the project's default obj subfolder. If your project is already down a few layers from the drive root it will run foul of the Copy MSBuild task which appears to still labour under the old DOS MAX_PATH of 255 characters. Copying to an overlong path fails by timing out, so breaking the build. In that case add

    <IntermediateOutputPath>shorter file path here</IntermediateOutputPath>
    <BaseIntermediateOutputPath>$(IntermediateOutputPath)</BaseIntermediateOutputPath>

where a suitable up-tree location is specified e.g. starting at the top of the repo, or in a shallow build output directory outside the repo, rather than as a child of a project directory. Note that both values need to be set and aligned, lest defaults be assumed during the process.

The resulting code can be found here.

Monday, May 03, 2021

F# under the covers XVIII -- lambdas and closures

Consider this code, using named and anonymous inner functions

  let F1 l =
    let aux i = i + 1

    let FI li =
      let rec FII lii acc =
        match lii with
        | [] -> acc
        | x :: xs -> FII xs (aux acc)
      FII li 0
    l |> List.map (fun i -> (string i).Length)

The inner functions are compiled as FSharpFunc objects, with values closed over being injected as constructor arguments.

Before .net 5.0.200, this would make function F1 look like

public static FSharpList<int> F1<a>(FSharpList<a> l)
{
	FSharpFunc<int, int> aux = new aux@9();
	FSharpTypeFunc FI = (FSharpTypeFunc)(object)new FI@11(aux);
	return ListModule.Map<a, int>((FSharpFunc<a, int>)new F1@17<a>(), l);
}

With .net 5.0.200, the fact that some of the inner functions -- like aux above -- are pure, closing over nothing, has been taken account of, and needless new object creation is avoided, in the same way that C# lambdas have long been cached after first use.

public static FSharpList<int> F1<a>(FSharpList<a> l)
{
	FSharpFunc<int, int> aux = aux@9.@_instance;
	FSharpTypeFunc FI = (FSharpTypeFunc)(object)new FI@11(aux);
	return ListModule.Map<a, int>((FSharpFunc<a, int>)F1@17<a>.@_instance, l);
}

where the aux and F1@17 functions -- the latter being the anonymous function used by List.map -- are referenced through a class internal static readonly value, rather than having to create a new instance every time.

String processing as a fold

Having occasion recently to ensure that text in XML/HTML containing non-ASCII (high-bit set) characters, but no control codes aside from line breaks, was presenting them as character references, the obvious algorithm in C#, using a StringBuilder, sb, was

  foreach( char ch in text )
  {
    if ( ch < 127 ) 
      { sb.Append(ch); }
    else
      { sb.AppendFormat( "&#x{0:X4}", (int) ch ); }
  }

In F# though, the obvious direct Seq.iter translation ends up needing |> ignore the results of the append operations. Since this is actually an accumulation operation into the StringBuilder, the better functional representation would be more like

  let sb = Seq.fold (fun (b:StringBuilder)
                         (c:char) -> let ic = int c
                                     if ic >= 127
                                     then b.AppendFormat( "&#x{0:X4};", ic )
                                     else b.Append(c))
              (StringBuilder(text.Length + extra)) // estimate the expansion up front
              text

which lets the StringBuilder flow naturally through the process, rather than closing over it and having to discard the value of the if expression. This could be done in C#, too along the lines of

  var sb = text.Aggregate(new StringBuilder(), (b, c) =>
                                     if (c >= 127) 
                                       {return b.AppendFormat("&#x{0:X4};", c);}
                                     else 
                                       {return b.Append(c);});

only here the returns have to be explicit.

Thursday, March 04, 2021

F# and XAML and OpenSilver ctd.

Last time, we concluded that what we really want is a reimplementation of the Silverlight System.Windows.Application.LoadComponent, whereas what we have at the moment is generated C# code built from the XAML.

In practice, those generated InitializeComponent methods are a compile-time version of what the runtime component load would give us. This leads to a next step that takes us in the direction of what our ideal would be, and is in any case closer to the initial manual code-only approach.

Start with a new OpenSilver application, and add an F# netstandard2.0 library like last time. This time, however, make this assembly depend on the primary OpenSilver project, and the .Simulation and .Browser projects depend on the F# library. Add OpenSilver to the F# library project, as a nuget package, but this time there's no need to add any ExcludeAssets qualifier, nor to static link the F# core library.

The F# library now contains the Page (or UserControl) and Application classes that you would want to write, but their XAML goes into the C# project that it builds against. In the XAML project, remove everything in the initial classes after the // Enter construction logic here... comments. In the .Simulation project, point the debug parameters at the F# library, and in the .Browser project's RunApplication method, construct the F# Application type. This is important, otherwise nothing will show when you try to run the application!

We can now go one of two ways to invoke the InitializeComponent methods into the F# code.

First, we can treat the generated InitializeComponent code as almost the static utility LoadComponent method we would desire; in which case, in the F# library, the Application leeches from the corresponding generated code by

    member this.InitializeComponent() =
        let prototype = Inversion.App()
        this.Resources <- prototype.Resources

and the Page similarly

    member this.InitializeComponent() =
       let prototype = Inversion.MainPage()
       prototype.InitializeComponent()
       this.Content <- prototype.Content
       let hack = typeof.GetField("_nameScopeDictionary",
                                                System.Reflection.BindingFlags.Instance |||
                                                System.Reflection.BindingFlags.NonPublic)

       hack
       |> Option.ofObj
       |> Option.map ((fun f -> f.GetValue prototype) >> Option.ofObj)
       |> Option.flatten
       |> Option.map(fun o -> o :?> System.Collections.Generic.Dictionary<string, Object>)
       |> Option.iter (fun dict ->
             dict
             |> Seq.iter (fun kvp ->>this.RegisterName(kvp.Key, kvp.Value)))

takes the work from its pre-processed XAML, here using an over-engineered construction to get at the private name registration dictionary while avoiding any hint of NRE. These methods should then be called from the respective F# type constructors.

Alternatively, we remove the sealed qualifier from the generated Application, mark it and the MainPage class abstract, and let the F# types directly subclass the skeleton types in the C# library; this case needs no F# level InitializeComponent methods as the code we're interested in already gets called during the base class constructor call.

If this starts with the same application code as previous -- here my original Silverlight ephemeris clock -- we get the same visual appearance as with the previous approach, so no extra pictures in this post.

F# and XAML and OpenSilver

Emboldened by yesterday's success, what happens when we try to emulate the next steps?

First off, when pasting the F# code into the project, we see that System.Windows.Application.LoadComponent isn't present. Adding the XAML file has that subsumed by the OpenSilver environment, to appear as a <Page/> element in the project file that generates C# code that also fails at compile time. That latter can be switched off by modifying the package reference to look like

    <PackageReference Include="OpenSilver" Version="1.0.0-alpha-007">
      <ExcludeAssets>build; native; contentfiles; analyzers</ExcludeAssets>
    </PackageReference>

but we still don't have the runtime-generated model that System.Windows.Application.LoadComponent would give us.

Without that support, we can still use the hybrid approach of the early F# Silverlight days.

In this case, start with a new OpenSilver project and add an F# netstandard2.0 library as a dependencyand add the modified package reference to the new F# library, to allow access to the GUI types. The C# project gets the project-related XAML file, with the necessary modifications for porting to OpenSilver, and the F# code now, rather than subclassing a UI element, works by composition and delegation, much as for the F#/Silverlight3.0 template for VS2008.

The F# type, rather than subclassing a UI type now takes one as a construction argument, and the C# initializer, having constructed the main control and initialized the UI tree can simply pass that object to the F# type constructor, storing the instance as a member variable. Any event handlers can then delegate to the F# object as well.

  public partial class MainPage : Page
  {
    private Astroclock.AstroPage carry;

    public MainPage()
    {
      this.InitializeComponent();

      var temp = new Astroclock.AstroPage(this);
      temp.Begin();
      carry = temp;
    }
  ...

At this point, attempting a build fails in the C# layer with

2>MSBUILD : error : C#/XAML for HTML5: BeforeXamlPreprocessor (pass 1) failed: System.IO.FileNotFoundException: Could not load file or assembly 'FSharp.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.

so we go back and add

    <OtherFlags>--standalone</OtherFlags>  

to the top-level Property Group on the F# project, and suddenly things start working.

For an example at this stage, I've ported the full applet to OpenSilver here. The other significant change to mention is that rather than just being a UserControl embedded into HTML, this is now a Page with the static HTML text included as TextBlock elements instead, but that is a generic OpenSilver behaviour I've just gone with the flow on, not something F# specific. Nor, I suspect, are the not quite circular circles...

Of course what we really want is a reimplementation of the Silverlight System.Windows.Application.LoadComponent. It's just a pity that that dives into native code in agcore.dll to do the interesting bits.

Also, alas, as currently implemented the browser version doesn't update even though the simulator does. Maybe if the updates were an event driven thing, like the button in the previous example?

Update: Events did the trick -- removing the sleepy BackgroundWorkers from the F# code, and instead adding a DispatcherTimer to fire the relevant UpdateXxx methods once a second unblocked the browser behaviour. The timer can be in either the C# or the F# layer. Again, I suspect that this is generic OpenSilver/Blazor behaviour, not anything F# specific.

Next -- A more LoadComponent-like approach with other benefits.

Wednesday, March 03, 2021

F# and OpenSilver -- first steps

This set of lab notes turned into a series of 3 4 (with the 1.0 release) posts.


Many years ago, I made a series of posts experimenting with F# (then still in preview) and Silverlight

  • documenting some of the hacks required to get it to work. Now, more than a decade later, WASM is the new in-browser hotness, with many approaches to getting your non-JS code to run in browser.

    One of these is OpenSilver, which is intended as a near as possible drop-in replacement for Silverlight. Like the original, it assumes C# and XAML with code-behind first, but let's see what we can do about that...

    Assuming you have OpenSilver set up, then the equivalent of the first post above is relatively simple. Make a new OpenSilver application solution, then replace the actual application project with a new netstandard2.0 F# library (in the solution and as a project reference in the two helper projects). Add OpenSilver as a nuget package to the F# project, then paste in the code from the same first working example to replace the initial stub code. In the .Browser project, fix the type of the App class -- new SilverLightFSharp.MyApp(); -- and then run whichever helper.

    It should look like this :--

    in the simulator after you click the button.

    Next -- Adding XAML, initial attempts

    Wednesday, November 18, 2020

    AltCover -- the road to .net 5, F# 5, and GitHub actions

    A for the record listing of what was needed to make the transition for a project that's over a decade old -- the current GitHub repo is only 3 years old, but that was formed by grafting the relevant contents of the original 2010-vintage SVN history onto a 2H17 git bootstrap.

    In the original 10Q2-11Q1 development, the build system was a PowerShell script that called msbuild on the .net3.5 solution, then did an automated copy and edit to also build against .net4; taking up the reins again in 17Q3, the latter were used as the basis for going forwards. Over time, parallel .net core projects were added, which eventually subsumed the originals.

    Thus, on the eve of the .net5 release, we had

    • A main solution targeting net472 (debug only) and netstandard2.0 for libraries; net472, netcoreapp2.0 and netcoreapp2.1 for tooling with netcoreapp3.0 for unit tests
    • A separate solution building a net20 library (the run-anywhere recorder) plus tests at net20/net472/netcoreapp3.0
    • A separate solution for the GUI tool, targeting net472 (debug only) and netstandard2.0 for libraries, net472 with a reference to the GTK2# assemblies in the GAC, and netcoreapp2.1 for tooling and netcoreapp3.0 for unit tests
    • Finally a solution for building a front end to the Mono C# compiler library, used to provide test data, still in the old project style

    So, all very good and mostly modern -- the transition should have been trivial then...

    Not so fast...

    Once I had the F#5 compiler installed, that broke the net20 build (looking for a System.Numerics.dll which was still part of FSharp.Core in net20). Applying an earlier F# compiler via NuGet package fixed that in Visual Studio, but then dotnet build failed, so I had to resort to msbuild-ing the recorder solution. But then on the travis-ci build, that meant that the net5.0 (upgraded from netcoreapp3.0) unit test target got interpreted as .net Framework 5.0, and the build failed for lack of approprate referenced assemblies. So, as you were before with the unit tests, at least for the moment.

    At this point, one obvious result of maintaining backwards compatibility by building deliverable executables (and unit tests) against older netcoreapp* targets, but with "rollForwardOnNoCandidateFx": 2 in runtimeconfig.template.json was a profusion of NETSDK1138 obsolescence warnings; silenced by building with /p:CheckEolTargetFramework="false" all around.

    Where I'd been running ahead of AppVeyor's current installed .net SDKs, I'd been using Chocolatey to bring down the version I wanted; time to be aware that the package has been renamed from dotnetcore-sdk to dotnet-sdk instead.

    That got things building on the "traditional" CI servers, so, a good time to make a first try of GitHub Actions for CI as well.

    With the at-time-of-writing latest windows and linux platforms, that meant that I didn't have a "modern enough" msbuild.exe on the PATH for the recorder and the GUI tool. To resolve that, I resorted to finding the path of the MSBuild.dll from the current .net5 SDK (via parsing the output of dotnet --info for the Base Path: value), and lashing up a way to use it with Fake's MSBuild command line construction support.

    Fall-out from this was needing to compile string resources ahead of time for the recorder -- no ResGen.exe support in the new regime -- and use the same (earlier) compiler for all the test platforms; also creating a set of reference assemblies for GTK#2, so I could avoid having any reference to the GAC (meaning that the GUI tool solution would now dotnet build, even if I still have to MSBuild the recorder solution). The Mono C# compiler project -- also using MSBuild -- which still made references out to .targets files out in Visual Studio or Windows SDK parts of MSBuildExtensionsPath32-land also needed attention; as a retro project, I simply added the earlier F# compiler package here, too, to make it build.

    On the plus side, now the net20 project is using the MSBuild executable from the net5 SDK, switching the unit tests all round to target net5.0 works as one would hope it to work.

    Tuesday, May 26, 2020

    Computing cyclomatic complexity with F# 5.0 interactive and Mono.Cecil

    Many moons ago, I posted a simple PowerShell script to use the FxCop SDK to introspect over a bunch of assemblies and compute a measure of the cyclomatic complexity of each method; in this case, by counting the number of IL branch instructions that do not branch to the next instruction, and which have a distinct target from any other branch.

    That in turn was based upon the algorithm used in NDepend 1.x, which was sufficiently antique to not consider switch opcodes, a deficiency which can be worked around. And as an alternative, the algorithm used in Mono.Gendarme can be implemented in F# instead.

    The main barrier to creating a simple replacement with Mono.Cecil has been the location of the assemblies with the NuGet package version in the file path. Now, however, with F# 5.0 interactive allowing reference-to-nuget statements, providing a pair of scripts that can do the job, without the awkward question "Where's Cecil?", becomes a simple task.

    The classic version first

    and another using the algorithm from Mono.Gendarme

    Monday, August 05, 2019

    F# under the covers XVII -- Code reuse loopiness

    For some level of complexity and size, the F# compiler is happy to say "I've already done something like that, let's jump back and do it again!" putting an apparent loop into iteration-free code. It's not a common thing -- I've seen it exactly once the the FSharp.Core v4.7 library --

            match t1, t2 with
            | SetEmpty, t2  -> add comparer k t2 // drop t1 = empty 
            | t1, SetEmpty  -> add comparer k t1 // drop t2 = empty 
            | SetOne k1, t2 -> add comparer k (add comparer k1 t2)
            | t1, SetOne k2 -> add comparer k (add comparer k2 t1)
            | SetNode (k1, t11, t12, h1), SetNode (k2, t21, t22, h2) ->
    

    The IL for this contains the expected forward conditional jumps from the match to the cases, but the second SetOne case preps the k2, t1 arguments then jumps backwards into the middle of the previous case, and exits after completing the shared call structure.

     IL_0029: ldloc.2
     IL_002a: isinst class Microsoft.FSharp.Collections.SetTree`1/SetOne
     IL_002f: brtrue.s IL_0055
    
     // item2 = t2;
     IL_0031: ldarg.3
     IL_0032: stloc.0
     // item = setOne.item;
     IL_0033: ldloc.1
     IL_0034: ldfld !0 class Microsoft.FSharp.Collections.SetTree`1/SetOne::item
     IL_0039: stloc.3
    
     // return add(comparer, k, add(comparer, item, item2));
     IL_003a: ldarg.0
     IL_003b: ldarg.2
     IL_003c: ldarg.0
     IL_003d: ldloc.3
     IL_003e: ldloc.0
     IL_003f: call class Microsoft.FSharp.Collections.SetTree`1 Microsoft.FSharp.Collections.SetTreeModule::'add'(class [mscorlib]System.Collections.Generic.IComparer`1, !!0, class Microsoft.FSharp.Collections.SetTree`1)
     IL_0044: call class Microsoft.FSharp.Collections.SetTree`1 Microsoft.FSharp.Collections.SetTreeModule::'add'(class [mscorlib]System.Collections.Generic.IComparer`1, !!0, class Microsoft.FSharp.Collections.SetTree`1)
     // (no C# code)
     IL_0049: ret
    
     // item2 = t1;
     IL_004a: ldarg.1
     IL_004b: stloc.0
    
     // return add(comparer, k, item2);
     IL_004c: ldarg.0
     IL_004d: ldarg.2
     IL_004e: ldloc.0
     IL_004f: call class Microsoft.FSharp.Collections.SetTree`1 Microsoft.FSharp.Collections.SetTreeModule::'add'(class [mscorlib]System.Collections.Generic.IComparer`1, !!0, class Microsoft.FSharp.Collections.SetTree`1)
     // (no C# code)
     IL_0054: ret
    
     // item2 = t2;
     IL_0055: ldarg.3
     // item = setOne.item;
     IL_0056: ldloc.1
     IL_0057: ldfld !0 class Microsoft.FSharp.Collections.SetTree`1/SetOne::item
     IL_005c: stloc.3
     // (no C# code)
     IL_005d: stloc.0
     IL_005e: br.s IL_003a
    

    This provided an interesting exercise for the branch-chasing algorithm in AltCover (inspired by the one in OpenCover), which hadn't anticipated a backwards leap inside a sequence point, and went off into a spin until told to look for ret and similar termination points.

    Thursday, February 01, 2018

    F# under the covers XVI -- Constructor weirdness

    Occasionally, even in F#, one needs to do OO stuff, like implementing a concrete subclass of some abstract framework type to feed into some other framework API. In my case, I recently needed to add a SerializationBinder to a BinaryFormatter to handle assembly versioning.

    So of course I wrote

    formatter.Binder <- { new System.Runtime.Serialization.SerializationBinder()
      with member self.BindToType (a:string, t:string) = ... }

    which worked perfectly happily, but threw up a warning from Gendarme about suspicious recursion in the constructor.

    So I decompiled the type to find it looked like

    [Serializable]
    [StructLayout(LayoutKind.Auto, CharSet = CharSet.Auto)]
    [CompilationMapping(SourceConstructFlags.Closure)]
    internal sealed class ReadResults@64 : SerializationBinder
    {
     public ReadResults@64()
     {
      ((SerializationBinder)this)..ctor();
     }
    
     public override Type BindToType(string _arg1, string _arg2)
     {
      ...
     }
    }

    or, as IL

    .method public specialname rtspecialname 
     instance void .ctor () cil managed 
    {
     // Method begins at RVA 0x3ce0
     // Code size 9 (0x9)
     .maxstack 8
    
     IL_0000: ldarg.0
     IL_0001: callvirt instance void [mscorlib]System.Runtime.Serialization.SerializationBinder::.ctor()
     IL_0006: ldarg.0
     IL_0007: pop
     IL_0008: ret
    }

    Writing the type as an explicit class, like

    type UpdateBinder () =
      inherit System.Runtime.Serialization.SerializationBinder()
      override self.BindToType ...

    yields exactly the same sort of IL.

    Revising the class yet again as

    type MonoTypeBinder (``type``:Type) =
      inherit System.Runtime.Serialization.SerializationBinder()
      override self.BindToType (_:string, _:string) =
        ``type``

    because I only have one type of interest, did produce the expected decompiled constructor, looking like

    public MonoTypeBinder(Type type)
      : this()
     {
      this.type = type;
     }

    even though the actual IL just adds the field assignment

    IL_0000: ldarg.0
     IL_0001: callvirt instance void [mscorlib]System.Runtime.Serialization.SerializationBinder::.ctor()
     IL_0006: ldarg.0
     IL_0007: pop
     IL_0008: ldarg.0
     IL_0009: ldarg.1
     IL_000a: stfld class [mscorlib]System.Type AltCover.MonoTypeBinder::'type'
     IL_000f: ret

    However, now we've changed the signature, the call no longer looks like a recursion. And, for once, this is a case where the virtual call in a constructor is safe.

    By contrast, a C# equivalent

    class UpdateBinder : System.Runtime.Serialization.SerializationBinder
        {
            public override Type BindToType(string a, string t)
            {
                ...
            }
        }

    generates a default constructor with IL that makes a non-virtual call to the base type

    IL_0000: ldarg.0
      IL_0001: call instance void [mscorlib]System.Runtime.Serialization.SerializationBinder::.ctor()
      IL_0006: nop
      IL_0007: ret

    and the call remains non-virtual when adding a constructor argument.

    Saturday, December 09, 2017

    Announcing AltCover

    Now I have time to myself, and the season doesn't lend itself to outdoor activities, I can start dusting off my various coding projects and devoting the time and energy to those that I used to expend for pay.

    First off the block is AltCover, an alternative code coverage tool for .net and Mono.

    This is a project I started back in the dark days in the spring of 2010, when changes in the profiling API of the new CLR 4.0 release meant that the old freeware NCover 1.5.x series no longer functioned. For some considerable while, the only FOSS alternative that worked with the new CLR (by instrumenting the code under test before execution, rather than on-the-fly) seemed to be an initial proof-of-concept on CodeProject.

    At that point, I'd been looking for something non-trivial to work on that would provide the opportunity to use F# to build up my fluency in the language; and so the obvious thing to to was to re-implement from scratch and extend to cover such gaps as I found in its functionality when trying to use it as a near drop-in replacement for the now non-functional NCover version.

    After some considerable interval and a failed dalliance with PartCover, including my first contribution to a real FOSS tool, but never a resolution to one real sticking problem, where it looked like JITting across assembly boundaries was causing executed lines to not appear in the coverage, I shelved my work in favour of the off-the-shelf OpenCover, where I could intermittently contribute enhancements to cover personal pain points.

    Why have I dusted it off again now?

    Well, for much the same reasons as before; a non-trivial project that does answer some pain-points (Mono and probably the new dotnet core amongst them) that OpenCover's necessarily intimate relationship with the runtime makes difficult. And it provides a reason to play with new toys that have grown up over the last few years, like Fake for builds, and the generous provision of CI tools to FOSS projects so people can take builds rather than having to roll their own.

    Friday, May 05, 2017

    Chunked Enumerable revisited (in F#)

    Revisiting an old post on the subject, prompted by a recent question on the MSDN Visual F# forum.

    Despite it being a very common thing to want to do, to process a stream of data in blocks, there isn't a library function for it yet; and it's not something that can be put together in a functional form using the pieces we have in the Collections.Seq module. Either, as in my earlier attempts, we have to go into object-land, with intrusions into the implementation details of enumerations (and their disposal), and eager evaluation at best; or we have to go imperative/procedural to get full lazy evaluation as per the code buried within the this C# example.

    For idiomatic F#, we don't want an extension method, but a stand-alone function which we can chain into the usual pipeline of |> operations, so we would translate it thus —


    where we bury the imperative/mutable kernel, the place where we count the number of steps we take (or stop early when we hit the end of the input), inside an inner function.

    Saturday, December 12, 2015

    A little app for monitoring connectivity

    One of the things that Windows Vista did better than the later releases was to recover network connectivity immediately when coming out of hibernation -- really, as if nothing had happened. Later releases seem to go back to square one to restart a WiFi connection -- and what is worse, there's no visual feedback from the systray icon after the initial handshake to tell when the link is actually useful.

    As this can take tens of seconds, it is the biggest pain point I have with Windows X. So, finally I got around to writing a monitoring app.

    I started with a simple systray application, and some sample code for using the Network Link Manager API as guidance for an F# application. I also needed to use the CoClass for instantiating an INetworkListManager in line 27 as noted here.

    Then it was just a matter of adding some icons, for which I used stock images for blocked, warning and OK from the Visual Studio image library to indicate no connection, not yet usable connection and internet available. Finally StackOverflow reminded me about how to add an application icon, to make a shortcut look nicer.

    So, without further ado


    Later: having slept on it, there's this simplification can be made to the OnLoad method


    Later still: when I've hibernated a laptop with this program running, both times now it has come back with the WiFi connection still live, just like it used to under Vista. Even better.