5

I'm playing around with StructuredFormatDisplay and I assumed I could use multiple properties for the Value, but it seems that is not the case. This question (and accepted answer) talk about customizing in general, but the examples given only use a single property. MSDN is not helpful when it comes to usage of this attribute.

Here's my example:

[<StructuredFormatDisplay("My name is {First} {Last}")>]
type Person = {First:string; Last:string}

If I then try this:

let johnDoe = {First="John"; Last="Doe"}

I end up with this error:

<StructuredFormatDisplay exception: Method 'FSI_0038+Person.First} {Last' not found.>

The error seems to hint at it only capturing the first property mentioned in my Value but it's hard for me to say that with any confidence.

I have figured out I can work around this by declaring my type like this:

[<StructuredFormatDisplay("My name is {Combined}")>]
type Person = {First:string; Last:string} with
    member this.Combined = this.First + " " + this.Last

But I was wondering if anyone could explain why I can't use more than one property, or if you can, what syntax I'm missing.

I did some digging in the source and found this comment:

In this version of F# the only valid values are of the form PreText {PropertyName} PostText

But I can't find where that limitation is actually implemented, so perhaps someone more familiar with the code base could simply point me to where this limitation is implemented and I'd admit defeat.

Community
  • 1
  • 1
Sven Grosen
  • 5,616
  • 3
  • 30
  • 52

2 Answers2

5

The relevant code from the F# repository is in the file sformat.fs, around line 868. Omitting lots of details and some error handling, it looks something like this:

let p1 = txt.IndexOf ("{", StringComparison.Ordinal) 
  let p2 = txt.LastIndexOf ("}", StringComparison.Ordinal) 
  if p1 < 0 || p2 < 0 || p1+1 >= p2 then  
      None  
  else 
    let preText = if p1 <= 0 then "" else txt.[0..p1-1] 
    let postText = if p2+1 >= txt.Length then "" else txt.[p2+1..] 
    let prop = txt.[p1+1..p2-1] 
    match catchExn (fun () -> getProperty x prop) with 
    | Choice2Of2 e -> 
        Some (wordL ("<StructuredFormatDisplay exception: " + e.Message + ">")) 
    | Choice1Of2 alternativeObj -> 
        let alternativeObjL =  
          match alternativeObj with  
          | :? string as s -> sepL s 
          | _ -> sameObjL (depthLim-1) Precedence.BracketIfTuple alternativeObj 
        countNodes 0 // 0 means we do not count the preText and postText  
        Some (leftL preText ^^ alternativeObjL ^^ rightL postText) 

So, you can easily see that this looks for the first { and the last }, and then picks the text between them. So for foo {A} {B} bar, it extracts the text A} {B.

This does sound like a silly limitation and also one that would not be that hard to improve. So, feel free to open an issue on the F# GitHub page and consider sending a pull request!

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • A question, would it be prudent to do any PR against the fsharp4 branch? Or should I do it against master? I assume master is the 3.1.x code base at this point? – Sven Grosen Feb 13 '15 at 18:33
  • I'm not quite sure - the best thing is probably to ask on GitHub! Also, it's a good idea to open an issue to discuss the feature first. See: https://github.com/Microsoft/visualfsharp/blob/fsharp4/CONTRIBUTING.md – Tomas Petricek Feb 13 '15 at 21:37
  • Very prudent suggestions, I've followed the guidelines and submitted a request to User Voice: https://fslang.uservoice.com/forums/245727-f-language/suggestions/7099985-support-multiple-properties-in-structuredformatdis – Sven Grosen Feb 16 '15 at 02:39
3

Just to put a bow on this, I did submit a PR to add this capability and yesterday it was accepted and pulled into the 4.0 branch.

So starting with F# 4.0, you'll be able to use multiple properties in a StructuredFormatDisplay attribute, with the only downside that all curly braces you wish to use in the message will now need to be escaped by a leading \ (e.g. "I love \{ braces").

I rewrote the offending method to support recursion and switched to using a regular expression to detect property references. It seems to work pretty well, although it isn't the prettiest code I've ever written.

Sven Grosen
  • 5,616
  • 3
  • 30
  • 52