F#: Refactoring that little twitter application into objects
I previously wrote about a little twitter application I've been writing to go through my twitter feed and find only the tweets with links it and while it works I realised that I was finding it quite difficult to add any additional functionality to it.
I've been following the examples in Real World Functional Programming which has encouraged an approach of creating functions to do everything that you want to do and then mixing them together.
This works quite well for getting a quick development cycle but I found that I ended up mixing different concerns in the same functions, making it really difficult to test the code I've been working on – I decided not to TDD this application because I don't know the syntax well enough. I am now suffering from that decision!
Chatting to Nick about the problems I was having he encouraged me to look at the possibility of structuring the code into different objects – this is still the best way that I know for describing intent and managing complexity although it doesn't feel like 'the functional way'.
Luckily Chapter 9 of the book (which I hadn't reached yet!) explains how to restructure your code into a more manageable structure.
I'm a big fan of creating lots of little objects in C# land so I followed the same approach here. I found this post really useful for helping me understand the F# syntax for creating classes and so on.
I started by creating a type to store all the statuses:
type Tweets = { TwitterStatuses: seq<TwitterStatus> }
F# provides quite a nice way of moving between the quick cycle of writing functions and testing them to structuring objects with behaviour and data together by allowing us to append members using augmentation.
From the previous code we have these two functions:
let withLinks (statuses:seq<TwitterStatus>) = statuses |> Seq.filter (fun eachStatus -> eachStatus.Text.Contains("http")) let print (statuses:seq<TwitterStatus>) = for status in statuses do printfn "[%s] %s" status.User.ScreenName status.Text
We can add these two methods to the Tweets type using type augmentations:
type Tweets with member x.print() = print x.TwitterStatuses member x.withLinks() = { TwitterStatuses = withLinks x.TwitterStatuses}
It looks quite similar to C# extension methods but the methods are actually added to the class rather than being defined as static methods. The type augmentations need to be in the same file as the type is defined.
Next I wanted to put the tweetsharp API calls into their own class. It was surprisingly tricky working out how to create a class with a no argument constructor but I guess it's fairly obvious in the end.
type TwitterService() = static member GetLatestTwitterStatuses(recordsToSearch) = findStatuses(0L, 0, recordsToSearch, [])
I managed to simplify the recursive calls to the Twitter API to keep getting the next 20 tweets as well:
let friendsTimeLine = FluentTwitter.CreateRequest().AuthenticateAs("userName", "password").Statuses().OnFriendsTimeline() let getStatusesBefore (statusId:int64) = if(statusId = 0L) then friendsTimeLine.AsJson().Request().AsStatuses() else friendsTimeLine.Before(statusId).AsJson().Request().AsStatuses() let rec findStatuses (args:int64 * int * int * seq<TwitterStatus>) = let findOldestStatus (statuses:seq<TwitterStatus>) = statuses |> Seq.sort_by (fun eachStatus -> eachStatus.Id) |> Seq.hd match args with | (_, numberProcessed, statusesToSearch, soFar) when numberProcessed >= statusesToSearch -> soFar | (lastId, numberProcessed, statusesToSearch, soFar) -> let latestStatuses = getStatusesBefore lastId findStatuses(findOldestStatus(latestStatuses).Id, numberProcessed + 20, statusesToSearch, Seq.append soFar latestStatuses)
To get the tweets we can now do the following:
let myTweets = { TwitterStatuses = TwitterService.GetLatestTwitterStatuses 100 };; myTweets.withLinks().print();;
I still feel that I'm thinking a bit too procedurally when writing this code but hopefully that will get better as I play around with F# more.
One other lesson from this refactoring is that it's so much easier to refactor code when you have tests around them – because I didn't do this I had to change a little bit then run the code manually and check nothing had broken. Painful!
[...] just spent the last 2 hours doing some refactoring on an F# twitter application I'm working on and because I didn't write any tests it's been a very painful experience [...]
I don’t have time not to test! at Mark Needham
18 Apr 09 at 9:27 am
F#: Refactoring that little twitter application into objects – Mark Needham…
Thank you for submitting this cool story – Trackback from DotNetShoutout…
DotNetShoutout
19 Apr 09 at 5:12 am
F#: Refactoring that little twitter application into objects at Mark Needham…
9efish.感谢你的文章 – Trackback from 9eFish…
9eFish
20 Apr 09 at 12:06 pm
[...] continuing playing with my F# twitter application I was trying to work out how to exclude the tweets that I posted from the list that gets [...]
F#: Not equal/Not operator at Mark Needham
25 Apr 09 at 10:14 pm
[...] trying to refactor my twitter application into a state where I could use Erlang style message passing to process some requests asynchronously [...]
F#: Overloading functions/pattern matching at Mark Needham
28 Apr 09 at 11:45 pm
[...] API only allows me to retrieve 20 statuses at a time and if I'm getting a large number of them my original design means that we are just waiting for the statuses to be accumulated before we can do anything else [...]
F#: Erlang style messaging passing at Mark Needham
2 May 09 at 1:55 am
[...] wanted to create an active pattern that would be able to tell me if a Twitter status has a url in it and to return me that url. If there are no urls then it should tell me that [...]
F#: Regular expressions/active patterns at Mark Needham
10 May 09 at 9:01 am
[...] with my attempts to test some of the code in my twitter application I've been trying to work out how to test the Erlang style messaging which I set up to process [...]
F#: Testing asynchronous calls to MailBoxProcessor at Mark Needham
30 May 09 at 8:46 pm
[...] moment and manages the complexity more easily. It is quite easy to switch between the two styles using features like member augmentation so I think it's probably possible to mix the two styles quite [...]
Real World Functional Programming: Book Review at Mark Needham
4 Jun 09 at 8:52 am