Sunday, October 02, 2022

Q3 Cycling

Hot and parched for the first two months, then cooler and damper. This meant a fair bit of riding when it was comfortable to do so, and a collapse in September.

Fen Road, Fenstanton

July was mostly business rides or enjoying the sunshine locally, with one long ride up to St. Ives at the start of the really hot spell mid-month. Notably, the month ended with the arrival of the "Our Place in Space" exhibition, which straggled from Midsummer Common, all the way to Waterbeach, with a return ride that time down Mere Way.

Inner System

Totals at the end of the month 1213.7 on the old bike (+48.8), 1438.7 on the new (+515.3), for a total of 564.1 miles for the month, 2803 YTD.

Ely in the distance

Early in the month, I did a ride through Aldreth, north through Witcham to Coveney, before returning, adding the northernmost point of the limits map. This was in the driest part of the summer, and there were signs of wildfires on Grunty Fen that looked like stubble burning had made a comeback. The dry weather also gave chance to see what Cow Lane was like in the dry (and how not-green Horse River Green had become).

I did the space ride a couple more times, to get pictures when there were fewer other folk around, exploring also the adequate (for during the day) cyclepath along the A10 from Waterbeach to Milton, and then towards the end of the month, riding up to St Ives again, up to Houghton and Wyton, before riding the St Ives perimeter road, gaining another bit of limits; then passing a major crash on the A14 and getting my photos onto the BBC news website..

Houghton Mill

Totals at the end of the month 1264.9 on the old bike (+51.2), 1913.1 on the new (+474.4), and 159.76 for the folder (+38.8) for a total of 564.4 miles for the month, 3368 YTD.

Rivey Hill West - the dry way up

Before the weather completely broke, I made another Linton to Gt Chesterford run, finding a third bridleway, and the dry way up Rivey Hill; but after a ride to explore some new trails and find lunch on my birthday, the weather broke -- some rain, but mostly cooler, so most of the rides for the rest of the month were pure A-to-B, including going to hear the city's proclamation of the new King.

Proclamation

The only long ride was the Camcycle/CTC byways and highways ride around Cambridge at the end of the month, and that mostly by virtue of having to get to and from the start in addition to the circuit (which was around the length of a pretty-way each way circuit-commute to the Science Park, if not quite the same track).

Totals at the end of the month 1541 on the old bike (+276.1), 2045.7 on the new (+132.6) for a total of 408.7 miles for the month, 3777 YTD, having passed last year's whole-year total during the group ride.

Friday, July 01, 2022

Q2 Cycling

Dry, mild to warm weather, was the overall tone of the quarter, meaning that the ground was dry enough for off-road riding; and a lot of riding did get done.

St Helen's, Colne

Good Friday, an unscripted ramble up and around the busway took me north of St. Ives, and allowed me to add a little nubbin to the east of Earith to the "Outer Limits" map (mid-green), and from there things snowballed. The Easter weekend also saw a ride along the bridleway from Little Cow Lane through Linton, following parts of the Ickneild Way -- a much drier route than the previous, until Linton, and there finding that the bridleway had a river running down the middle. And from there, the next few rides kept up the theme, following the Ickneild Way west from Gt. Chesterford, and a bit further north from Linton. The month closed with a different big ride, up byways from Rampton to Aldreth.

Totals at the end of the month 947.8 on the old bike (+336.7), 250.2 on the new (+203.1), for a total of 539.8 miles for the month, 1141 YTD.

Avenue nr Little Bradley

May Day holiday, I took more of the Ickneild Way, north from Six Mile Bottom towards Newmarket (north from Burrough Green on the Outer Limits) and back to explore the byways that linked up to the Wilbrahams; and then having pushed the limits a bit, the next week I did the section south from Burrough Green to the Thurlows, with some exploration of byways near Carlton on the return. The following week, more Ickneild Way, from Melbourne to Letchworth, and another new section of limits. Finally, rounding the month off, I took my first cycling holiday since late '17, the CycleBreaks Gardens and Gallopers tour; marred slightly by pouring rain on the first day, it was a nice mix of new, familiar and "I've only cycled this in the opposite direction before".

Totals at the end of the month 1164.9 on the old bike (+217.1), 480.9 on the new (+230.7) + 14.1 off meter, and 151 on the tour bike, for a total of 612.9 miles for the month, 1754 YTD.

