Is it possible to unwrap a single-case DU without creating a function?

I have this simple code which works:

type Name = Name of string

let myName = Name "Garry"

let isAllLowerCase (text : string) = if text = text.ToLower() then true else false

let (Name name) = myName

let result = name |> isAllLowerCase

Is it possible to unwrap the name from the DU without using a function, such as:

type Name = Name of string

let myName = Name "Garry"

let isAllLowerCase (text : string) = if text = text.ToLower() then true else false

let result = (Name myName) |> isAllLowerCase

…which I know doesn’t compile, but hopefully you get the picture.

I know I can do this:

type Name = Name of string

let myName = Name "Garry"

let isAllLowerCase (text : string) = if text = text.ToLower() then true else false

let result = myName |> (fun (Name name) -> name) |> isAllLowerCase

…but that seems worse then having the named function.

And I know I can do this:

type Name = Name of string with
    member this.Value =
        let (Name name) = this
        name

let myName = Name "Garry"

let isAllLowerCase (text : string) = if text = text.ToLower() then true else false

let result = myName.Value |> isAllLowerCase

…but that seems a little ‘clunky’.

And I know I can change the Name definition to:

type Name = Name of string with
    member this.Value =
        let (Name name) = this in name

…but that’s not much better.

Is there a better way?

I’m not aware of a much better way. What I’ve done in the past is indeed to use members, but with an interface:

type Wrapped<'x> = abstract member Value : 'x

let unwrap<'x> (wrapped:#Wrapped<'x>) = wrapped.Value

then your code would be

type Name = Name of string with 
  interface Wrapped<string> with
    member this.Value = let (Name name) = this in name

...
let result = unwrap name |> isAllLowerCase
// alternatively, if you want more of an annotation
let result = unwrap<Name> name |> isAllLowerCase

still a little clunky because of working with interfaces & members, but what I like is that the unwrap function can still work with function composition, pipelines, partial application and all that without making a lambda, so all the clunkiness is in having to take those 2 lines in the type definition of your Name type to add the interface.

1 Like

It depends a bit on your situation, but there are a few options. If you “own” the consuming function, you can simply do the unwrapping in the function declaration:

let isAllLowerCase (Name text) = 
  text |> String.forall Char.IsLower

However, for a more general solution, I would recommend defining an explicit conversion operator:

type Name = Name of string
  with static member op_Explicit(Name name) = name

// ... elsewhere ...

myName |> string |> isAllLowerCase

Further, if you don’t plan to attach any extra business functionality to the “wrapped” string, you may want to consider using something other than a single-case discriminated union. I usually use something like UMX (GitHub - fsprojects/FSharp.UMX: F# units of measure for primitive non-numeric types) when I don’t want a custom type but do want to distinguish between strings.

3 Likes

Thanks for the information ntwilson.

I’ve not had the chance to get into using interfaces in F# yet but that works very nicely.

P.S. There’s a superfluous ‘#’ in your code.

Thanks for the information pblasucci.

Explicit conversion operators are something that I will probably need to look up later but they look interesting.

I’ve also bookmarked Fsharp.UMX for further reading at some point.

There’s so much stuff to learn that it’s difficult to know what to learn next.