Improving F# Skills
I am interested in learning more about F# and applying it to problems that I encounter. However, for learning purposes it is often easier to pick up a new skill when applying it to an existing project. I found the fsharp github page and many projects in the fsharp community and one drew my interest in particular, FsAutoComplete. In general I have not been much of a contributor to open source because it is a challenge to get involved. Each project has its own rules, features, issues and some maintainers are better than others at attracting contributions and easing in new contributors. In addition finding the time to work on these projects is not always possible for every person.
Finding what to work on
Browsing FsAutoComplete, I became aware of its usecase. F# tooling is improving at a rapid pace and FsAutoComplete extends this to text editors that do not have a native language server protocol to communicate with the ide and the language in use. This seemed like a great project to get some familiarity with using FSharp. Reviewing the github issues I found one issue marked with the tag "help wanted" and the feature related to adding additional logging when FsAutoComplete started with verbose logging on.
Here we go
My first step to claiming this new feature was to respond to the github issue thread and request to work on the issue. I asked some questions about how I should approach the issue and received great feedback on the proper and idiomatic way to deal with the issue. Many FSharp projects use FAKE a dsl for build automation and my task would be to augment the current build automation to add the latest githash to the AssemblyInfo as a metadata property. Once this new information appears in the AssemblyInfo I can create a function in the running program to pull out the data and log it at runtime.
Searching for Answers
The first step was simple since FAKE has git integration and can write to the assembly easily so I augmented that portion quickly. The biggest challenge that I found while working on this issue involved understanding how AssemblyInfo properties could be accessed and how to manipulate that information in a FSharp idiomatic way. After much googling I came up with my particular solution to grab the information but I was dissatisfied with how it looked.
let getGitHash () = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof<AssemblyMetadataAttribute>, true) |> Array.map (fun x-> let meta = (downcast (box x) : AssemblyMetadataAttribute) meta.Key,meta.Value ) |> Array.filter (fun (x,y) -> if x = "githash" && y <> String.Empty then true else false ) |> Array.map (fun (x,y) -> y ) |> Array.tryHead
For my needs I was satisfied with being able to retrieve the data I wanted. However, in the world of software development feedback is important to becoming a better developer. I reached out to the kind people in the FSharp Foundation Slack Channel and asked how I could improve this code. I received great information even from other developers of FSharpAutoComplete.
The solution that I settled on turned into this
let getGitHash () = Assembly.GetEntryAssembly().GetCustomAttributes(typeof<AssemblyMetadataAttribute>, true) |> Seq.cast<AssemblyMetadataAttribute> |> Seq.map (fun (m) -> m.Key,m.Value) |> Seq.tryPick (fun (x,y) -> if x = "githash" && not (String.IsNullOrWhiteSpace(y)) then Some y else None )
This solution turned out very nice and succint in a FSharp way. I also learned there was a subtle difference between the entry assembly and executing assembly which only the entry assembly had the metadata I needed. The Seq.cast function is very helpful to turn some older non-generic collections into a typed generic one. I then could map the key value pairs of the metadata collection into a Seq of tuples then use try pick which would return me a Some string if the conditions were met otherwise None.
This type of solution conveys how concise and excellent FSharp is to work with. I always receive one of these values without nulls, there is very little focus on the mechanisms of transforming the data into the type I need and selecting it and the inferred typing saves me time without the loss of additional information.
Just by asking the question about how this could be better I learned many new things about FSharp. The last few steps to tie up the feature were to add this as a member on the Command object that would pattern match for the result and store either the hash if it succeeded or an empty string if it did not. Then jumping into the two implementations of either stdio or suave to add this log at the start.
Once I was content with my additions I synced the main branch back up with mine and sent a pull request to FsAutoComplete. In a few days one of the maintainers reviewed my changes and merged them in. The commit and discussion can be seen at this link here.
Overall this was a great and wondeful first time experience committing to a major FSharp project. This is a language that I have been toying around with for a few months and nothing solidifies knowledge more than diving in and solving how to get something done. I invite anyone interested in FSharp to browse the FSharp GitHub Repo to see about adding a contribution to one of the projects. The FSharp Foundation also has mentorship matching events to help people learn the language and also commit to open source.
If you're interested in participating in the FSharp community checkout the FSharp Foundation Website
If you're interested in Veterans United Home Loans check us out here