Bridleway

June opened with a ride out to Burwell, the byway-defined salient on the limits map, followed by another limits buster, going down from Wendens Ambo and Newport, south of Saffrom Walden to Radwinter, then taking the Little Cow Lane path back, reaching the 2000 mile mark on 15-Jun vs 21-Aug-21, then finally a reprise of the Aldreth run, this time taking the byway looping north of Haddenham to Wilburton.

Totals at the end of the month 1164.9 on the old bike (+0), 923.4 on the new (+442.5) and 120.96 (+42.5) for the folder, for a total of 485.0 miles for the month, 2239 YTD - a number which I have considered a reasonable whole-year total in the past!

Stuck in a rut


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.

Friday, April 01, 2022

22Q1 Cycling

A milder winter than the last, with strong winds being the main feature; so a few longer rides early on, and storm-blown gaps in the record.

Late January snowdrops

January ended at 193.9 on the clock, or 116.1 miles for the month; February 314.2, meaning 120.3 miles for the month. March pushed the old bike to 611.1 on the clock (298.9 for the month including 2 off-meter), but the folder went to 78.5 (+11.9), and a new shopping bike clocked up 47.1, plus an unmetered 6.7 miles home for a total of 362.6 miles. Year to date, that's 601 miles, or about the same as last April.

Horse River Green

A burst of really warm weather near the end of the month allowed the first real exploration, rather than just pounding familiar nearby loops, this one exploring Cow Lane, from Linton to Great Chesterford, as a way of avoiding having to go as far as Saffron Walden to close the loop. Most of it is perfectly fine; but there's a section in the middle that suddenly went from dry to sodden, threading between ponds, and at times becoming indistinguishable from a river bed. Best for high summer, and from the Linton side, so the steep bit is downhill.

Saturday, January 01, 2022

Anime 2021

A generally disappointing year in all.

Spring season had a few "might watch" titles, but Crunchy picked up none of them, so I only picked up Odd Taxi, a noir-ish story of modern social-media driven ennui, which ends up being AOTY (long-form) pretty much by default.

Summer had the global release of Shin Eva, which was as much of a mess as it had seemed from original accounts, the painful and wildly mislabelled Fena, Pirate Princess (dropped after one episode of Fena being a vapid gold-digging whore), and Aquatope which began by looking like it would be cute girls doing cute aquarium things, with a little bit of low-key magic (from a local sprite wandering around in the first couple of episodes, and the occasional trippy underwater sequence), but rapidly turned into family drama and "welcome to the world of work", rather like Hanasaku Iroha, only without the bondage; and got dropped after 5 episodes.

Autumn brought just the tried and dropped Takt Op. Destiny, Sakugan and Digimon Ghost Game -- the latter being an episodic "urban legend kids' horror" series, a world away from Adventure, Adventure 02 and Adventure: that were a background to much of the year and now form an indistinguishable melange in my memory.

From the backlog, I went through Berserk in both '97 and '16 flavours, which passed some autumn evenings; but I still don't see what the fuss was about (except for the detailed crafting of the art in the original manga).

2H21 cycling

Ickneild Way, near Royston

In the second half of the year, I managed to do more each month than in 2020, reaching 2000 miles 21-Aug vs 29-Sep-20. At the end of November, I also managed to twist the frame on the old bike that had served me since '98, just shy of the 20,000 miles logged on that odo mark; and at the same time had odo problems on the newer bike, which just passed the 10,000 miles from new mark. Time to buy myself a present in the new year, I think.

As before, September was the high point -- monthly totals being July 361.1 (summer bike), August 504.4 (a little of the folding bike), September 609.9 (mostly summer bike) -- probably an all-time record -- October 429.1 (a dry month, this time, but mostly winter bike), November 309 miles (winter mostly, until written off), December 172 miles (including riding to both Christmas lunches I attended this year, much to the amazement of the others in the respective groups), for a total of just over 3700 miles for the year. The summer bike starts the new year with 77.8 miles on the (latest) clock, and the folding bike 66.5

While a lot of the rides were retreading old territory, I did a fair amount of exploration on byways, like taking the Ickneild Way most of the way from Royston to Baldock, even if many byways end up being just dead-ends; and from the ancient to the modern, making a lot of use of the new Girton to Fenstanton multi-use path in preference to the busway -- a very convenient fast route to places like the Golden Ball at Boxworth.

