Integrating the Polish surname guessing into our WebSharper website

After spending a few posts on F# itself, It is high time we went back to our old friend WebSharper. I decided to integrate the Polish surname guessing functionality into the website we created in our Hello WebSharper post. This is how it looks like now:

(click on the GIF to enlarge)As you can see on the GIF above, the server can answer in 3 different ways:

  • ‘A stranger has no name?’ if the user didn’t type anything.
  • A prompt to the user to enter his surname too, if he only typed his first name.
  • The origin of the surname as determined by the server, if the user typed both his first name and surname.

You can read more about the Polish surname guessing algorithm here and here. Below is the list of notable changes I made to the website to make it work.

PolishSurnames.fs

I extracted all the functions from AreYouPolish.fsx and created a proper PolishSurnames module (source code here). The most notable refactor was the addition of a proper record type to store the results of the computations:

type SurnameOrigin = DefinitelyPolish | ProbablyPolish | NotPolish

type SurnameStatistics = {
    Surname: string
    Origin: SurnameOrigin
    PolishDensity: float
}

That way it will be easier to pass the data around, as we will see below. I also changed the decideIfPolish function accordingly:

/// Decide if a word is Polish based on its density
let private decideIfPolish (word, density) =
    if (density < 0.2) then {Surname = word; Origin = SurnameOrigin.NotPolish; PolishDensity = density }
    else if (density >= 0.2 && density <= 0.8) then {Surname = word; Origin = SurnameOrigin.ProbablyPolish; PolishDensity = density }
    else {Surname = word; Origin = SurnameOrigin.DefinitelyPolish; PolishDensity = density }

Remoting.fs

I added the new IsSurnamePolishAsync function to Remoting.fs so our JavaScript client can call it directly.

[<Remote>]
let IsSurnamePolishAsync username =
    async {
        let helloMessage = sayHello username       
        let surnameOnly = PolishSurnames.TryExtractSurname username
        
        match surnameOnly with
        | None -> return (username, helloMessage, None)
        | Some surname -> return (username, helloMessage, Some (PolishSurnames.IsSurnamePolish surname))
     }

The  function returns a tuple containing elements:

  • The original username passed as input
  • A custom Hello message based on username
  • Some SurnameStatistics record if a surname could be found, or None otherwise.

This naturally means I had to update the callsite in Client.fs too!

Client.fs

The previous version looked as follows:

async {
    inputValue.Text <- input.Value
    let! data = Server.SayHelloAsync input.Value
    output.Text <- data
}

While the new version is a bit more complex:

async {
    inputValue.Text <- input.Value
    let! (username, helloMessage, stats) = Server.IsSurnamePolishAsync input.Value
    helloOutput.Text <- helloMessage
    
    if username = "" then statsOutput.Text <- "No data, no stats! What were you thinking?"
    else               
        match stats with
        | None -> statsOutput.Text <- "That's a nice name. We could tell you interesting stuff if you provided us with a surname too!"
        | Some s -> statsOutput.Text <- DisplaySurnameStats s
}

This line shows nicely the tuple we saw above being deconstructed into 3 parameters username, helloMessage and stats:

let! (username, helloMessage, stats) = Server.IsSurnamePolishAsync input.Value

Now I have to confess that I didn’t unit-test all those new functions just yet. Nor did I write integration tests, for that matter… This is bad, I know!But fear not: I will write a whole new post dedicated to unit tests in F# soon. To conclude this post, let’s have a look at some JavaScript code generated by WebSharper. The client-side F# DisplaySurnameOrigin function:

let DisplaySurnameOrigin origin =
    match origin with
    | SurnameOrigin.NotPolish -> "not Polish"
    | SurnameOrigin.ProbablyPolish -> "probably Polish"
    | SurnameOrigin.DefinitelyPolish -> "definitely Polish"

is transpilated to the following JavaScript:

DisplaySurnameOrigin:function(o) {
    return o.$==1? "probably Polish" :
	   o.$==0?"definitely Polish" :
	   "not Polish";
}

If we look at the Json data returned from the server for Adam Młynarczyk, we can see that the value 0 in fact represents SurnameOrigin.DefinitelyPolish.

{
   "$DATA":[
      "Adam Młynarczyk",
      "Hello, Adam Młynarczyk!",
      {
         "$V":{
            "$":1,
            "$0":{
               "$V":{
                  "Surname":"Młynarczyk",
                  "Origin":{
                     "$V":{
                        "$":0
                     }
                  },
                  "PolishDensity":1
               }
            }
         }
      }
   ]
}

Wow, that’s a lot of brackets. Another thing I love about F#’s syntax is that I don’t have to deal with those anymore!

That’s it for today, thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *