Skip to content

Instantly share code, notes, and snippets.

@sjmyuan
Created July 5, 2020 03:35
Show Gist options
  • Save sjmyuan/d01e0cd978822858b5ec8b087b6b0bdc to your computer and use it in GitHub Desktop.
Save sjmyuan/d01e0cd978822858b5ec8b087b6b0bdc to your computer and use it in GitHub Desktop.
case class Reader[A, B](run: A => B) {
def map[C](f: B => C): Reader[A, C] = {
val runN: A => C = (x: A) => f(run(x))
Reader(runN)
}
def flatMap[C](f: B => Reader[A, C]): Reader[A, C] = {
val runN: A => C = (x: A) => f(run(x)).run(x)
Reader(runN)
}
}
object Reader {
def ask[A]: Reader[A, A] = Reader[A, A](identity)
}
trait HttpRequest {
def get(url: String): String
}
trait HasHttpRequest[A] {
def getHttpRequest(v: A): HttpRequest
}
class LogHttpRequest extends HttpRequest {
override def get(url: String): String = {
println(s"send request to ${url}")
List(1, 2, 3, 4, 5, 6).mkString(",")
}
}
trait Database {
def runSql(sql: String): Unit
}
trait HasDatabase[A] {
def getDatabase(v: A): Database
}
class LogDatabase extends Database {
override def runSql(sql: String): Unit = println(s"run sql ${sql}")
}
case class Env(http: HttpRequest, database: Database)
object Env {
implicit object EnvHasHttpRequest extends HasHttpRequest[Env] {
override def getHttpRequest(v: Env): HttpRequest = v.http
}
implicit object EnvHasDatabase extends HasDatabase[Env] {
override def getDatabase(v: Env): Database = v.database
}
}
trait DataSource[A] {
def getData: Reader[A, List[Int]]
}
class HttpDataSource[A: HasHttpRequest] extends DataSource[A] {
override def getData: Reader[A, List[Int]] =
for {
env <- Reader.ask[A]
http = implicitly[HasHttpRequest[A]].getHttpRequest(env)
data = http.get("http://example.com/data").split(",").map(_.toInt).toList
} yield data
}
trait DataStore[A] {
def save(data: List[Int]): Reader[A, Unit]
}
class DatabaseStore[A: HasDatabase] extends DataStore[A] {
override def save(data: List[Int]): Reader[A, Unit] =
for {
env <- Reader.ask[A]
database = implicitly[HasDatabase[A]].getDatabase(env)
} yield database.runSql(
s"insert into data_table values(${data.mkString(",")})"
)
}
trait DataEncoder {
def encode(data: List[Int]): List[Int]
}
class PlusOneEncoder extends DataEncoder {
def encode(data: List[Int]): List[Int] = {
println(s"encoding ${data}")
data.map(_ + 1)
}
}
class DataJob[A](
source: DataSource[A],
store: DataStore[A],
encoder: DataEncoder
) {
def run: Reader[A, Unit] =
for {
data <- source.getData
val encodedData = encoder.encode(data)
_ <- store.save(encodedData)
} yield ()
}
object Main {
def main() {
val http = new LogHttpRequest()
val database = new LogDatabase()
val env = Env(http, database)
val source = new HttpDataSource[Env]
val store = new DatabaseStore[Env]
val encoder = new PlusOneEncoder()
val program = new DataJob[Env](source, store, encoder)
program.run.run(env)
}
}
Main.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment