Journal
Sections
Reading settings
No voice for this language in this browser.
More voices
Code and English terms
Off: the page’s voice reads everything, with no pause between two voices.
No voice for this language in this browser.
More voices
More voices: Microsoft Edge has natural online voices, and Windows (Settings › Time & language › Speech), macOS and Android install others.
Progress
Section titled “Progress”- Course code: scripts, F# Interactive sessions, rejected snippets, exercise solutions and three small projects, compared with their expected output by
check.sh - CI on Linux, Windows and macOS: pushed, green on 2026-09-16, red since 2026-09-20 on three expected files (see 2026-09-22)
- Lesson 1: scripts, F# Interactive and projects
- Lesson 2: values, functions and type inference
- Lesson 3: tuples, records, unions and options
- Lesson 4: pattern matching
- Lesson 5: lists, arrays and sequences
- Lesson 6: modules, namespaces and project organization
- Lesson 7: errors with
Result - Lesson 8: computation expressions applied to a DSL parser
- Lesson 14: CSV and JSON type providers with FSharp.Data 8.2.0
The .NET 10 SDK (10.0.112), F# 10 and dotnet fsi 14.0.112.0; TARS at 87464ce and GuitarAlchemist/ga at 32f143c for the practical cases. The first row is the course’s own: it is why the CI has been red since 2026-09-20. Nothing here has been reported upstream. The existing Experiments table below is unchanged.
| Expected | What happens | Where | Measure | Status |
|---|---|---|---|---|
| The expected outputs committed with lessons 5-8 are what the examples print | Three of them end with one blank line more than the examples print, so the CI fails on the three OSes | code/fsharp/expected/l05_collections.txt, l06_modules.txt, l07_result.txt, commit 69a02c4 |
FAIL l05_collections, FAIL l06_modules, FAIL l07_result on each OS in run 35528402823; the files end in exit 0 followed by two newlines |
Reproduced; diagnosed, not fixed yet 2026-09-22 |
A script runs top to bottom, so a printfn above a later error prints |
The whole script is type-checked first, so nothing prints | l01_format_type.fsx, dotnet fsi 14.0.112.0, F# 10.0 |
The printfn above the error prints nothing |
By design 2026-09-15 |
| A compiler reports every independent error of a file in one pass | F# Interactive reports one error per run | F# Interactive 14.0.112.0, lesson 2’s exercise 3 | Three independent mistakes take four runs to clear | Reproduced; whether dotnet build batches them is still to verify 2026-09-15 |
dotnet fsi and dotnet build report a diagnostic the same way |
fsi prints the bare file name; dotnet build prints the full path, appends [project.fsproj], prints each error twice, and leaves trailing spaces in FS0001’s text |
SDK 10.0.112 | The same error, two shapes | Reproduced; check.sh strips the path, the suffix, the duplicates and the trailing spaces 2026-09-15 |
| An equality used as a statement warns wherever it appears | strings = 7 raises FS0020 inside a function and nothing at the top level of a script |
dotnet fsi, SDK 10.0.112 |
FS0020 inside a function; no warning at the top level | Reproduced, not reported 2026-09-15 |
| A tab is whitespace, so tab-indented code compiles | The compiler rejects it | F# Interactive, SDK 10.0.112 | error FS1161: TABs are not allowed in F# code unless the #indent "off" option is used |
By design 2026-09-15 |
A C# switch with one arm per sealed subclass of an abstract record is exhaustive |
Roslyn still warns; the program compiles and runs | hierarchy.cs(3,42), SDK 10.0.112 |
warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). |
By design: C# has no closed hierarchies, which is what the comparison shows 2026-09-15 |
A C# switch over the three named values of an enum is exhaustive |
Roslyn warns about an unnamed value outside the declared set | enumswitch.cs(3,41), SDK 10.0.112 |
warning CS8524: … For example, the pattern '(Accidental)3' is not covered. |
By design 2026-09-15 |
.fs files in the active source tree are compiled by some project |
Nine .fs files in v2/src are in no .fsproj, and one project is missing from the solution, so the CI never builds it |
GuitarAlchemist/tars at 87464ce, v2/src; Tars.LSP.fsproj absent from v2/Tars.sln |
Fibonacci.fs, LintRunner.fs, OllamaClient.fs, ToolFactory.fs and five more; two of them 95 and 3 bytes long |
Reproduced; used in lesson 1 2026-09-15 |
The README’s “License: MIT” badge links to a LICENSE file |
There is no LICENSE file at this commit |
GuitarAlchemist/tars at 87464ce, the README’s link to ./LICENSE |
A broken link | Reproduced, not reported 2026-09-15 |
A bare Error in F# means Result.Error |
Two unions declared without [<RequireQualifiedAccess>] shadow it, so every later file must qualify it |
Domain.fs, lines 62-78 |
342 occurrences of Result.Error in v2/src; ArcTypes.fs goes as far as FSharp.Core.Result.Error |
Reproduced in compile_fail/l03_case_shadowing.fsx and shown in lesson 3; a fix upstream would be a breaking change 2026-09-15 |
| A text normalizer keeps the meaningful characters of its input | Its [^a-z0-9\s] pattern strips # and every accented letter, and no test covers either |
TextNormalizer.fs, lines 51-58 |
"learn F#" gives the keywords learn and f; café gives caf |
Reproduced; shown in lesson 2 2026-09-15 |
| A type documented as thread-safe reads its mutable state under its lock | Remaining takes the lock; the Consumed property returns the mutable consumed field without it |
Budget.fs, lines 133-150, BudgetGovernor |
Read in the code | Not reproduced: this row is a code reading. Held for lesson 16 2026-09-15 |
A parser that returns Ok consumed its whole input |
pChord is not followed by eof, so it stops at the first character it does not know and returns Ok for the prefix |
GuitarAlchemist/ga at 32f143c, ChordParser.parse, inherited by ChordDslService and the chatbot’s ResponseValidator |
C7sus4 parses as C7; Am(maj7) as Am |
Reproduced in examples/l04_ga_parse.fsx and shown in lesson 4 2026-09-15. Fixed upstream by #682 and #689, merged 2026-09-23: on 27a1257, C7sus4 parses with the components 7 and sus4, and Am(maj7) is an error at column 3 2026-09-24 |
| Normal form does not depend on transposition | List.minBy fst keeps the first rotation of equal span instead of comparing the inner intervals, so the answer moves with the transposition |
GuitarAlchemist/ga at 32f143c, HarmonicTransformationService.GetNormalForm |
{0, 4, 7, 8} gives [0; 4; 7; 8]; the same set transposed by 8 gives [0; 3; 4; 8] |
Reproduced in examples/l02_ga_transformations.fsx; no caller found, and no test covers it 2026-09-15. HarmonicTransformationService.fs is unchanged at 27a1257 2026-09-24. Fixed by #734, merged 2026-09-24, with Rahn’s tie-break 2026-09-24 |
Experiments
Section titled “Experiments”| Question | Hypothesis | Measured result | Verdict | Evidence |
|---|---|---|---|---|
| Can a minimal computation expression make a DSL grammar readable without hiding failures? | Bind and Return are enough for the sequential note grammar. |
Four cases passed, including rejection of an invalid letter and of trailing input. | Confirmed | 2026-09-20 entry, l08_parser_ce.fsx |
| Does FSharp.Data infer useful CSV and nested JSON members from local samples on .NET 10? | FSharp.Data 8.2.0 will expose typed columns and nested properties in F# 10. | Two CSV rows and one nested JSON document compiled and printed the expected typed values. | Confirmed | 2026-09-20 entry, l14_type_providers.fsx |
2026-09-15 — The SDK and F# Interactive
Section titled “2026-09-15 — The SDK and F# Interactive”- My machine has the .NET SDKs 10.0.112 and 11.0.100-preview.3.26207.106. The course’s
global.jsonpins10.0.100withrollForward: latestFeature, which selects 10.0.112: F# Interactive 14.0.112.0 for F# 10.0, and FSharp.Core with the assembly version 10.0.0.0. - The examples are
.fsxscripts run withdotnet fsi, rather than projects: a script starts in about 2 seconds on my machine, needs no build, and can#loadGA’s files and reference FParsec with#r "nuget: …". Only lesson 1 builds projects, three small ones, each in about 2 seconds. - A script is type-checked completely before it runs: in
l01_format_type.fsx, theprintfnabove the error prints nothing. - F# Interactive reported one error per run in the scripts of this lot: lesson 2’s exercise 3 has three independent mistakes and needs four runs. I haven’t checked whether
dotnet buildreports more of them at once in a project. - Compiler messages of a script show the file name without its folder, even when the script is run from another folder.
dotnet buildshows the full path, appends[project.fsproj], and prints each error twice (once as it happens, once in the summary):check.shkeeps theerror FS/warning FSlines, removes the path and the suffix, and removes the duplicates. Multi-line messages such as FS0001’s “expected to have type” end some lines with spaces, whichcheck.shalso removes. - An unhandled exception in a script prints the stack trace with full paths, then
Stopped due to error, anddotnet fsiexits with code 1.check.shdrops theat …lines. - F# Interactive sessions are checked by typing a file into
dotnet fsi --nologo: the output contains the>prompts and the answers, without the typed lines. The lessons show the input next to the answers, as a person sees them. - An equality used as a statement (
strings = 7) gets warning FS0020 inside a function, but no warning at the top level of a script: I tried it outside the course code, and lesson 2 only shows the function. - Indentation with a tab is rejected:
error FS1161: TABs are not allowed in F# code unless the #indent "off" option is used(tried outside the course code). - A C# check behind lesson 4, compiled on the side with SDK 10.0.112: see 2026-09-15 — C# comparisons.
2026-09-15 — CI
Section titled “2026-09-15 — CI”- The workflow
.github/workflows/fsharp-examples.ymlrunscheck.shonubuntu-latest,windows-latestandmacos-latest, like the other courses. It isn’t pushed: the token used to push this repository can’t create workflow files (it lacks theworkflowscope). Until it is, the outputs have only been checked on Windows, and everything specific to Linux and macOS is marked to verify. - Two outputs contain non-ASCII characters:
EbΔ9and FParsec’s quotes‘…’inl04_ga_parse. They are correct in Git Bash on my machine; the Windows runner may print them in another encoding (to verify).
2026-09-15 — Dogfooding: TARS
Section titled “2026-09-15 — Dogfooding: TARS”TARS at commit 87464ce.
- What is live. The README says that all active development is in
v2/. The CI workflowdotnet.ymlrestores, builds and testsv2only, andv2/Tars.slncontains 17 projects underv2/srcand one test project. The top level of the repository keeps dozens of older folders (TarsEngine.FSharp.*,backup_fake_elimination_*…) that no workflow builds: the lessons don’t cite them. - Nine
.fsfiles that nothing compiles.v2/srcitself containsFibonacci.fs,FSharp.ReverseList.fs,LintRunner.fs,Main.fs,OllamaClient.fs,PalindromeChecker.fs,Program.fs,StaticAnalysisRunner.fsandToolFactory.fs. No.fsprojlists them (projects with the same file names list their own copies, such asTars.Llm/OllamaClient.fs), and two of them are almost empty (95 and 3 bytes).v2/src/Tars.LSPhas a project,Tars.LSP.fsproj, thatTars.slndoesn’t include, so CI doesn’t build it either. Lesson 1 uses this to show that an F# project compiles only the files it lists. - No license file. The README shows a “License: MIT” badge that links to
./LICENSE, but the repository has noLICENSEfile at this commit, so the link is broken. The course links and quotes TARS’s code but doesn’t copy its files;examples/l02_tars_normalize.fsxretypes one eight-line function to run it. - Union cases that hide
Result.Domain.fsdeclaresPartialFailure.ErrorandExecutionOutcome.Failurewithout[<RequireQualifiedAccess>]. Every file compiled after it that meansResult.Errormust qualify it:v2/srchas 342 occurrences ofResult.Error, andArcTypes.fswritesFSharp.Core.Result.Error. Files compiled before it, such asBudget.fs, write a plainError. Adding the attribute would be a breaking change inside TARS (everyWarning,Error,Success,Failureof these unions would need its type name). Reproduced incompile_fail/l03_case_shadowing.fsx; shown in lesson 3. TextNormalizer.normalizekeeps ASCII only. The regular expression[^a-z0-9\s]ofTextNormalizer.fsremoves#("learn F#"gives the keywordslearnandf) and every accented letter (cafégivescaf). Its tests use English sentences only, and I found no caller inv2/srcbesides its tests (grepat this commit). Shown in lesson 2.- For lesson 16:
BudgetGovernorinBudget.fscalls itself thread-safe and reads itsmutable consumedunder a lock inRemaining, but itsConsumedproperty returns the field without the lock.
2026-09-15 — Dogfooding: Guitar Alchemist
Section titled “2026-09-15 — Dogfooding: Guitar Alchemist”GA at commit 32f143c. Six files are copied in code/fsharp/external/ga, unchanged except for their line endings.
ChordParser.parsedrops the end of the input.pChordisn’t followed byeof, so the parser stops at the first character it doesn’t recognize and returnsOkwith what it read:C7sus4is normalized toC7, andAm(maj7)toAm(examples/l04_ga_parse.fsx, lesson 4).ChordDslService.ParseandNormalizeinherit it, and the C#ResponseValidatorof the chatbot, which only checks whetherParsesucceeded, accepts any word that starts like a chord and matches its regular expression. A possible fix isrun (pChord .>> eof) chordStr, withC7sus4andAm(maj7)added toChordDslTests.cs; a realsus4after an extension would then need a parser rule too.HarmonicTransformationService.GetNormalFormdepends on the transposition. For the set{0, 4, 7, 8}it returns[0; 4; 7; 8], and for the same set transposed by 8 semitones it returns[0; 3; 4; 8]: two rotations have the same span, andList.minBy fstkeeps the first one instead of comparing the inner intervals as the normal order does (examples/l02_ga_transformations.fsx, lesson 2). GitHub’s code search found no caller ofGetNormalFormin GA on 2026-09-15, and GA’sHarmonicTransformationTests.csdoesn’t test it. In the same file,InvertandApplyNegativeHarmonycompute the same formula (2 * axis - pcandsumAxis - pc), and the comment ofApplyNegativeHarmonystill asks “axis = 3.5 semitones?”.Scripts/BinObj.fsxselects too much and runs nothing.isObjOrBinFoldertests whether a full path ends withbinorobj, ignoring case, so folders namedcabinorRobinare selected, and their subfolders aren’t visited. The script defines its functions and never calls them (examples/l04_ga_binobj.fsx, lesson 4).Scripts/ModesConfig.fsxdoesn’t run. Run as it is withdotnet fsifrom theScriptsfolder, it stops on line 21 with twoerror FS3373: Invalid interpolated string(at columns 57 and 59): a string literal", "inside a$"…"interpolation. With a triple-quoted string, the same line getsFS0039: The value, constructor, namespace or type 'Join' is not defined, since the script has noopen System(reproduced in lesson 1’s exercise 2). Beyond that,#r "nuget: GA.Business.Config, 1.0.0"names a package that nuget.org doesn’t have (its package index answeredBlobNotFoundon 2026-09-15), and#Iadds a folder to search for assemblies, not a package source. A differentModesConfig.fsxexists at the root of the repository.- Two trees for one chord.
ChordAstcan writeBbmaj7asQuality = NonewithExtension "maj7"(what the parser produces) or asQuality = Some MajorwithExtension "7"; both render the same text and compare as different (lesson 3’s exercise 3).
2026-09-15 — C# comparisons
Section titled “2026-09-15 — C# comparisons”Lesson 4 compares F#’s exhaustiveness check with C#’s. The two C# files below were compiled on the side with dotnet run and SDK 10.0.112, not in the course’s CI:
-
A record hierarchy
abstract record Fingeringwith threesealedsubclasses, and aswitchexpression with one arm per subclass:hierarchy.cs(3,42): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '_' is not covered. -
An
enum Accidental { Natural, Sharp, Flat }and aswitchexpression with one arm per named value:enumswitch.cs(3,41): warning CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. For example, the pattern '(Accidental)3' is not covered.
Both programs still ran and printed their result.
2026-09-20 — Collections, modules and Result
Section titled “2026-09-20 — Collections, modules and Result”- Added lesson 5 with executable comparisons of eager
ListandArraytransformations against a lazySeqpipeline. - Added lesson 6 with nested modules, explicit visibility and a small organization example that keeps the public surface narrow.
- Added lesson 7 with a
Resultvalidation pipeline, explicit error composition and the boundary between expected failures and exceptions. dotnet fsireproduced the stored outputs forl05_collections.fsx,l06_modules.fsxandl07_result.fsxon .NET SDK 10.0.112.
2026-09-20 — Computation expressions and type providers
Section titled “2026-09-20 — Computation expressions and type providers”- Added lesson 8 around a real
Parser<'T>builder. The harness executes success, invalid-token and trailing-input cases; the last one keeps the end-of-input invariant explicit. - Added lesson 14 with FSharp.Data 8.2.0, pinned from NuGet. The CSV and JSON samples are local strings, so type checking does not depend on a remote schema.
dotnet fsiproduced the outputs stored inexpected/l08_parser_ce.txtandexpected/l14_type_providers.txton .NET SDK 10.0.112.
2026-09-22 — CI on three OSes
Section titled “2026-09-22 — CI on three OSes”- The 2026-09-15 entry says the workflow isn’t pushed. It was pushed afterwards: run 35097250880, on 2026-09-16, passed on
ubuntu-latest,windows-latestandmacos-latest. - Run 35528402823, on 2026-09-20, the first after lessons 5 to 8 and 14, failed on all three. Most of its log is
oklines, which made it look like a failure with no failing check. It has one:FAIL l05_collections,FAIL l06_modulesandFAIL l07_result, once per OS, each followed by adiffthat removes one empty line at the end. The three expected files end withexit 0and two newlines; the other expected files end with one.check.shsetsstatus=1on a mismatch and carries on, so every later check still printsokand the script exits with 1 at the end. The diagnosis is auggie’s, checked against the log and the files; how the extra lines got into the commit is not known. - The fix is to remove one trailing newline from each of the three files. It is not applied yet.
- The same run settles two to verify notes:
l04_ga_parse, withEbΔ9and FParsec’s‘…’, printsokon the Windows runner, and every lesson prints the same output on Linux, macOS and Windows apart from those three files.
2026-09-24 — Upstream fixes checked again
Section titled “2026-09-24 — Upstream fixes checked again”ChordParser.parsenow requires the whole symbol (#682) and reads7sus4,Maj7andomit(#689), both merged 2026-09-23. Re-run on GA27a1257by music-theory-ga’srecheck/probe.cs:C7sus4→OkwithComponents = [Extension "7"; Extension "sus4"], andAm(maj7)→Error in Ln: 1 Col: 3. Lesson 4 stays pinned to32f143cand still shows the prefix being accepted.GetNormalFormis unchanged: its file is identical at32f143cand27a1257.- #734, merged 2026-09-24, fixes
GetNormalForm: ties of span are broken as Rahn does, by the intervals from the first element to the second-to-last, then the third-to-last, and so on.{0, 4, 7, 8}now gives[0; 3; 4; 8], the same as its transposition by 8. Its test runs every one of the 4,096 sets under T1 to T11; on the old code, 5,920 of those 45,045 transpositions changed the answer. The doc comment no longer calls the result a Forte prime form: it is the normal order transposed to zero, without the inversion. A last test pins{0, 1, 5, 6, 8}, a set where Rahn and Forte disagree.
To verify
Section titled “To verify”- The build output folders of lesson 1 on Linux and macOS (
Helloexecutable, resource folders). - Alt+Enter to send code to F# Interactive in VS Code with Ionide, Rider and Visual Studio.
- Whether
dotnet buildreports several independent errors of one file in a single run, where F# Interactive reported one.