Created
August 14, 2018 04:28
-
-
Save rjdestigter/4f7866afb5497c7c4f907e89a04cdfff to your computer and use it in GitHub Desktop.
scalaz-state-talk converted from Scala to TypeScript with fp-ts
This file contains 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
import { State, modify, gets } from 'fp-ts/lib/State' | |
import { Option, fromNullable, none, some } from 'fp-ts/lib/Option' | |
import * as _ from 'lodash' | |
/** Use state combinators. */ | |
type StateCache<A> = State<Cache, A> | |
interface SocialService { | |
followerStats(u: string): StateCache<FollowerStats> | |
} | |
class FollowerStats { | |
public readonly username: string | |
public readonly numFollowers: number | |
public readonly numFollowing: number | |
constructor(username: string, numFollowers: number, numFollowing: number) { | |
this.username = username | |
this.numFollowers = numFollowers | |
this.numFollowing = numFollowing | |
} | |
} | |
class Timestamped<A> { | |
public readonly value: A | |
public readonly timestamp: number | |
constructor(value: A, timestamp: number) { | |
this.value = value | |
this.timestamp = timestamp | |
} | |
} | |
class Cache { | |
public readonly stats: Map<string, Timestamped<FollowerStats>> // Map[string, Timestamped[FollowerStats]], | |
public readonly hits: number | |
public readonly misses: number | |
constructor( | |
stats: Map<string, Timestamped<FollowerStats>>, | |
hits: number, | |
misses: number | |
) { | |
this.stats = stats | |
this.hits = hits | |
this.misses = misses | |
} | |
public get(username: string): Option<Timestamped<FollowerStats>> { | |
return fromNullable(this.stats.get(username)) | |
} | |
public update(u: string, s: Timestamped<FollowerStats>): Cache { | |
const entries = [...this.stats.entries()].concat([u, s]) | |
const nextStats = new Map(entries) | |
return new Cache(nextStats, this.hits, this.misses) | |
} | |
public recordHit(): Cache { | |
return new Cache(this.stats, this.hits + 1, this.misses) | |
} | |
public recordMiss(): Cache { | |
return new Cache(this.stats, this.hits, this.misses + 1) | |
} | |
} | |
export default class FakeSocialService implements SocialService { | |
public followerStats(u: string): StateCache<FollowerStats> { | |
const initialState = this.checkCache(u) | |
return initialState.chain(ofs => | |
ofs.fold( | |
this.retrieve(u), | |
(fs: FollowerStats) => new State((c: Cache) => [fs, c]) | |
) | |
) | |
} | |
private checkCache(u: string): StateCache<Option<FollowerStats>> { | |
const initialState = gets((c: Cache) => | |
c.get(u).chain(a => (this.stale(a.timestamp) ? none : some(a.value))) | |
) | |
return initialState.chain(ofs => | |
modify((c: Cache) => (ofs.isNone() ? c.recordMiss() : c.recordHit())).map( | |
() => ofs | |
) | |
) | |
} | |
private stale(ts: number): boolean { | |
return Date.now() - ts > 5 * 60 * 1000 | |
} | |
private retrieve(u: string): StateCache<FollowerStats> { | |
const initialState = new State((c: Cache) => [this.callWebService(u), c]) | |
const bar = (fs: FollowerStats) => { | |
const tfs = new Timestamped(fs, Date.now()) | |
return modify<Cache>(c => c.update(u, tfs)).map(() => fs) | |
} | |
return initialState.chain(bar) | |
} | |
private callWebService(u: string): FollowerStats { | |
return new FollowerStats(u, 0, 0) | |
} | |
} |
Related file: https://github.com/mpilquist/scalaz-state-talk/blob/master/src/main/scala/training/5.scala
with thanks to @mpilquist
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Related YouTube: https://www.youtube.com/watch?v=Jg3Uv_YWJqI