Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Define default value for discriminated union

I would like to define a default value for a discriminated union, like this:

open System.Linq

type Result =
| Ok
| Error

let results : seq<Result> = [] |> Seq.ofList

let firstResult = results.FirstOrDefault()
// I want firstResult to be Error, currently it is null.

option<'a> works in this way (firstResult would be None), so it should be possible. Thanks for your help.

Edit: I'm using SQLDataProvider and would like to write code like

let userEmail =
    query {
        for user in dbContext.Public.Users do
        where (user.Id = 42)
        select (Ok user.Email)
        headOrDefault
        // should result in Error
        // when no user with Id=42 exists 
    }

My actual result type looks like this:

type Result<'a> =
| Ok of 'a
| Failure of string // Expected, e. g. trying to log in with a wrong password
| Error // Unexpected

Returning an option, the caller would not be able to differentiate between failures and errors.

like image 852
René Ederer Avatar asked Dec 10 '22 06:12

René Ederer


2 Answers

In general F# avoids the concept of defaults, instead making everything as explicit as possible. It's more idiomatic to return an option and for the caller to decide what to default to for a particular use case.

The FirstOrDefault method can only return .NET's default value for any type. So for any class it would return null, and for a number it would return zero.

I would recommend this approach instead assuming your desired default is Ok:

results |> Seq.tryHead |> Option.defaultValue Ok
like image 164
TheQuickBrownFox Avatar answered Dec 14 '22 11:12

TheQuickBrownFox


You might be able to do this using the UseNullAsTrueValue compilation parameter. This tells the compiler to use null as an internal representation of one of the parameter-less cases. It is a bit of a hack (because this is meant mostly for performance optimization and here we are misusing it somewhat, but it might work).

I don't have SQL database setup to try that with the SQL provider, but the following works in memory and I think it should work with SQL too:

[<CompilationRepresentation(CompilationRepresentationFlags.UseNullAsTrueValue)>]
type Result<'a> =
  | Ok of 'a
  | Failure of string
  | Error

let userEmail =
  query {
    for a in [1] do
    where (a = 2)
    select (Ok a)
    headOrDefault }

If you run this, F# interactive prints that userEmail = null, but that's fine and the following is true:

userEmail = Error
like image 33
Tomas Petricek Avatar answered Dec 14 '22 09:12

Tomas Petricek