라스칼라코딩단
(주)두물머리 CTO
스칼라 프로그래밍 2012~2019
akka, shapeless, doobie 등 기여
TW, GH, FB @guersam
함수형 프로그래밍 좋다는 얘기는 많이 듣는데
대체 실무에는 어떻게 쓰나요?
/done [업적]
/done_list
그리고 50년이 지났습니다
한 수준에서의 제약은
다른 수준에서의 자유와 힘으로 이어진다.
- Runar Bjarnason
val a = 3
(a, a) <-> (3, 3)val a = iter.next()
(a, a) <!-> (iter.next(), iter.next())val a = print("hi")
(a, a) <!-> (print("hi"), print("hi"))표현식과 참조를 서로 바꿔 써도
프로그램이 동일하게 동작하면
그렇지 않으면
// 업적
case class Achievement(teamId: TeamId, userId: UserId, text: String)
// 축하용 밈
case class Meme(uri: Uri)
// 축하축하
case class Congratulation(achievement: Achievement, text: String, meme: Option[Meme])
trait AchievementRegistry {
def registerAchievement(achievement: Achievement): Unit
def findAllAchievementsByTeam(teamId: TeamId): List[Achievement]
}
trait MemeFinder {
def findRandomMemeByKeyword(keyword: String): Meme
}데이터베이스?
비동기?
동시성?
import cats.effect.IO
trait AchievementRegistry {
def registerAchievement(achievement: Achievement): IO[Unit]
def findAllAchievementsByTeam(teamId: TeamId): IO[List[Achievement]]
}
trait MemeFinder {
def findRandomMemeByKeyword(keyword: String): IO[Meme]
}
Effect = Value
값(표현식) 중심 프로그래밍
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import cats.implicits._ // for >> operator
val ha = Future(print("h")) >> Future(print("a"))
// ha
ha >> ha
Future(print("h")) >> Future(print("a")) >> Future(print("h")) >> Future(print("a"))
// hahaimport cats.effect.IO
import cats.implicits._
val ha = IO(print("h")) >> IO(print("a"))
(ha >> ha).unsafeRunSync()
// haha
(IO(print("h")) >> IO(print("a")) >> IO(print("h")) >> IO(print("a"))).unsafeRunSync()
// hahaIO(1).map(_ + 10) <-> IO(11)case class Foo(bar: Int, baz: String)
(IO(42), IO("answer")).mapN(Foo) <-> IO(Foo(42, "answer"))def fetchEmpolyee(id: EmployeeId): IO[Employee]
def fetchBranch(id: BranchId): IO[Branch]
// ...
fetchEmpolyee("guersam").flatMap(e => fetchBranch(e.branchId)): IO[Branch]가능한 한 멀리, 런타임 직전까지
import cats.effect.IOApp
import cats.implicits._
object App extends IOApp {
val app = new AppF[IO]
def run(args: List[String]): IO[ExitCode] =
app.run.use(_ => IO.never).as(ExitCode.Success)
}monix.eval.Task
monix.eval.Coeval
scalaz.concurrent.Task
scalaz.effect.IO
scalaz.zio.IO
OptionT[IO, ?]
EitherT[IO, E, ?]
ActionT[IO, S, E, ?]
Kleisli[OptionT[IO, ?], A, ?]
trait AchievementRegistry[F[_]] {
def registerAchievement(achievement: Achievement): F[Unit]
def findAllAchievementsByTeam(teamId: TeamId): F[List[Achievement]]
}
trait MemeFinder[F[_]] {
def findRandomMemeByKeyword(keyword: String): F[Meme]
}몰라 나중에 정할래
Functor[F].map(fa: F[Int])(_.toString): F[String]Applicative[F].product(fa: F[A], fb: F[B]): F[(A, B)]def fetchEmpolyee(id: EmployeeId): F[Employee]
def fetchBranch(id: BranchId): F[Branch]
// ...
Monad[F].flatMap(fetchEmpolyee("guersam")) {
e => fetchBranch(e.branchId)
}: F[Branch]trait CongratulationService[F[_]] {
def congratulate(achievement: Achievement): F[Congratulation]
}
object CongratulationService {
def apply[F[_]: ApplicativeError[?[_], Throwable]](
achievementRegistry: AchievementRegistry[F],
memeFinder: MemeFinder[F]
) =
new CongratulationService[F] {
def congratulate(achievement: Achievement): F[Congratulation] = {
val registrationF: F[Unit] =
achievementRegistry.registerAchievement(achievement)
val memeF: F[Option[Meme]] =
memeFinder
.findRandomMemeByKeyword("congratulations")
.attempt.map(_.toOption)
(registrationF *> memeF).map { maybeMeme =>
Congratulation(achievement, "Congratulations!", maybeMeme)
}
}
}
}
class ApiRoutes[F[_]: Effect](
congratulationService: CongratulationService[F],
achievementRegistry: AchievementRegistry[F],
) extends Http4sDsl[F] {
// ...
private val registerAchievement = HttpRoutes.of[F] {
case req @ POST -> Root / "done" =>
req.decode[Achievement] { achievement =>
for {
congrats <- congratulationService.congratulate(achievement)
slackResp = SlackPresenter.renderCongratulation(congrats)
resp <- Ok(slackResp)
} yield resp
}
}
private val listTeamAchievements = HttpRoutes.of[F] {
case req @ POST -> Root / "done-list" =>
// ...
}
val routes: HttpRoutes[F] =
registerAchievement <+> listTeamAchievements
}
Request[F] => F[Response[F]]
object PostgresAchievementRegistry extends AchievementRegistry[ConnectionIO] {
def registerAchievement(achievement: Achievement): ConnectionIO[Unit] =
Statements
.registerAchievement(achievement.teamId, achievement.userId, achievement.text)
.run.void
def findAllAchievementsByTeam(teamId: TeamId): ConnectionIO[List[Achievement]] =
Statements.findAchievementsByTeam(teamId).to[List]
object Statements {
def registerAchievement(teamId: TeamId, userId: UserId, achievement: String): Update0 =
sql"""INSERT INTO achievements (team_id, user_id, text)
VALUES (${teamId}, ${userId}, ${achievement})
""".update
def findAchievementsByTeam(teamId: TeamId): Query0[Achievement] =
sql"""SELECT team_id, user_id, text
FROM achievements
WHERE team_id = ${teamId}
ORDER BY id DESC
""".query
}
// ...
}
ConnectionIO ~> F
trait AchievementRegistryLaws[F[_]] {
def algebra: AchievementRegistry[F]
implicit def M: Monad[F]
def registerFindAllComposition(a: Achievement) =
(
algebra.registerAchievement(a) >>
algebra.findAllAchievementsByTeam(a.teamId).map(_.headOption)
) <->
M.pure(Some(a))
}
trait AchievementRegistryTests[F[_]] extends Laws {
def laws: AchievementRegistryLaws[F]
def algebra(implicit
arbAchievement: Arbitrary[Achievement],
eqFOptAchievement: Eq[F[Option[Achievement]]]) =
new SimpleRuleSet(
name = "Achievements",
"register and findAll compose" -> forAll(laws.registerFindAllComposition _)
)
}class InMemoryAchievementRegistryTest
extends FunSuite
with Discipline
with ArbitraryInstances
with TestInstances {
implicit val context = TestContext()
val registry = InMemoryAchievementRegistry.make[IO].unsafeRunSync
checkAll(
"InMemoryAchievementRegistry",
AchievementRegistryTests(registry).algebra
)
}class PostgresAchievementRegistryTest
extends FunSuite
with Discipline
with ArbitraryInstances
with ConnectionIOInstances[IO]
with TestInstances {
implicit val context = TestContext()
implicit val M: Monad[ConnectionIO] = Async[ConnectionIO]
val transactor = TestTransactor.autoRollback
checkAll(
"PostgresAchievementRegistry",
AchievementRegistryTests(PostgresAchievementRegistry).algebra
)
}
함수형 프로그래밍
스칼라 기초
웹 브라우저에서 스칼라 배우기
스칼라와 함수형 프로그래밍
함수형 프로그래밍과 이펙트
함수형 스칼라와 도메인 모델링
함수형 HTTP, JDBC
테스팅