Henge, at Carriages, Fenstanton

The highlight of the year, though, was the opening of the long awaited Abbey Bridge, just before Christmas. Even if the Chisholm Trail itself is on the wrong side of the tracks for convenience (cycling it was only the second time in 40+ years living in the area that I'd taken the path across Coldhams Common), it means that I'll only use the Green Dragon bridge as and when I next visit the Green Dragon, which had been a regular location for pub lunch with former colleagues working on the Science Park, but alas such attendance has suffered under the continuing work-from-home regime.

Abbey Bridge, Christmas Eve, first full day of operation

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.

Tuesday, July 20, 2021

Lemon Cheesecake recipe

Delving through a mass of old paperwork for more mundane things, I found this in passing, dating from 1960s Autralia

Case: 8oz digestive biscuit crumbs + 4oz butter melted and pressed into the pie dish; chill in fridge.

Filling: Mix 2oz whole milk and 4oz cream cheese into a smooth mix, add ½ can condensed milk (about 200g) and ¼ cup fresh lemon juice (about 2 lemons) and 1tsp vanilla essence. Fold in a small can of cream (about 100g), and pour into the case.

Leave in fridge overnight, and sprinkle with freshly grated nutmeg before serving.

I've not made this in ages, but it was always a hit.

Thursday, July 01, 2021

Q2 Cycling

April was cool but dry -- only one "maybe thinking of spitting" in the whole 30 days event; and concluded with the re-opening of both the Coton footpath bridge and the jetty at Ditton Meadows, both of which I covered for the last ride of the month. At the end milage totals were winter bike 17954.3, summer bike to 3640.4 and then a battery change ending at 136.3 for 288 miles for the month.

May blossom in April

May was cool but also wet -- the usual rainy season -- though it did end with a burst of hot weather that just beat out March for hottest day of the Spring; concluding with a 48 mile ride through the fens to Wicken and Upware. The totals were 18268.3 and 136.3, for a total of 314.4 miles

Those wind turbines at close quarters

June's weather went downhill towards the end -- see the gap -- but that didn't stop me ending the month at 18718.5 and 136.3 for a total of 450.2 miles (just short of June 2015's total, but best month since), and 1319 for the year, after exploring more new cycle routes along the A14 which make for conveniant 20-mile level rides, and also going across the A11 at the Wilbrahams to explore a by-way that runs close by the foot of the wind turbine installation visible for miles around.

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.

Saturday, April 10, 2021

Anime roundup '21Q1 -- Shin Eva and others

So, yeah, there'll be spoilers for the new Evangelion movie, after the run-down of other watching.

The Q1 line-up was fairly thin, with Yuru Camp△ 2 the only title I'd marked as a definite watch, and only a handful more of "might look for to watch if well received". Yuru Camp△ 2 was not as good as the previous season -- being less about the equipping and camping, and more of a Google StreetView journey around tourist spots (including one instance of the Google watermarking not being adequately removed in the transformation from photo to anime background) -- but was pleasant enough all the same.

The surprise hit of the season, though, was Pui Pui Molcar, a series of whimsical stop-motion shorts about giant guinea-pigs that are also cute semi-autonomous vehicles

after which, most other new titles paled into insignificance.

On the rewatch front, I completed Tegami Bachi and the Koihime Musou sequels, plus Modern-Day Magic for Dummies, which all held up reasonably; and also Noir, which is indeed an decent enough actioner but which is pushed over the top by the sound track.

From the backlog, I did finish Fate/kaleid liner Prisma☆Illya 2wei Herz, which was definitely not as good as the previous season.

In the tried-and-dropped pile are the new Mamoru Oshii comedy Vlad Love, which, alas, wore out its welcome very quickly, the GTO OVA, and Cowboy Bebop (which as the OP indicated, felt like just another of those 1970s noir actioners, usually starring either Roger Moore or Tony Curtis, which I watched back in the day -- which maybe explains the popularity as being accessible for newcomers to Japanimation -- also with bouts of not funny humour). One episode each was quite enough, thank you.

Continuing are Digimon Adventure: and the original Digimon Adventure, and rewatches of THE UNLIMITED Hyobu Kyosuke, Yuru Camp△ and Saki.

OK, Shin Evangelion spoilers time now, so avert your eyes.

Having originally been set for its release last summer, and after two holds for Corona-chan, the long awaited final Evangelion movie released on the 8th of March. With curfews still in place, in lieu of a midnight showing, the first fifteen minutes -- the first action scene and the opening credits -- were streamed on YouTube, much as the short opening action scene had been televised for the previous movie.

This then caused a demonstration that computer security is hard. As part of its accessibility arrangements, a smartphone app had been provided with sub-titles for the hard of hearing and audio description for the partially sighted, to be cued by the movie sound-track playing. Armed with a capture of the YouTube stream, little work was then needed to obtain a complete set of subtitle images -- sufficiently little that they were being passed around before lights went down for the 07:30 JST premiere. The next day, in a display of belatedly bolting the stable door, the movie was pulled from the app, inconveniencing only the people in Japan who actually needed the assistive technology.

I really mean it about the spoilers now -- skip to after the picture to continue if you wish.

So yes, this movie does wrap everything up with a nice bow and a "That's All, Folks!".

Cutting to the chase, it ends with a title and sub-title drop, before Shinj wakes up in the real world, on the station platform in Anno's home town of Ube, where he is greeted by someone identified as "Large breasts, nice girl." with whom he proceeds to run out of the station and into the wide world beyond, thus sinking all the usual ships, especially the ones from the party line of "EoE was a hopeful end, with the two lovebirds on the beach together." Now, I know NGE prided itself on disregarding anime clichés, like "first girl wins" or "the boy who walks in on a girl changing suffers extreme physical violence at her hands", but taking a story that was about immanentizing the eschaton on the surface, and overcoming depression as a subtext, treating it as a harem show was always going to be misguided.

Right now, I'm feeling vindicated on a number of accounts, having proposed a Shinji/Mari end even before the second movie came out, that Mari would stand for Moyoco (to Hideaki's Shinji), having my own diagnosis of what Shinji needed validated by

Shikinami: What the brat needs is not a lover, it's a mother.

and that, of the named cast, that Kensuke would be a better fit for Asuka.

Elsewhere, the post-credits opening sequence sounds very mellow, and the provision at last of some of Asuka's Shikinami's Instrumentality-cum-back-story, a nice idea, albeit possibly a bit too much, too late.

Still, after all these years, this is the bit that makes it difficult for me to decided to actually watch any of these movies after the first. The first suffered enough from making everyone seem miserable all the time, in distinct contrast to the original episodes which it reworked, and being sufficiently off-putting that the introduction of a 5th Child and Gerry Anderson spaceships in the trailers for the next movie were net negative, even before the appearance of a new character that was wearing Asuka as a skin-suit. Maybe when it becomes feasible to deep-fake the whole thing with a different character -- maybe a green-haired girl with green outfits and Eva unit -- and different name instead.

Post spoilers bit starts here.

But enough about how I feel that the Rebuilds fall into an uncanny valley near the original, even if it is only the authors' all being older and somewhat wiser, trying to revisit characters from their youth, and on to a related subject. Following the release, NHK put out a documentary about Hideaki Anno, and the four years post-Shin Godzilla that were the making of 3.0+1.0, which proved interesting viewing -- especially the parts where Anno kept trying and failing to delegate to the team, before going on to do "if you want a job done properly..." I know that feel.

Well worth seeking out if you have any interest in the behind the scenes and the people involved. And it's not too spoilery -- so much got dropped and rewritten that it's not obvious what in the documentary ends up in the film, or what it means if it does.

Thursday, April 01, 2021

Q1 cycling

On the winter bike, from 17608.7 to 17833.4, and on the two hot days leading up to Easter, the summer bike from 3583.1 to 3610.9, plus 13.9 unmetered for a total of 266.4 miles. January was generally not nice, so I logged only one 8 mile ride that month; February was better and accounted for 107 miles, including two almost 20-milers; then despite the wet and windy mid-month, managed a lot of shorter rides, and a couple of 20 milers on the summer bike.

The flooding that marked the end of the year was slow to recede, but the roads at least were dry, even if brooks and ditches remained high -- and occasional waterlogged fields stayed that way

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.