70
1 Scala technology supporting the Dwango account system Seitaro Yuki / @pab_tech / DWANGO Co., Ltd. Scala / @pab_tech /

ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

Embed Size (px)

Citation preview

Page 1: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

1

Scala technology supportingthe Dwango account system

Seitaro Yuki / @pab_tech / DWANGO Co., Ltd.

ドワンゴアカウントシステムを支えるScala技術結城清太郎 / @pab_tech / 株式会社ドワンゴ

Page 2: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

2

OverviewDwango is often named as a leading company to adoptScala among the enterprises in Japan.The account system is known to be largest Scala projectin our company, responsible for registering niconico usersand authenticating them.In this session we will introduce a variety of Scalaprogramming techniques that are used in the accountsystem

ドワンゴのアカウントシステムで使われているScalaプログラミング技術について解説します

Page 3: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

3

ContentsWeb applications by continuation monadsDependency injection by Minimal Cake PatternNew transaction monad using subtyping

継続モナド、Minimal Cake Pattern、トランザクションモナドについて解説します

Page 4: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

4

Web applications by continuation monads

継続モナドによるWebアプリケーション

Page 5: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

5

Technical problems:How to construct web components

Web application needs to be decomposed to requestprocessing, response processing, filtering processing, etc.Each component needs to be easy to understand and testWe want a simple and powerful component technology

課題: Webアプリケーションの部品化

Page 6: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

6

Simple and powerful component technology:Monads

We will explain how to build a web application usingcontinuation monads

継続モナドを使ったWebアプリケーションの構成方法を説明します

Page 7: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

7

Similarity of a typical web application andcontinuation monads

まずは一般的なWebアプリケーションの構造と継続モナドの類似性から見ていきましょう

Page 8: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

8

Typical web application structureTypical web applications have nested structure likeonions

e.g. Java Servlet, Python WSGI, Ruby RackMiddleware performs the following operations

It processes a request, and passes the result to the nextmiddlewareIt modifies a response that came back, and returns theresult to the before middlewareOr, when an error occurs it returns the result withoutcalling the next middleware

典型的なWebアプリケーションの構造

Page 9: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

9

Typical web application structureWeb application

Web Browser Web Server Filter A Filter B Main Processing

request

request

request

request

response

response

response

response

典型的なWebアプリケーションの構造

Page 10: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

10

Authentication middlewareLet's consider authentication middlewareWe assume that users logined already somewhereAuthentication middleware retrieves a session ID fromcookies, confirms using a storage such as Redis, passesthe result to the next middlewareOr, if there is no session ID or the session is invalid,authentication middleware redirects to a login form

認証処理をおこなうミドルウェアについて考えてみる

Page 11: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

11

Authentication middlewareWeb Browser Web Server Authentication Filter Main Processing

request

request

alt ["Authentication is successful"]

request

request

response

response

response

["Authentication is failed"]redirect

response

認証処理のシークエンス図

Page 12: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

12

Code this authenticationmiddleware using continuation

monad

では、この認証処理を継続モナドを使って書いてみましょう

Page 13: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

13

What is continuation?Continuation represents the rest of thecomputation at a given point in theprocess's execution

Continuation from A

Continuation from B

B

R

Caller

A

Caller

プログラムの実行のある時点において評価されていない残りのプログラムを継続と呼ぶ

Page 14: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

14

Continuation-passing style (CPS)CPS is a style of programming in which control is passed acontinuation explicitly

Caller

Caller

A

A

B

B

R

R

pass a continuation after A and execute

pass a continuation after B and execute

pass a continuation after R and execute

execute the continuation

execute the continuation

execute the continuation

継続を明示的に受け渡しするプログラミングスタイルを継続渡しスタイルと呼ぶ

Page 15: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

15

CPS and web applications are similarWeb application seems to be able to be written in CPSFurther if continuation is monad, it becomes a simple andpowerful component

CPSとWebアプリケーションは似ている継続がモナドならさらによい

Page 16: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

16

Continuation monad

