Entreprise qui offre du support autour de Scala et de librairies Scala (et avec des API Java)
Propose une transition simple depuis Java via des librairies mixant OO et FP
Orienté architectures d’entreprises : micro services, cloud, etc.
Communauté open source promouvant la FP pure et proposant un grand nombre de librairies basées sur ce principe
Akka
Play Framework
Slick
SBT
Lagom
Cats (fondation)
Shapeless
Http4s
Doobie
Circe
FS2
Monix
...
Les fonctions pures sont :
RT (Wikipedia) :La transparence référentielle est une propriété des expressions d'un langage de programmation qui fait qu'une expression peut être remplacée par sa valeur sans changer le comportement du programme
Fonctions à effet de bord (wikipedia) :
Les fonctions qui modifient une variable ou ses arguments, qui lèvent une exception, qui écrivent des données vers un écran ou un fichier, qui lisent les données d'un clavier ou d'un fichier, les fonctions appelant d'autres fonctions à effet de bord
Plus simplement : l'absence de transparence référentielle.
def futureComputation(x: Int): Future[Int] = ???
//parallel computation
val f1 = futureComputation(1)
val f2 = futureComputation(2)
for {
v1 <- f1
v2 <- f2
} yield (v1 + v2)
//sequential computation
def f1 = futureComputation(1)
def f2 = futureComputation(2)
for {
v1 <- f1
v2 <- f2
} yield (v1 + v2)
Ok mais attention au refactoring!
def futureComputation(x: Int): IO[Int] = ??? //async IO
val f1 = futureComputation(1)
val f2 = futureComputation(2)
for {
v1 <- f1
v2 <- f2
} yield (v1 + v2)
def f1 = futureComputation(1)
def f2 = futureComputation(2)
for {
v1 <- f1
v2 <- f2
} yield (v1 + v2)
for {
v1 <- futureComputation(1)
v2 <- futureComputation(2)
} yield (v1 + v2)
Ces 3 for comprehension sont équivalents.
Les traitements sont séquentiels.
IO est lazy et permet d'assurer la transparence référentielle (RT).
Exemple avec Cats Effect
def futureComputation(x: Int): IO[Int] = ??? //async IO
val f1 = futureComputation(1)
val f2 = futureComputation(2)
// traitement parralèle
val program = (f1 , f2).parMapN {
(v1, v2) => v1 + v2
}
program.unsafeRunSync()
//ou
object Main extends IOApp {
//...
def run(args: List[String]): IO[ExitCode] = program.as(ExitCode.Success)
}
Avant le unsafeRunSync rien n'est exécuté, tout est lazy
On sait où les effets vont se produire et où les erreurs peuvent apparaître
Massivement utilisées dans Cats
case class Color(r: Int, g: Int, b: Int)
implicit val colorNumeric: Numeric[Color] = ??? //implement numeric methods for Color
List(Color(10,10,10), Color(200, 255, 10), Color(20, 55, 1)).sum
// Color(62,66,32)
// List scaladoc :
def sum[B >: A](implicit num: Numeric[B]): B
List(1,2,3).sum //6
import cats.effect.IO
import cats.implicits._
//traverse
val ioList: List[IO[Int]] = List(1, 2, 3).map(x => IO.pure(x+1))
val ioList2: IO[List[Int]] = List(1, 2, 3).map(x => IO.pure(x+1)).sequence
val ioList3: IO[List[Int]] = List(1, 2, 3).traverse(x => IO.pure(x+1))
//cartesian
// execute both, keep only dbCall result in IO
val dbResult: IO[DBResult] = IO(println("go!")) *> IO(dbCall(x))
// execute both, keep only println result in IO (IO[Unit])
val logMessageIO: IO[Unit] = IO(println("go!")) <* IO(dbCall(x))
Cats Effect bénéficie des type classes de Cats.
case class Message(text: String, author: String)
object Message { implicit val messageReader = Json.reads[Message] }
object MixedStream extends App {
implicit val system = ActorSystem("MixedStream")
implicit val materializer = ActorMaterializer()
val wsClient = StandaloneAhcWSClient()
def queryToSource(keyword: String): Source[Message, NotUsed] = {
val request = wsClient
.url(s"http://localhost:3000?keyword=$keyword")
Source.fromFuture(request.stream()).flatMapConcat(_.bodyAsSource)
.via(Framing.delimiter(ByteString("\n"),
maximumFrameLength = 100, allowTruncation = true))
.map(byteString => Json.parse(byteString.utf8String).as[Message])
}
// can mix n streams
val keywordSources: Source[String, NotUsed] = Source(List("Akka", "FS2"))
val done: Future[Done] = keywordSources.flatMapMerge(10, queryToSource)
.runWith(Sink.foreach(println))
Await.result(done, Duration.Inf)
}
Akka Stream / Play
case class Message(text: String, author: String)
object Message { implicit val messageReader = Json.reads[Message] }
object MixedStream extends App {
implicit val system = ActorSystem("MixedStream")
implicit val materializer = ActorMaterializer()
val wsClient = StandaloneAhcWSClient()
def queryToSource(keyword: String): Source[Message, NotUsed] = {
val request = wsClient
.url(s"http://localhost:3000?keyword=$keyword")
Source.fromFuture(request.stream()).flatMapConcat(_.bodyAsSource)
.via(Framing.delimiter(ByteString("\n"),
maximumFrameLength = 100, allowTruncation = true))
.map(byteString => Json.parse(byteString.utf8String).as[Message])
}
// can mix n streams
val keywordSources: Source[String, NotUsed] = Source(List("Akka", "FS2"))
val done: Future[Done] = keywordSources.flatMapMerge(10, queryToSource)
.runWith(Sink.foreach(println))
Await.result(done, Duration.Inf)
}
Akka Stream / Play
case class Message(text: String, author: String)
object Message { implicit val messageReader = Json.reads[Message] }
object MixedStream extends App {
implicit val system = ActorSystem("MixedStream")
implicit val materializer = ActorMaterializer()
val wsClient = StandaloneAhcWSClient()
def queryToSource(keyword: String): Source[Message, NotUsed] = {
val request = wsClient
.url(s"http://localhost:3000?keyword=$keyword")
Source.fromFuture(request.stream()).flatMapConcat(_.bodyAsSource)
.via(Framing.delimiter(ByteString("\n"),
maximumFrameLength = 100, allowTruncation = true))
.map(byteString => Json.parse(byteString.utf8String).as[Message])
}
// can mix n streams
val keywordSources: Source[String, NotUsed] = Source(List("Akka", "FS2"))
val done: Future[Done] = keywordSources.flatMapMerge(10, queryToSource)
.runWith(Sink.foreach(println))
Await.result(done, Duration.Inf)
}
Akka Stream / Play
case class Message(text: String, author: String)
object Message { implicit val messageReader = Json.reads[Message] }
object MixedStream extends App {
implicit val system = ActorSystem("MixedStream")
implicit val materializer = ActorMaterializer()
val wsClient = StandaloneAhcWSClient()
def queryToSource(keyword: String): Source[Message, NotUsed] = {
val request = wsClient
.url(s"http://localhost:3000?keyword=$keyword")
Source.fromFuture(request.stream()).flatMapConcat(_.bodyAsSource)
.via(Framing.delimiter(ByteString("\n"),
maximumFrameLength = 100, allowTruncation = true))
.map(byteString => Json.parse(byteString.utf8String).as[Message])
}
// can mix n streams
val keywordSources: Source[String, NotUsed] = Source(List("Akka", "FS2"))
val done: Future[Done] = keywordSources.flatMapMerge(10, queryToSource)
.runWith(Sink.foreach(println))
Await.result(done, Duration.Inf)
}
Akka Stream / Play
case class Message(text: String, author: String)
object Message { implicit val messageReader = Json.reads[Message] }
object MixedStream extends App {
implicit val system = ActorSystem("MixedStream")
implicit val materializer = ActorMaterializer()
val wsClient = StandaloneAhcWSClient()
def queryToSource(keyword: String): Source[Message, NotUsed] = {
val request = wsClient
.url(s"http://localhost:3000?keyword=$keyword")
Source.fromFuture(request.stream()).flatMapConcat(_.bodyAsSource)
.via(Framing.delimiter(ByteString("\n"),
maximumFrameLength = 100, allowTruncation = true))
.map(byteString => Json.parse(byteString.utf8String).as[Message])
}
// can mix n streams
val keywordSources: Source[String, NotUsed] = Source(List("Akka", "FS2"))
val done: Future[Done] = keywordSources.flatMapMerge(10, queryToSource)
.runWith(Sink.foreach(println))
Await.result(done, Duration.Inf)
}
Akka Stream / Play
object MixedStream extends App {
implicit val sttpBackend = AsyncHttpClientFs2Backend[cats.effect.IO]()
def queryToStream(keyword: String): IO[Stream[IO,Message]] = {
val responseIO: IO[Response[Stream[IO,ByteBuffer]]] =
sttp.post(uri"http://localhost:3000?keyword=$keyword").response(asStream[Stream[IO, ByteBuffer]])
.readTimeout(Duration.Inf).send()
responseIO.map { response =>
response.body match {
case Right(stream) => stream.flatMap { bytes =>
val s = new String(bytes.array(), "UTF-8")
Stream(s).through(stringStreamParser[IO]).through(decoder[IO, Message])
}
case Left(_) => Stream(Message( "http error", "app"))
}
}
}
val streamsIO: IO[List[Stream[IO,Message]]] = List("Akka", "FS2").traverse(queryToStream)
val mergedIO: IO[Stream[IO, Message]] = streamsIO.map(streams => streams.reduceLeft(_.merge(_)))
val printStreamIO: IO[Stream[IO, Unit]] = mergedIO.map(merged => merged.map(println))
val printIO: IO[Unit] = printStreamIO.flatMap(_.compile.drain)
printIO.unsafeRunSync()
}
FS2 / Circe
object MixedStream extends App {
implicit val sttpBackend = AsyncHttpClientFs2Backend[cats.effect.IO]()
def queryToStream(keyword: String): IO[Stream[IO,Message]] = {
val responseIO: IO[Response[Stream[IO,ByteBuffer]]] =
sttp.post(uri"http://localhost:3000?keyword=$keyword").response(asStream[Stream[IO, ByteBuffer]])
.readTimeout(Duration.Inf).send()
responseIO.map { response =>
response.body match {
case Right(stream) => stream.flatMap { bytes =>
val s = new String(bytes.array(), "UTF-8")
Stream(s).through(stringStreamParser[IO]).through(decoder[IO, Message])
}
case Left(_) => Stream(Message( "http error", "app"))
}
}
}
val streamsIO: IO[List[Stream[IO,Message]]] = List("Akka", "FS2").traverse(queryToStream)
val mergedIO: IO[Stream[IO, Message]] = streamsIO.map(streams => streams.reduceLeft(_.merge(_)))
val printStreamIO: IO[Stream[IO, Unit]] = mergedIO.map(merged => merged.map(println))
val printIO: IO[Unit] = printStreamIO.flatMap(_.compile.drain)
printIO.unsafeRunSync()
}
FS2 / Circe
object MixedStream extends App {
implicit val sttpBackend = AsyncHttpClientFs2Backend[cats.effect.IO]()
def queryToStream(keyword: String): IO[Stream[IO,Message]] = {
val responseIO: IO[Response[Stream[IO,ByteBuffer]]] =
sttp.post(uri"http://localhost:3000?keyword=$keyword").response(asStream[Stream[IO, ByteBuffer]])
.readTimeout(Duration.Inf).send()
responseIO.map { response =>
response.body match {
case Right(stream) => stream.flatMap { bytes =>
val s = new String(bytes.array(), "UTF-8")
Stream(s).through(stringStreamParser[IO]).through(decoder[IO, Message])
}
case Left(_) => Stream(Message( "http error", "app"))
}
}
}
val streamsIO: IO[List[Stream[IO,Message]]] = List("Akka", "FS2").traverse(queryToStream)
val mergedIO: IO[Stream[IO, Message]] = streamsIO.map(streams => streams.reduceLeft(_.merge(_)))
val printStreamIO: IO[Stream[IO, Unit]] = mergedIO.map(merged => merged.map(println))
val printIO: IO[Unit] = printStreamIO.flatMap(_.compile.drain)
printIO.unsafeRunSync()
}
FS2 / Circe
object MixedStream extends App {
implicit val sttpBackend = AsyncHttpClientFs2Backend[cats.effect.IO]()
def queryToStream(keyword: String): IO[Stream[IO,Message]] = {
val responseIO: IO[Response[Stream[IO,ByteBuffer]]] =
sttp.post(uri"http://localhost:3000?keyword=$keyword").response(asStream[Stream[IO, ByteBuffer]])
.readTimeout(Duration.Inf).send()
responseIO.map { response =>
response.body match {
case Right(stream) => stream.flatMap { bytes =>
val s = new String(bytes.array(), "UTF-8")
Stream(s).through(stringStreamParser[IO]).through(decoder[IO, Message])
}
case Left(_) => Stream(Message( "http error", "app"))
}
}
}
val streamsIO: IO[List[Stream[IO,Message]]] = List("Akka", "FS2").traverse(queryToStream)
val mergedIO: IO[Stream[IO, Message]] = streamsIO.map(streams => streams.reduceLeft(_.merge(_)))
val printStreamIO: IO[Stream[IO, Unit]] = mergedIO.map(merged => merged.map(println))
val printIO: IO[Unit] = printStreamIO.flatMap(_.compile.drain)
printIO.unsafeRunSync()
}
FS2 / Circe
object MixedStream extends App {
implicit val sttpBackend = AsyncHttpClientFs2Backend[cats.effect.IO]()
def queryToStream(keyword: String): IO[Stream[IO,Message]] = {
val responseIO: IO[Response[Stream[IO,ByteBuffer]]] =
sttp.post(uri"http://localhost:3000?keyword=$keyword").response(asStream[Stream[IO, ByteBuffer]])
.readTimeout(Duration.Inf).send()
responseIO.map { response =>
response.body match {
case Right(stream) => stream.flatMap { bytes =>
val s = new String(bytes.array(), "UTF-8")
Stream(s).through(stringStreamParser[IO]).through(decoder[IO, Message])
}
case Left(_) => Stream(Message( "http error", "app"))
}
}
}
val streamsIO: IO[List[Stream[IO,Message]]] = List("Akka", "FS2").traverse(queryToStream)
val mergedIO: IO[Stream[IO, Message]] = streamsIO.map(streams => streams.reduceLeft(_.merge(_)))
val printStreamIO: IO[Stream[IO, Unit]] = mergedIO.map(merged => merged.map(println))
val printIO: IO[Unit] = printStreamIO.flatMap(_.compile.drain)
printIO.unsafeRunSync()
}
FS2 / Circe
case class Country(code: String, name: String, pop: Int, gnp: Option[Double])
class Countries(tag: Tag) extends Table[Country](tag, "country") {
def code = column[String]("code", O.PrimaryKey)
def name = column[String]("name")
def pop= column[Int]("population")
def gnp= column[Double]("gnp")
def * = (code , name, pop, gnp.?) <> (Country.tupled, Country.unapply)
}
val countries = TableQuery[Countries]
def populationIn(range: Range): Future[Seq[Country]] = {
val query = for {
c <- countries if (c.pop > range.min && c.pop < range.max)
} yield c
db.run(query.result)
}
case class Country(code: String, name: String, pop: Int, gnp: Option[Double])
def populationIn(range: Range) =
sql"""
| select code, name, population, gnp
| from country
| where population > ${range.min}
| and population < ${range.max}
| """.query[Country]
val xa = Transactor.fromDriverManager[IO](
"org.postgresql.Driver", "jdbc:postgresql:world", "postgres", ""
)
sql"select name from country"
.query[String] // Query0[String]
.to[List] // ConnectionIO[List[String]]
.transact(xa) // IO[List[String]]
.unsafeRunSync // List[String]
.take(5) // List[String]
.foreach(println) // Unit
Conclusion :Points forts