F# Kung Fu #4: Avoid using relative paths in #r directives

Today, Vladimir Makarov faced with quite interesting ‘bug'(very unexpected behavior) of FSI. The initial goal was quite simple – count number of NuGet packages, which have “ASP.NET” in title. As a result, there was created a script that perfectly works in compiled form and crashes in FSI, here it is:

#r "../packages/nuget.core.2.8.2/lib/net40-Client/Nuget.Core.dll"
#r "System.Xml.Linq.dll"

let repository =
    NuGet.PackageRepositoryFactory.Default.CreateRepository
        "https://nuget.org/api/v2"

let aspnet = query {
    for p in repository.GetPackages() do
    where (p.Title.Contains "ASP.NET")
    count
}

When we run this in FSI we got an exception

System.ArgumentException: Incorrect instance type
Parameter name: obj
at Microsoft.FSharp.Quotations.PatternsModule.mkInstanceMethodCall(FSharpExpr obj, MethodInfo minfo, FSharpList`1 args)
at Microsoft.FSharp.Quotations.ExprShapeModule.RebuildShapeCombination(Object shape, FSharpList`1 arguments)
at Microsoft.FSharp.Primitives.Basics.List.map[T,TResult](FSharpFunc`2 mapping, FSharpList`1 x)
at Microsoft.FSharp.Linq.QueryModule.walk@933-1[a](FSharpFunc`2 f, FSharpExpr p)
at Microsoft.FSharp.Linq.QueryModule.EvalNonNestedInner(CanEliminate canElim, FSharpExpr queryProducingSequence)
at Microsoft.FSharp.Linq.QueryModule.EvalNonNestedOuter(CanEliminate canElim, FSharpExpr tm)
at Microsoft.FSharp.Linq.QueryModule.clo@1741-1.Microsoft-FSharp-Linq-ForwardDeclarations-IQueryMethods-Execute[a,b](FSharpExpr`1 )
at <StartupCode$FSI_0002>.$FSI_0002.main@() in D:\Downloads\test.fsx:line 4

When we looked more carefully to FSI output, we saw that:

nuget.core

WAT? FSI lies to us?! First message says that correct version of DLL was referenced, but then FSI loads completely wrong old version installed with ASP.NET. Why? Let’s check what #r actually does…

#r means to reference by dll-path; focusing on name. This means that FSI will use the file name first, looking in the system-wide search path and only then try to use the string after #r as a directory-relative hint

So that means, #r is not reliable way to reference assemblies. You can get into the situation when your script depends on the environment: assemblies in GAC, installed software (like version of ASP.NET) and so on. To avoid this it is better to explicitly specify an assembly search path (#I) and then reference the assembly:

#I "../packages/nuget.core.2.8.2/lib/net40-Client"
#r "Nuget.Core.dll"
#r "System.Xml.Linq.dll"

let repository =
    NuGet.PackageRepositoryFactory.Default.CreateRepository
        "https://nuget.org/api/v2"

let aspnet = query {
    for p in repository.GetPackages() do
    where (p.Title.Contains "ASP.NET")
    count
}

Thanks to Vladimir Makarov for interesting challenge and be careful in your scripts.

F# Kung Fu #3: Exceptions recap.

Define

Usually, you do not need to define custom exceptions during programming in F#. If you do scripting in F#, in the most cases you will be happy with standard .NET exception types and F# built-in helper functions. But when you create a custom library or design complex enterprise software, you will need to use power of .NET exceptions. How you can do it from F#:

// C# style exception (possible, but not recommended)
type MyCsharpException(msg, id:int) =
  inherit System.Exception(msg)
  member this.Id = id

// F# style exceptions
exception MyFsharpSimpleException
exception MyFsharpException of string * int

Note that F# has a special keyword exception for defining “handy” exceptions.

Raise(Throw)

F# provides set of functions (failwith, failwithf, invalidArg, invalidOp, nullArg) that help to raise most common exceptions. They are very convenient especially for F# scripts.

let rnd = System.Random()

let rnd = System.Random()

let raiseException() =
    match rnd.Next(8) with
    | 0 -> failwith "throws a generic System.Exception"
    | 1 -> failwithf "throws a generic Exception with formatted message (%d)" 1
    | 2 -> invalidArg "_" "throws an ArgumentException"
    | 3 -> invalidOp "throws an InvalidOperationException"
    | 4 -> nullArg "throws a NullArgumentException"
    | 5 -> raise <| MyFsharpException("throws a MyFsharpException", 5)
    | 6 -> raise <| MyCsharpException("throws a MyCsharpException", 6)
    | 7 -> assert (2>1)
    | _ -> raise <| System.Exception("Impossible case")