A is a type of value at a given point, R is a final result typeA => R represents a continuation(A => R) => R represents a function that take acontinuationCont[R, A] is a constructor that creates a monad from afunction that take a continuation継続モナドは、継続を受け取る関数を受け取って、モナドを作る

case class Cont[R, A](run: (A => R) => R) { def map[B](f: A => B): Cont[R, B] = Cont(k => run(a => k(f(a)))) def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = Cont(k => run(a => f(a).run(k))) }

Page 17: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

17

Let's make web application usingcontinuation monads

継続モナドを使ってWebアプリケーションを作ってみよう

Page 18: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

18

Authentication of continuation monad

継続モナドによる認証処理

def readSessionIdFromCookie(request: Request): Option[SessionId] = ???

def readSessionFromRedis(sessionId: SessionId): Option[Session] = ???

def redirectLoginForm: Response = ???

def auth(request: Request): Cont[Response, Session] = Cont((k: Session => Response) => readSessionIdFromCookie(request) .flatMap(readSessionFromRedis) .map(k) .getOrElse(redirectLoginForm)

Page 19: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

19

Web application using for-expression

for式を使ってWebアプリケーションを組み立てる

def compress(request: Request): Cont[Response, Unit] = ???

def mainProcessing(request: Request, session: Session): Cont[Response,

def webApplication(request: Request): Cont[Response, Response] = for { session <- auth(request) _ <- compress(request) response <- mainProcessing(request, session) } yield response

// execution webApplication(request).run(identity) // => Response

Page 20: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

20

Let's look at Play Action compositionmethods for comparison

比較のためにPlay標準のAction合成方法を見てみよう

Page 21: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

21

Play Action composition methodsActionFunction

def invokeBlock

ActionBuilder ActionRefiner

ActionFilter ActionTransformerAction

The methods of Play Action composision is performed byfunctional composition

PlayのAction合成方法ActionFunctionの合成によっておこなわれる

Page 22: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

22

Actually continuation also appears in Play

block: P[A] => Future[Result] is continuationBut it was not a monad

実はPlayでも継続の概念が使われているしかしモナドではなかった

trait ActionFunction[-R[_], +P[_]] { def invokeBlock[A](request: R[A], block: P[A] => Future[Result]): Future}

Page 23: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

23

Problems of Play Action compositionFunctional composition of ActionFunction is less flexiblethan monadic compositionFor example, AuthenticatedBuilder returnsAuthenticatedRequestTherefore, AuthenticatedBuilder can be composed to onlyActionFunction receives AuthenticatedRequest

PlayのAction合成の問題点ActionFunctionの合成は柔軟性に欠ける

Page 24: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

24

Advantages of using a continuation monadContinuation monad is simple

It can be represented by 6 linesMonadic composition is more flexible rather thanfunctional compositionIt is possible to combine with other kinds of monad byusing monad transformers

The account system uses ContT[Future, Result, A]for error handling

継続モナドをWebアプリケーションの記述に使う利点

Page 25: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

25

Dependency Injection by MinimalCake Pattern

Minimal Cake PatternによるDependencyInjection

Page 26: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

26

Technical problems:Large-scale system development

The account system has 230,000 lines in only code ofScala

For comparison, Play has 90,000 lines, Slick has 30,000lines

How to modularizeHow to test

課題: 大規模システム開発23万行のScalaコードがある

Page 27: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

27

Let's solve these problems by thedependency injection

Dependency Injectionでこれらの課題を解決しよう

Page 28: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

28

DI technology that we have adoptedDI technology that we have adopted is defined as"Minimal Cake Pattern"We will explain the following contents

Comparison of various other DI technologies of ScalaWhat is difference from the existing Cake PatternAdvantages for large-scale system development

ここでは我々が採用している"Minimal CakePattern"というDI技術について解説をします

Page 29: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

29

Various methods of DI in ScalaDI Container such as GuiceCake patternReader monad

様々なScalaのDependency Injection方法

Page 30: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

30

DI Container such as GuiceAdvantages

Experience in JavaAlso adopted in Play

Dependencies in source code are eliminatedcompletely

Shortening of compile timeDisadvantages

DI container often causes a run-time error of objectcreationWe should learn about the DI container

GuiceなどのDIコンテナを使う方法の利点と欠点

Page 31: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

31

Cake patternAdvantages

Checking at compile timeFrameworks such as DI container are not required

DisadvantagesCake patterns would complicate a system as acomponent technologyIncrease boilerplates

Cakeパターンを使う方法の利点と欠点

Page 32: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

32

Reader monadAdvantages

Same as Cake PatternDisadvantages

It will be full of reader monads in everywhereIt is difficult to manage a large number ofdependenciesIt is sensitive to change such as IO monads in HaskellWe conclude that reader monads should not be usedfor DI

Readerモナドを使う方法の利点と欠点

Page 33: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

33

Which is good for large-scale systemdevelopment?

大規模システム開発ならどの方法がいいのか?

Page 34: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

34

Rethink of Cake PatternCake Pattern is a proposed component technique in"Scalable Component Abstractions" by Odersky's et al.But, there are some problems as a component technique

By using an abstract type, types are likely to becometoo complicatedCakes are likely to be tightly coupled to each other

We will consider Cake Pattern not as a componenttechnique

コンポーネント技術としてのCake Patternの問題点

Page 35: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

35

Mimimal Cake PatternDo not use advanced features such as abstract typesProvide only one field variableDo not nest classDefine the Cake Pattern with such features as "MimimalCake Pattern"

Mimimal Cake Patternとはコンポーネント技術ではないCake Patternである

Page 36: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

36

Example of not use DI

まずはDIを使わない例

// Interface trait Repository

// Implementation object RepositoryImpl extends Repository

// Client object Service { // ↓Here's the problem referring to the Implementation val repository: Repository = RepositoryImpl }

Page 37: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

37

Example of Mimimal Cake Pattern (Interface)

Add Uses trait to declare interfaceUses trait just provides one field variableDo not use abstract types and nested classes

Interfaceの例Usesトレイトを追加する

trait UsesRepository { val repository: Repository }

trait Repository

Page 38: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

38

Example of Mimimal Cake Pattern(Implementation)

Add MixIn trait to provide the implementation

実装の例MixInトレイトを追加する

trait MixInRepository extends UsesRepository { val repository = repositoryImpl }

object RepositoryImpl extends Repository

Page 39: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

39

Example of Mimimal Cake Pattern(Client)

Extend UsesRepository for using RepositoryUse MixInRepository in MixInService for providingService implementationWe can replace the implementation here

UsesとMixInを使ったServiceの例ServiceもMixInトレイトを提供する

trait Service extends UsesRepository

trait MixInService extends UsesService { val service = new Service with MixInRepository }

Page 40: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

40

Development style using Minimal CakePattern

Our development style is based on Minimal Cake PatternInterface, Uses trait, implementation, MixIn traits andtest form a development unitWe should write a comment for all methods in theinterface, including all error valuesTests should include all error cases in the interfaceOnly when there are all present, the code is ready to bereviewed

Mimimal Cake Patternによる開発スタイル

Page 41: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

41

Advantage of Minimal Cake PatternThe account system consists of 400 or more modules

We need to simplify cake pattern by discarding extrafeatures not related to DI

Static checking is desirable in large-scale systemIf we use DI container, it should be verified by startingthe system each time

DI has established our development styleWe want to re-evaluate Cake Pattern by limiting only to DI

Mimimal Cake Patternの利点の説明DIだけのCake Patternを再評価したい

Page 42: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

42

New transaction monad using subtyping

サブタイピングを使った新たなトランザクションモナド

Page 43: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

43

Technical problems:Abstraction of storage access

We got the power to abstract components by DIWe want to do even abstraction of storage access, such asMySQL and RedisHow to abstract transaction objects of the infrastructurelayer, such as DB sessionHow to compose transactionsWe want to facilitate a programming of Master/Slavestructure

技術的な課題: ストレージアクセスの抽象化

Page 44: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

44

We will introduce a new transaction monadto solve these problems

これらの問題を解決する新しいトランザクションモナドを紹介します

Page 45: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

45

Transaction monad: FujitaskFujitask enables abstraction of storage access andcomposition of transactionsFujitask treats transaction objects in the same way as areader monadFujitask determines whether the composed monad willquery Master or Slave by using subtyping

トランザクションモナド: Fujitask

Page 46: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

46

Common problems of the Master / Slavestructure

In programming for Master/Slave structure, often weshould explicitly specify whether each code will queryMaster or SlaveA run-time errors when send a query of update to SlaveIt is hard to notice that we send a query that should besent to Slave to Master because no error occursFujitask will solve the these problems

Master/Slave構成のよくある問題Fujitaskはこのような問題を解決します

Page 47: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

47

Basic parts of Fujitask

Fujitaskの基本部分

trait Task[-R, +A] { lhs => // To be implemented def execute(r: R)(implicit ec: ExecutionContext): Future[A]

def flatMap[S <: R, B](f: A => Task[S, B]): Task[S, B] = ???

def map[B](f: A => B): Task[R, B] = ???

def run[S <: R]()(implicit runner: TaskRunner[S]): Future[A] = ??? }

trait TaskRunner[R] { // To be implemented def run[A](task: Task[R, A]): Future[A] }

Page 48: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

48

How to use FujitaskWe'll explain how to use the Fujitask through theimplementation of ScalikeJDBC

ScalikeJDBC版のFujitaskの実装を通じてFujitaskの使い方を説明します

Page 49: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

49

Define types of transaction objects

Define kinds of transactionThese are assigned to a left type variable of Task[R, A]The important point is that ReadWriteTransaction inheritsReadTransaction

トランザクションオブジェクトを定義するReadWriteがReadを継承するのがポイント

trait Transaction

trait ReadTransaction extends Transaction

trait ReadWriteTransaction extends ReadTransaction

Page 50: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

50

Implement transaction objects ofScalikeJDBC

These classes inherit Transaction and wrap DBSession ofScalikeJDBC

ScalikeJDBCのトランザクションオブジェクトの型を定義する

abstract class ScalikeJDBCTransaction(val session: DBSession)

class ScalikeJDBCReadTransaction(session: DBSession) extends ScalikeJDBCTransaction(session) with ReadTransaction

class ScalikeJDBCReadWriteTransaction(session: DBSession) extends ScalikeJDBCTransaction(session) with ReadWriteTransaction

Page 51: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

51

Implement TaskRunner ofScalikeJDBC (1)

Define ask method to get the transaction objectDerived from the ask function of Reader monad

ScalikeJDBC版のTaskRunnerを実装するaskメソッドを定義する

package object scalikejdbc { def ask: Task[Transaction, DBSession] = new Task[Transaction, DBSession] { def execute(transaction: Transaction) (implicit ec: ExecutionContext): Future[DBSession] = Future.successful(transaction.asInstanceOf[ScalikeJDBCTransaction } }

Page 52: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

52

Implement TaskRunner ofScalikeJDBC (2)

Define TaskRunner for ReadTransaction

ReadTransaction用のTaskRunnerを定義する

package object scalikejdbc { implicit def readRunner[R >: ReadTransaction] : TaskRunner[R] = new TaskRunner[R] { def run[A](task: Task[R, A]): Future[A] = { val session = DB.readOnlySession() val future = task.execute(new ScalikeJDBCReadTransaction(session)) future.onComplete(_ => session.close()) future } } }

Page 53: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

53

Implement TaskRunner ofScalikeJDBC (3)

Define TaskRunner for ReadWriteTransaction

ReadWriteTransaction用のTaskRunnerを定義する

package object scalikejdbc { implicit def readWriteRunner[R >: ReadWriteTransaction]: TaskRunner[ new TaskRunner[R] { def run[A](task: Task[R, A]): Future[A] = { DB.futureLocalTx(session => task.execute(new ScalikeJDBCReadWriteTransaction(session))) } } }

Page 54: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

54

Now, we can use Fujitask ofScalikeJDBC

TaskRunner can be implemented for another DB libraryand Redis client in the same wayNext, we create domain code using this Fujitask ofScalikeJDBC

これでScalikeJDBCのFujitaskを使うことができるようになりました

Page 55: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

55

Define an interface of Repository

Repositoryのインターフェースを定義する

trait UserRepository {

def create(name: String): Task[ReadWriteTransaction, User]

def read(id: Long): Task[ReadTransaction, Option[User]]

}

Page 56: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

56

Implement Repository using ScalikeJDBCFujitask

ScalikeJDBC版Fujitaskを使ったRepositoryの実装

object UserRepositoryImpl extends UserRepository {

def create(name: String): Task[ReadWriteTransaction, User] = ask.map { implicit session => val sql = sql"""insert into users (name) values ($name)""" val id = sql.updateAndReturnGeneratedKey.apply() User(id, name) }

def read(id: Long): Task[ReadTransaction, Option[User]] = ask.map { implicit session => val sql = sql"""select * from users where id = $id""" sql.map(rs => User(rs.long("id"), rs.string("name"))).single.apply() }

}

Page 57: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

57

Implement Service using Repository

Fujitaskで作ったRepositoryを使ってみる

trait MessageService extends UsesUserRepository with UsesMessageRepository {

def createByUserId(message: String, userId: Long): Future[Message] = { val task = for { userOpt <- userRepository.read(userId) user = userOpt.getOrElse( throw new IllegalArgumentException("User Not Found")) message <- messageRepository.create(message, user.name) } yield message

task.run() } }

Page 58: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

58

How Fujitask worksTransactions are combined by for-expressionuserRepository.read has ReadTransaction andmessageRepository.create has ReadWritetransaction,then the combined monad has ReadWritetransaction bysubtypingAnd, when task.run is invoked, an appropriateTaskRunner is selected by implicit parameter mechanism

Fujitaskはどのように動作するか

Page 59: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

59

The advantage of FujitaskStorage access can be abstracted by FujitaskWe could compose transactions by FujitaskMaster/Slave structure Programming is made easier

Fujitaskの利点

Page 60: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

60

Sharding by Fujitask

Use ShardedTask having a shard key instead of the Task ifwe want shardingType of shard key is checked statically by ShardedTaskAlso, the value of the shard key is checked at run timewhether the same in flatMap

Fujitaskによるシャーディング

abstract class ShardedTask[Key, -R, +A](val shardKey: Key)

Page 61: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

61

Conclusion

総括

Page 62: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

62

Let's consider the commonalitiesamong these techniques

最後に今回説明した技術の共通点を考えたい

Page 63: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

63

SimpleContinuation monad can be represented by 6 linesMinimal Cake Pattern is the minimized Cake Pattern for DIEven though Fujitask has a variety of storage accessfunctions, it can be written in about 30 lines

今回我々が紹介した技術はどれもシンプルである

Page 64: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

64

Use new features introduced in ScalaContinuation monad can be briefly composed by Scalamonads syntax

Also, It can be combined with other monads in monadtransformers by higher kinded types

Minimal Cake Pattern uses a mixin of traitsFujitask uses variances and ad hoc polymorphism byimplicit parameter

どれもScalaで新しく導入された機能を使っている

Page 65: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

65

Without frameworksWeb components are implemented by web framework inother languages

We have proposed a method to use continuationmonad

Dependency Injection has been implemented by DIcontainer in Java

We can use cake pattern instead of DI containerWe would have to use AOP If we try to implement thefunctions of the Fujitask in other languages

我々の手法ではフレームワークは必要ではない

Page 66: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

66

The Dwango account system is supported bythe advanced features of Scala

Thanks to the advanced features of Scala, our systembecame more simple, more understandable and moreconvenient

ドワンゴアカウントシステムはScalaの高度な言語機能に支えられている

Page 67: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

67

So, we want to conclude this session that ...

よって、このセッションをこう締め括りたい

Page 68: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

68

We will continue to use the advancedlanguages like Scala

我々はScalaのような進歩した言語を使っていきたい

Page 69: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

69

And, we hope that programming languagescontinue to make progress

そして、これからもプログラミング言語は進歩しつづけてほしい

Page 70: ScalaMatsuri 2016 ドワンゴアカウントシステムを支えるScala技術

70

Thank you for listening

ご清聴ありがとうございました