Scala + Liftフレームワーク その2
Scalaの概要 JVM上で動作するオブジェクト指向+関数型言語 JVMのスケーラビリティを適用できる Javaとの相互利用が可能 traitを利用した多重継承(Mix-In)が可能
Liftの概要(復習) フルスタックのWebアプリケーションフレームワーク 簡潔な関数型Webフレームワーク Snippetアプローチ Viewを中心としたアーキテクチャ
Liftでのチャットアプリケーションに機能追加 メッセージを削除する機能を追加する LiftのJqJsCmdsを利用してメッセージの処理を行 う
追加する定義 sealed trait ChatCmd object ChatCmd { ① sealed trait ChatCmd object ChatCmd { implicit def strToMsg(msg: String): ChatCmd = new AddMessage(Helpers.nextFuncName, msg, new Date) } final case class AddMessage(guid: String, msg: String, date: Date) extends ChatCmd final case class RemoveMessage(guid: String) extends ChatCmd ② ③ ① seald を指定することで、同一ファイル内からしか継承できなくなる。プラス、分岐漏れをコ ンパイル時に検知できるようになる。 ② implicit とは、暗黙変換の定義。必要なときに、自動的に変換処理を行ってくれる ③ caseクラスとは、コンストラクタの引数を自動的にフィールド定義してくれる。また、マッチ ングに使用でき、比較メソッド等を持つ。
チャットサーバーオブジェクトの定義 object ChatServer extends LiftActor with ListenerManager { private var messages: List[ChatCmd] = List("ようこそチャットルームへ") def createUpdate = messages override def lowPriority = { case s: String => messages ::= s ; updateListeners() case d: RemoveMessage => messages ::= d ; updateListeners() } ④ ⑤ ④ メソッドの内容がcase文だけであれば、引数は省略し、その引数をマッチングにそのまま使用 できる。Scalaでは省略できるものは基本的に省略するスタイルが多い。 ⑤ ChatCmdのリストであるmessagesに、String型の s を追加しようとしている。普通であればコ ンパイルエラーとなるはずだが、②の暗黙変換が利用され、AddMessageに変換されて追加され る。
Chat コンポーネントの定義 1 Listの型をChatCmdに変更。 class Chat extends CometActor with CometListener { private var msgs: List[ChatCmd] = Nil private var bindLine: NodeSeq = Nil def registerWith = ChatServer override def lowPriority = { case m: List[ChatCmd] => { val delta = m diff msgs msgs = m updateDeltas(delta) } Listの型をChatCmdに変更。 LowPriorityの中では、ローカルのリストと新しいリストの差異をとり、ローカルのリストを更新 後にupdateDeltasに差異を渡している。updateDeltasは後述。
Chat コンポーネントの定義 2-1 updataDeltasの引数としてwhatを定義。whatのfoldRidghtを呼び出す。 def updateDeltas(what: List[ChatCmd]) { partialUpdate(what.foldRight(Noop) { case (m: AddMessage , x) => x & AppendHtml("ul_dude", doLine(m)) & Hide(m.guid) & FadeIn(m.guid, TimeSpan(0),TimeSpan(500)) case (RemoveMessage(guid), x) => x & FadeOut(guid,TimeSpan(0),TimeSpan(500)) & After(TimeSpan(500),Replace(guid, NodeSeq.Empty)) }) } updataDeltasの引数としてwhatを定義。whatのfoldRidghtを呼び出す。 foldRightとは、二項関数を使い、引数からはじまりリストの全ての要素を右から左に結合する。 foldRight [B](z : B)(f : (A, B) => B) : B particalUpdateは、引数としてJsCmdを受け取る。つまり、foldRightの二項関数下で、リスト内部 を一つのJsCmdオブジェクトに変換を行う。
Chat コンポーネントの定義 2-2 Noopはjqueryの何もしないという空の処理。 def updateDeltas(what: List[ChatCmd]) { partialUpdate(what.foldRight(Noop) { case (m: AddMessage , x) => x & AppendHtml("ul_dude", doLine(m)) & Hide(m.guid) & FadeIn(m.guid, TimeSpan(0),TimeSpan(500)) case (RemoveMessage(guid), x) => x & FadeOut(guid,TimeSpan(0),TimeSpan(500)) & After(TimeSpan(500),Replace(guid, NodeSeq.Empty)) }) } ⑥ Noopはjqueryの何もしないという空の処理。 二項関数は、現在のリスト要素と、今までの累積値の2つの引数を受け取る。 関数内で、追加と削除処理をcaseクラスによるマッチングで分岐させ、それぞれのJsCmdオブジェ クトを作成し、&メソッドにより結合していく。 ⑥ doLineは追加するHtmlを作成する処理。後述。
Chat コンポーネントの定義 3 private def doLine(m: AddMessage): NodeSeq = bind("chat", addId(bindLine, m.guid), "msg" -> m.msg, “btn” -> SHtml.ajaxButton(“削除", () => { ChatServer ! RemoveMessage(m.guid) Noop})) doLineは、メッセージを表示するためのhtmlタグを作成する。 この例では、メッセージごとにそれを削除するボタンを出力。 ajaxButtonのラベルに「削除」を指定し、ボタン押下時の処理として、削除メッセージをbangメソ ッドでChatServerに送信、最後にNoopを指定(こうしないとJsCmdのオブジェクトとならないた め)
Index.htmlの中身 <lift:surround with="default" at="content"> <lift:comet type="Chat"> <ul id="ul_dude"> <chat:line> <li><chat:msg/> <chat:btn/></li> </chat:line> </ul> <lift:form> <chat:input/> <input type="submit" value="chat"/> </lift:form> </lift:comet> </lift:surround>
総評 SnippetとJqJsCmds で、完全に隠蔽されたAjax とjqueryの処理実装が可能! 相変わらず、scala自体が難しいので、あんまり楽 になった気はしない 実コーディング量はかなり少なくなるため、玄人な ら開発効率向上するかも?