Skip to content

Instantly share code, notes, and snippets.

@rjdestigter
Created August 14, 2018 04:28
Show Gist options
  • Save rjdestigter/4f7866afb5497c7c4f907e89a04cdfff to your computer and use it in GitHub Desktop.
Save rjdestigter/4f7866afb5497c7c4f907e89a04cdfff to your computer and use it in GitHub Desktop.
scalaz-state-talk converted from Scala to TypeScript with fp-ts
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)
}
}
@rjdestigter
Copy link
Author

@rjdestigter
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment