Phillip Trelford's Array

POKE 36879,255

Calendar Types

Welcome to the 2015 F# Advent Calendar and one of 2 posts for December 3rd. For last year’s advent calendar I tried to follow the seasonal theme and produced an article on generating snowflakes. This year I thought I’d be more literal and look at producing calendar types using F#’s Type Provider mechanism resurrecting a project from 2014, FSharp.Date.

FSharp.Date

FSharp.Date is a simple F# Type Provider, inspired by VB.Net’s date literal feature,that lets you define dates and time values in F# by pressing dot and selecting only valid values:

2015 December 3rd

Further inspired by the advent calendar theme I’ve added a new feature that lets you visualize the calendar month as a tooltip in your editor:

2015 December Calendar

The source is available on BitBucket or you can download the package from Nuget.

But wait there’s more…

On This Day

That got me thinking, what if you could provide topical information on a particular day direct to the editor, and then I found the BBC news site On This Day.

The web site contains a set of both historically significant and quirky stories from the same day in the past.

First I needed a way of finding an article and scraping the news items from the page.

Thankfully the BBC pages use a uniform URL taking the month and day making it easy to get a specific page:

let getPage (month,day) =
    let date = System.DateTime(2005,month,day)    
    let monthName = date.ToString("MMMM").ToLower()
    let url = sprintf "http://news.bbc.co.uk/onthisday/low/dates/stories/%s/%d/default.stm"
                 monthName date.Day
    use client = new System.Net.WebClient()
    client.DownloadString(url)  

The page’s HTML is not well formed so I resorted to a regular expression to parse the news items:

let getNewsItems html =
    let pattern = """<a href="([^"]*)"><span class="h1">(.*?)</span></a><br>(.*?)<br clear="ALL">"""
    let matches = Regex.Matches(html, pattern, RegexOptions.Singleline)
    let newsItems = [for m in matches -> [for i in 1..m.Groups.Count-1 -> m.Groups.[i].Value]]
    [for newsItem in newsItems do
        match newsItem with
        | [link;title;description] ->
            yield "http://news.bbc.co.uk" + link, title.Trim(), description.Trim().Replace("\n","").Replace("\r","")
        | _ -> ()
    ]

Which returns a list of news items for the specified month and day:

> (12,3)|> getPage |> getNewsItems;;
val it : (string * string * string) list =
  [("http://news.bbc.co.uk/onthisday/low/dates/stories/december/3/newsid_2698000/2698709.stm",
    "1984: Hundreds die in Bhopal chemical accident",
    "A dense cloud of lethal gas escapes from a chemical factory in the central Indian city of Bhopal, killing hundreds of people.");
   ("http://news.bbc.co.uk/onthisday/low/dates/stories/december/3/newsid_2519000/2519715.stm",
    "1992: Bomb explosions in Manchester",
    "Emergency services are dealing with casualties at the scene of two bomb blasts in the centre of Manchester.");
   ("http://news.bbc.co.uk/onthisday/low/dates/stories/december/3/newsid_4119000/4119950.stm",
    "1989: Malta summit ends Cold War",
    "The leaders of the two world superpowers declare an end to the Cold War after two days of storm-lashed talks at the Malta summit.");
   ("http://news.bbc.co.uk/onthisday/low/dates/stories/december/3/newsid_4119000/4119070.stm",
    "1965: White jury convicts Ku Klux Klansmen",
    "For the first time an all-white jury convicts members of the KKK over the murder of a white civil rights activist Viola Liuzzo.");
   ("http://news.bbc.co.uk/onthisday/low/dates/stories/december/3/newsid_2519000/2519133.stm",
    "1971: Pakistan intensifies air raids on India",
    "India declares a state of emergency as  airports are hit during a Pakistani attack on the country.");
   ("http://news.bbc.co.uk/onthisday/low/dates/stories/december/3/newsid_2519000/2519451.stm",
    "1988: Egg industry fury over salmonella claim",
    "Claims by a health minister that eggs contain salmonella are branded alarmist and incorrect.")]
> 

Now to create a type provider.

Creating your own Type Provider

If you’re new to writing your own type provider I’d recommend starting with Michael Newton’s Type Providers From the Ground Up post.

First off we need to reference two F# files from the FSharp.TypeProviders.StarterPack.

Paket

Rather than make a static copy I used Paket, a handy .Net dependency management tool, to reference the files directly from GitHub.

This required a simple paket.dependencies file:

source https://nuget.org/api/v2

github fsprojects/FSharp.TypeProviders.StarterPack src/ProvidedTypes.fsi 
github fsprojects/FSharp.TypeProviders.StarterPack src/ProvidedTypes.fs 

and a paket.references file:

File:ProvidedTypes.fsi
File:ProvidedTypes.fs

With that in place all that’s left to do is run > paket install.

Defining the Type Provider

To create a type provider you need a type with the TypeProvider attribute that inherits from TypeProviderFromNamespaces. For this example I only need a single type OnThisDay which provides the news items via a set of static properties:

[<TypeProvider>]
type OnThisDayProvider (config:TypeProviderConfig) as this = 
   inherit TypeProviderForNamespaces ()

   let getProperties newsItems =
       [for (url, title, description) in newsItems ->
           let property = 
               ProvidedProperty(title, typeof<string>, IsStatic=true,
                  GetterCode=fun _ -> <@@ url @@>)
           property.AddXmlDoc(description)
           property]

   let ns = "FsAdvent"
   let asm = System.Reflection.Assembly.GetExecutingAssembly()
   let today = System.DateTime.Now
   let providedType = ProvidedTypeDefinition(asm, ns, "OnThisDay", Some typeof<obj>)
   do  providedType.AddXmlDoc(today.ToLongDateString())
   do  providedType.AddMembersDelayed(fun () ->             
            (today.Month,today.Day) |> getPage |> getNewsItems |> getProperties
       )
   do  this.AddNamespace(ns, [providedType])

Once it’s built you can reference the type provider and get a list of topical news items for the day directly in your editor:

Tunnel

The selected property returns the URL as the value, which you can easily launch with your default browser using Process.Start:

OnThisDay.``1995: Rogue trader jailed for six years``
|> System.Diagnostics.Process.Start

If you fancy a play the source code is available on GitHub: https://github.com/ptrelford/OnThisDay

Happy holidays!

Pimping BASIC with lower casing & JSON literals

Back on April Fool’s day Anthony Green published a funny article “How ‘Roslyn# Finally Unshackled Visual Basic From The Tyranny of the Pretty-Lister”, showing VB.Net with lower case keywords, and how it made it look cooler. And more recently Mads Torgersen gave an “Early View of C# 7” demonstrating pattern matching, a feature that didn’t make it into C# 6, and syntax for tuples.

Taking some inspiration from this I thought I’d add some new extensions to my own language project, Fun Basic (try it free on the Windows Store). But Fun Basic already supports lower case keywords and tuples with pattern matching (which I implemented about 2 years ago), so instead over the last week I’ve added some different language features including cleaner aesthetics for BASIC and JSON literal support.

Hipster BASIC

People often complain about the verbosity of VB.Net, I see two parts to this:

  • End statements – End If, End While, End Select, etc.
  • Casing – Dim is the same length as var, but var just looks smaller

Fun Basic now lets you write `end` and it will infer the type of end for you:

 while i < 10
    i = i - 1
 end

With the lower case keywords & simple end statement you could easily mistake this syntax for Ruby code.

JSON literals

VB.Net has XML literal support, which was a cool feature at the time, but these days XML is more associated with big enterprise, and all the hipsters are using JSON. With that in mind I’ve added JSON literal support to Fun BASIC:

name = "Phil"
age = 27
phil = {"name":name, "age":age}

This allows you to build up strings to send to web based services.

The syntax is also quite close to record syntax in ML, OCaml, F#, TypeScript etc.

Pattern matching

JSON literals are cool, but most of the time you’re using it the other way around, and consuming JSON from an API. For this I’ve added pattern matching over JSON literals.

For example say we want to get the temperature and humidity in London, we can use the Open Weather API, which spits back:

{"coord":{"lon":-0.13,"lat":51.51},
 "weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],
 "base":"cmc stations",
 "main":{"temp":282.515,"pressure":1026.96,"humidity":97,"temp_min":282.515,"temp_max":282.515,
         "sea_level":1037.24,"grnd_level":1026.96},
 "wind":{"speed":5.75,"deg":228.012},"rain":{"3h":0.6475},"clouds":{"all":92},"dt":1447504501,
         "sys":{"message":0.003,"country":"GB","sunrise":1447485423,"sunset":1447517536},
 "id":2643743,
 "name":"London",
 "cod":200}

With the new Fun Basic pattern matching syntax we can easily extract the values of temp and humidity:

london = "http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=2de143494c0b295cca9337e1e96b00e0"
weather = Web.Get(london)
{"main":{"temp":temp, "humidity":humidity}} = weather

We can also use the pattern matching in a select case statement:

function ageGroup(person)
  select case person
    case { "age": Is >= 18 }
       return "Adult"
    case else
       return "Child"
  end       
end

sean = {"name":"sean", "age":9}
TextWindow.WriteLine(ageGroup(sean))

Summary

Both features were easy to implement (JSON literals took me the morning), and feel quite natural in the setting of BASIC, you can try them out now in Fun Basic, who knows one day we might see them in mainstream enterprise languages Smile

Mini US Tour – Fall 2015

Next week I’ll be heading over to the US for some tourism with a little speaking along the way:

Hope to see some of you on the way, and a big thanks to Rachel Reese and Jet.com for inviting me across the pond! :)

P.S. To see what a big tour looks like check out supreme F# tourist Mathias Brandewinder’s European adventure last year…