From 4da1dc74954fafe4d21841892bafc955a37ac805 Mon Sep 17 00:00:00 2001 From: muqiuhan Date: Tue, 27 May 2025 06:06:45 +0000 Subject: deploy: 74a1d57ead41454de540829cd6de5ba1f5702bd6 --- .../10-Tips-for-Productive-FSharp-Scripting/index.html | 6 +++--- .../index.html | 18 +++++++++--------- .../index.html | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) (limited to '2024/10') diff --git a/2024/10/18/10-Tips-for-Productive-FSharp-Scripting/index.html b/2024/10/18/10-Tips-for-Productive-FSharp-Scripting/index.html index 5d3257c6..6c52f1b6 100644 --- a/2024/10/18/10-Tips-for-Productive-FSharp-Scripting/index.html +++ b/2024/10/18/10-Tips-for-Productive-FSharp-Scripting/index.html @@ -211,7 +211,7 @@

You can also execute a single-line with Alt + ‘. I rarely use this option, but this can save you time because you don’t need to select the entire line of code.

-

In case the keyboard shortcuts to send code to FSI do not work anymore (ReSharper used to over-write them in the past), you can reset them in Visual Studio, by going to Tools / Options / Environment / Keyboard. The 2 commands you need to map are EditorContextMenus.CodeWindow.ExecuteInInteractive and EditorContextMenus.CodeWindow.ExecuteLineInInteractive.

+

In case the keyboard shortcuts to send code to FSI do not work anymore (ReSharper used to over-write them in the past), you can reset them in Visual Studio, by going to Tools / Options / Environment / Keyboard. The 2 commands you need to map are EditorContextMenus.CodeWindow.ExecuteInInteractive and EditorContextMenus.CodeWindow.ExecuteLineInInteractive.

You can also use these shortcuts from a regular .fs file, which can be handy if you want to validate that a piece of code is behaving the way you want.

@@ -297,7 +297,7 @@

Tip 6: Use Paket

The Nuget package manager is useful to consume existing packages. However, by default, Nuget stores assemblies in a folder that includes the package version number. This is very impractical for a script. In our example above, if fsharp.data gets an update, our script reference will be broken once we update the Nuget package:

#r @"../packages/FSharp.Data.2.2.5/lib/net40/FSharp.Data.dll"

-

Fixing the script requires manually editing the version number in the path, which quickly becomes a pain. Paket provides a better experience, because it stores packages without the version number, in this case, under:

+

Fixing the script requires manually editing the version number in the path, which quickly becomes a pain. Paket provides a better experience, because it stores packages without the version number, in this case, under:

#r @"../packages/FSharp.Data/lib/net40/FSharp.Data.dll"

Your scripts will now gracefully handle version number changes.

If you end up consuming numerous packages, you can make your life even easier, by referencing paths where assemblies might be searched for, using #I:

@@ -335,7 +335,7 @@

Tip 10: Bonus Material

Did you know that you could…

-

As you see, what seemed to be simple task at the beginning can quickly blow up out of proportion. As the number of arguments grows, the more nasty our wiring code eventually becomes. Quite common pattern is to hide all of that nastiness under the carpet a.k.a. composition root. However this doesn’t have to be the case.

+

As you see, what seemed to be simple task at the beginning can quickly blow up out of proportion. As the number of arguments grows, the more nasty our wiring code eventually becomes. Quite common pattern is to hide all of that nastiness under the carpet a.k.a. composition root. However this doesn’t have to be the case.

Below we’ll cover another approach for dealing with dependencies - inspired by Scala ZIO library - using incremental steps, from first principles to monadic bindings.

Managing dependencies beyond partial application

Let’s start from how our code from above will eventually look like at the end of this step:

1
2
3
4
5
6
7
8
9
10
11
let changePass env = fun req -> task {
let! user = Db.fetchUser env req.UserId
if user.Hash = bcrypt user.Salt req.OldPass then
let salt = Random.bytes env 32
do! Db.updateUser env { user with Salt = salt; Hash = bcrypt salt req.NewPass }
Log.info env "Changed password for user %i" user.Id
return Ok ()
else
Log.error env "Password change unauthorized: user %i" user.Id
return Error "Old password is invalid"
}
@@ -255,8 +255,8 @@
  • It doesn’t impose specific restrictions on libraries and frameworks.
  • Now we could as well stop here - IMHO this approach is already good and useful for most cases. We can also try to push it further. As you’ve seen, our code now requires quite a lot of env passing around. Could we do something about this? It turns out that yes, we could.

    -

    Reader monad

    Before we continue: what we’re going to cover now is less useful in terms of current state of F# ecosystem for the reasons I’ll mention later.

    -

    The pattern we’ll use here is known as a Reader Monad. While it’s useful in certain situations, it’s not widely used - IMO it’s fault lies in the name itself, which somehow managed to sound both borderline meaningless and scary in ears of many developers.

    +

    Reader monad

    Before we continue: what we’re going to cover now is less useful in terms of current state of F# ecosystem for the reasons I’ll mention later.

    +

    The pattern we’ll use here is known as a Reader Monad. While it’s useful in certain situations, it’s not widely used - IMO it’s fault lies in the name itself, which somehow managed to sound both borderline meaningless and scary in ears of many developers.

    The rest of this blog post will be introduction to this style in F#, however focused solely around problem of dependency management - we’ll ignore other aspects of monads.

    We’ll going to reuse our environment type from above, but now encode it directly into another type we’ll call Effect. Since I’ve mentioned that our pattern has M-word in it, you can safely assume that our handler’s logic will be defined as a lazy sequence of steps to be executed (sounds almost like async/await). In F# we’ll sugar them by using custom computation expression (I’m going to call it effect { ... }) returning our effect type, which we’ll define as:

    1
    [<Struct>] type Effect<'env, 'out> = Effect of ('env -> 'out)
    -- cgit v1.2.3