The assert expression is syntactic sugar for System.Diagnostics.Debug.Assert. The assertion is triggered only if the DEBUG compilation symbol is defined.

Try-With/Finally (Catch)

The last step is to catch exceptions and handle them in a proper way.

let catchException() =
    try
        raiseException()
    with
    | Failure(msg) // 'Failure' active pattern catches only Exception objects
     -> printfn "%s" msg
    | MyFsharpException(msg, id)
     -> printfn "Catch F# exceptions using pattern matching"
    | : ? System.ArgumentException as ex
     -> printfn "Invalid argument '%s'" ex.ParamName
    | : ? MyCsharpException | : ? System.InvalidOperationException
     -> printfn "You can handle multiple exceptions at a time"
    | _ as ex
     -> printfn "Log: exception '%s'" (ex.GetType().Name)
        reraise() // re-raise exception

let finaly() =
    try
        catchException()
    finally
        printfn "Now, I am ready for exceptions!"

: ?‘ is a two-symbol operator without space inside.

Note:

  • Failure active pattern catches only System.Exception objects. It is useful to handle exceptions raised by failwith & failwithf functions.
  • Exceptions defined using exception keyword could be handled automatically using pattern matching.
  • F# provides reraise function that helps to raise a current exception one more time. This function can be used only from pattern matching rules of try-with expressions.

If you want to learn more about exceptions, read an amazing The “Expressions and syntax” series from Scott Wlaschin.

F# Kung Fu #2: Custom Numeric Literals.

All of you probably know that F# has a set of predefined numerical literals that allow you to clarify meaning of numbers. For example, number 32 will be interpreted as int by default. If you add ‘L’ suffix – 32L, you will get int64 number. If you add ‘I’ suffix- 32I, you will get System.Numerics.BigInteger number.

But F# has an extensible point here: you can define custom interpretation for suffixes Q, R, Z, I, N, G. The trick is that you need to declare an F# module with a special name and define converters functions. (For example NumericLiteralZ for Z literal).

module NumericLiteralZ =
    let FromZero() = 0
    let FromOne() = 1
    let FromInt32 n =
        let rec sumDigits n acc =
            if (n=0) then acc
            else sumDigits (n/10) (acc+n%10)
        sumDigits n 0
    //let FromInt64 n = ...
    //let FromString s = ...

let x = 11111Z
//val x : int = 5
let y = 123Z
//val y : int = 6

Have fun, but be careful.

Update: Note that you cannot use constants integer literals with suffixes Q, R, Z, I, N, G in pattern matching.

F# Kung Fu #1: Mastering F# Script references.

One of the most boring parts during working with F# Script files is external references. We need to write a lot of directives with paths to the required files.

New Visual Studio 2013 can help here a bit. A new ‘Send to Interactive‘ button is available there.  Now you can avoid typing an extra command to load references for interactive execution of a small part of your application. But what to do if F# script is our goal?

If you are VS2012 user, then one nice plugin from Tao Liu is available for you “AddReferenceInFSI“. It is a really nice extension, but it is still not a silver bullet.

What do real gurus do in such a situation? Tomas Petricek has shared one typing trick. You can find it in his latest Channel 9 video “Understanding the World with F#” starting from 4:40. What?!? How did he open file picker inside the source code file, chose file that he wanted and inserted relative path to the file directly in code? Why do I always type these long and boring paths if it can be done so easy? Today the truth will come true!

Thomas was so kind and revealed the secret of this trick:

The truth is not so magical as on the face of it – just use the power of your file editor. Let’s repeat all steps once again to better remember:

  1. Find a place where you want to insert file path
  2. Press Ctrl+O that should open a standard file picker. By default VS should open a dialog in the directory where your current file is saved in.
  3. Start typing relative or absolute path to your file, BUT do not use mouse – you are able to use only auto-complete in file path edit box.
  4. When you find a file – select path to it (Ctrl+A)
  5. Copy it (Ctrl+C)
  6. Close file picker (Esc)
  7. Insert path in your script (Ctrl+V)

Do not type boring paths – do it like Tomas 😉

Update from Yan Cui: There is one useful script from Gustavo Guerra. You can load it in every FSI session and save your time.