Víctor Escobar
Víctor Escobar

Reputation: 27

F# shortcut to access a property of an optional record

I am looking at the F# syntax and I wonder if there is any operator to access to a struct field when the struct is optional and assign assign None to the expression the original struct was optional. For example a way to shorten the first line into the second:

type Post = { picture: string }
let post:Post option = None
let maybeImage = if post <> None then Some post.Value.picture else None
let maybeImage = Option.bind (fun p -> p.picture ) post
// the operator I am looking should be a shortcut for the previous operations:
let maybeImage = post?picture

Upvotes: 0

Views: 513

Answers (2)

kaefer
kaefer

Reputation: 5751

Record fields are usually accessed by compiler-generated properties, where the field name is the same as the property name. Alternatively, you might use Record Patterns, see spec 7.10, to match the field name and retrieve the field's value.

Also, in monad terms, the operation you are after is map, not bind, as Bent's answer points out.

type Post = { picture: string }
let post = None
let maybeImage = Option.map (fun { picture = p } -> p)
// val maybeImage : (Post option -> string option)
maybeImage post
// val it : string option = None

While bind has a canonical operator >>=, map is often considered a functor. As such, you can replace it with functional composition >> map, here >> Option.map.

Generally, it is not recommended, but by using Reflection it's actually quite possible and in some situations even very useful to define a custom operator that combines the record accessor and the map operation. It's a generic operator that takes the field name as a string and would work on any record, but must be given enough type information being able to figure out the result type.

open Microsoft.FSharp.Reflection
let (?) (o : 'T option) name : 'R option =
    typeof<'T>.GetProperty name |> function
    | null -> None
    | pi -> Option.map (pi.GetValue >> unbox<'R>) o 
// val ( ? ) : o:'T option -> name:string -> 'R option
let pictureName : string option = post?picture
// val pictureName : string option = None

Upvotes: 2

Bent Tranberg
Bent Tranberg

Reputation: 3470

type Post = { picture: string }

let getPostPicture (post: Post option) =
    Option.map (fun p -> p.picture) post

let post = Some { picture = "the picture" }

let picture = getPostPicture post

match picture with
| Some picture -> printfn "%s" picture
| None -> printfn "No picture."

You should not use Value, and not test for option values the way you did. Instead,

let maybeImage = match post with Some post -> Some post.picture | None -> None

Which can be shortened to

let maybeImage = Option.map (fun p -> p.picture) post

Upvotes: 0

Related Questions