Skip to content

Instantly share code, notes, and snippets.

@SamirTalwar
Last active September 4, 2015 19:22
Show Gist options
  • Select an option

  • Save SamirTalwar/5651ad6ade5fd9fc1713 to your computer and use it in GitHub Desktop.

Select an option

Save SamirTalwar/5651ad6ade5fd9fc1713 to your computer and use it in GitHub Desktop.
Solution to the Social Network kata in F#.
module Social
open FSharpx.State
open NodaTime
type User = {Name: string}
type Quack = {User: User; Text: string; Timestamp: Instant}
type Quacks = Quack list
type Timeline = Quack list
type Timelines = Map<User, Quacks * Timeline>
type Following = Map<User, User list>
type Network = {Timelines: Timelines; Following: Following; Now: unit -> Instant}
let newSocialNetwork (clock: IClock) = {Timelines = Map.empty; Following = Map.empty; Now = (fun () -> clock.Now)}
let timeline user = state {
let! (network: Network) = getState
return defaultArg (Map.tryFind user network.Timelines) ([], []) }
let read user = state {
return! timeline user |> map fst }
let post user message = state {
let! (network: Network) = getState
let! quacks, timeline = timeline user
let newQuack = {User = user; Text = message; Timestamp = network.Now()}
do! putState { network with Timelines = Map.add user (newQuack :: quacks, newQuack :: timeline) network.Timelines }
return () }
let follows follower followee = state {
let! (network: Network) = getState
let followees = defaultArg (Map.tryFind follower network.Following) []
let newFollowing = Map.add follower (followee :: followees) network.Following
let! followerQuacks, followerTimeline = timeline follower
let! followeeQuacks = timeline followee |> map fst
let newTimeline = List.append followeeQuacks followerTimeline |> List.sortWith (fun a b -> Operators.compare b.Timestamp a.Timestamp)
do! putState { network with Timelines = Map.add follower (followerQuacks, newTimeline) network.Timelines; Following = newFollowing }
return () }
let timelineFor user = state {
return! timeline user |> map snd }
module Tests =
open NUnit.Framework
open NodaTime.Testing
open Swensen.Unquote.Assertions
[<Measure>]
type minute =
static member asDuration value = Duration.FromMinutes(int64 value)
[<Measure>]
type hour =
static member asDuration value = Duration.FromHours(int64 value)
let advanceMinutes (time: int<minute>) (clock: FakeClock) =
clock.AdvanceMinutes(int64 time)
let advanceHours (time: int<hour>) (clock: FakeClock) =
clock.AdvanceHours(int64 time)
let assertThat (actual: 'T) (expression: Constraints.IResolveConstraint) = Assert.That (actual, expression)
[<Test>]
let ``Alice can publish messages to a personal timeline``() =
let alice = {Name = "Alice"}
let clock = FakeClock.FromUtc(2015, 5, 4)
let network = newSocialNetwork clock
let aliceTimeline = eval (state {
do! post alice "message"
return! timeline alice |> map fst}) network
aliceTimeline =? [{User = alice; Text = "message"; Timestamp = clock.Now}]
[<Test>]
let ``Anyone can view Alice’s timeline``() =
let alice = {Name = "Alice"}
let bob = {Name = "Bob"}
let clock = FakeClock.FromUtc(2015, 5, 5, 13, 0, 0)
let network = exec (state {
do! post bob "message from bob"
advanceHours 1<hour> clock
do! post alice "message from alice" }) (newSocialNetwork clock)
let quacksByAlice = eval (read alice) network
quacksByAlice =? [{User = alice; Text = "message from alice"; Timestamp = clock.Now}]
[<Test>]
let ``Carol can subscribe to Alice’s and Bob’s timelines, and view an aggregated list of all subscriptions``() =
let alice = {Name = "Alice"}
let bob = {Name = "Bob"}
let carol = {Name = "Carol"}
let dave = {Name = "Dave"}
let clock = FakeClock.FromUtc(2015, 3, 14)
let network = exec (state {
do! post alice "Is anyone home?"
advanceMinutes 5<minute> clock
do! post bob "I am. Come say hi!"
do! post dave "Oh?"
advanceMinutes 2<minute> clock
do! post carol "I'm coming!"
advanceMinutes 1<minute> clock
do! post dave "I can't make it. :-("
advanceMinutes 2<minute> clock
do! post alice "OK, party time." }) (newSocialNetwork clock)
let timelineForCarol = eval (state {
do! follows alice bob
do! follows carol alice
do! follows carol bob
return! timelineFor carol }) network
let now = clock.Now
timelineForCarol =?
[{User = alice; Text = "OK, party time."; Timestamp = now};
{User = carol; Text = "I'm coming!"; Timestamp = now - (3<minute> |> minute.asDuration)};
{User = bob; Text = "I am. Come say hi!"; Timestamp = now - (5<minute> |> minute.asDuration)};
{User = alice; Text = "Is anyone home?"; Timestamp = now - (10<minute> |> minute.asDuration)}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment