Instantly share code, notes, and snippets.
Last active
September 4, 2015 19:22
-
Star
1
(1)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save SamirTalwar/5651ad6ade5fd9fc1713 to your computer and use it in GitHub Desktop.
Solution to the Social Network kata in F#.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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