raphinou 19 days ago

FSharpPacker works on scripts (with extension .fsx) written with F# and usually run with `dotnet fsi`. For the F# advent calendar [1] I recently blogged about how those scripts are a viable starting point for developing an application in F#. Actually, it's even possible to easily maintain a scripted version and a compiled version of the same F# app, with basically the same code. For those interested, it's at https://www.asfaload.com/blog/fsharp-fsx-starting-point/

1: https://sergeytihon.com/2024/10/26/f-advent-calendar-in-engl...

  • nickpeterson 19 days ago

    Any reason to prefer this or fflat (bflat)?

    • neonsunset 19 days ago

      Fflat does not work on macOS and does not support .NET 9. Unfortunately, I had issues with making it work on Windows and Linux too.

      This one works like a charm.

neonsunset 19 days ago

A few of notes regarding AOT compilation if you would like to have the best experience:

1. print* functions with "%A" format do not work because "structured output" uses reflection patterns that the linker cannot analyze. You can easily replace them with string interpolation that F# now has. I carry around these bindings:

  open System

  let println s = Console.WriteLine s
  let print s = Console.Write(x: string)
and use them as

  println $"Hello, {thing}"
2. AOT compilation is not a scripting environment so accessing fsi args does not work, here's the snippet that I use for it:

  open System

  // Detect fsi, if not - slice appropriately
  // You do not need this if you know that you will be
  // only compiling the script or only running it with fsi
  // just use the offset of 2 or 1 respectively :)
  let args =
    let args = Environment.GetCommandLineArgs()
    if args[0].Contains "fsi.dll" then args[2..] else args[1..]

  // Use args in some way
  args |> Array.iter println
3. For quickly publishing script files you may want to define a (fish) shell function like this:

    function fspk
        set name (string replace '.fsx' '' $argv[1])
        fspack $argv[1] -f net9.0 --aot -o . \
            /p:InvariantGlobalization=true \
            /p:OptimizationPreference=Size &&
        rm -rf ./$name.dbg ./$name.dSYM
    end
This will reduce the binary size and will give you just a single executable for every `fspk my_script.fsx`. The binaries do start at ~4MB but beyond that - they scale efficiently with dependencies.

4. Except what is explicitly noted in repo, other scripting features work like referencing nuget packages or importing files. I did not expect to like this as much as I did, it's incredibly productive and you can just throw a few files together while having fully statically typed scripting environment and built-in package manager.

You can also get Ionide for VS Code for language server, autocomplete and debugging support.

  • fire_lake 19 days ago

    Given these limitations, isn’t it easier to create a small dotnet project?

    • neonsunset 19 days ago

      It might be! I'd expect that at some point this would be a natural progression.

      However, very often you don't need that - you can just compile individual script files which merge together code, package and file references. No need to maintain a separate folder with a separate .fsproj - simply #open some_utils.fsx, #r "nuget: FsHttp" or #r "SomePackage.dll" and 'fspk my_script.fsx'.

      Initially, I did not know this existed, but it turned out a friend of mine wrote this tool and I have been using it ever since.

Multicomp 19 days ago

Nice work shipping this! I enjoy and prefer f# to c# but never gave scripts much care because I could never do this, fsi and the repl was not enough. Now? Hmmm I have options !

Alifatisk 19 days ago

I am a huge fan of keeping things as standalone executable, good work!