読者です 読者をやめる 読者になる 読者になる

ITの隊長のブログ

ITの隊長のブログです。いや、まだ隊長と呼べるほどには至っていないけど、日々がんばります。CakePHPとPlayFrameworkを使って仕事しています。最近はAngular2をさわりはじめたお(^ω^ = ^ω^)

【Scala】PlayFrameworkのcheckbox.scala.htmlが読み解けなくてワロタ

Scala Play Framework Java

スポンサードリンク

photo by masayukig

Scalaが読めなくて辛い。

タイトルの通り、全然読めなくて、一日を無駄にしそうなので、ブログを書く。

前提

呼び元はこんな感じ。

@myCheckBoxes(field = formValue("testVal"),
    options(models.TestVal.getList),
    'name -> "testVal.value")

formValue("testVal")formValueは、PlayFrameworkのForm.classです。

んで、optionsは、models.TestValクラスから、Mapを取得しています。

複数の値をチェックボックス化するヘルパーを以前つくったんだけど、編集画面で登録済み、つまりcheckedをいれてほしいんだけど、それの原因がわからないので、今回のソースリーディングとなる。

読みとくぞ―!

ちなみに初期のcheckboxのテンプレートはこんな感じ

github.com

@**
* Generate an HTML checkbox group
*
* Example:
* {{{
* @inputCheckboxGroup(
*           contactForm("hobbies"),
*           options = Seq("S" -> "Surfing", "R" -> "Running", "B" -> "Biking","P" -> "Paddling"),
*           '_label -> "Hobbies",
*           '_error -> contactForm("hobbies").error.map(_.withMessage("select one or more hobbies")))
*
* }}}
*
* @param field The form field.
* @param options Sequence of options as pairs of value and HTML
* @param args Set of extra HTML attributes.
* @param handler The field constructor.
*@
@(field: play.api.data.Field, options: Seq[(String,String)], args: (Symbol,Any)*)(implicit handler: FieldConstructor, messages: play.api.i18n.Messages)

@input(field, args.map{ x => if(x._1 == '_label) '_name -> x._2 else x }:_*) { (id, name, value, htmlArgs) =>
  <span class="buttonset" id="@id">
    @defining(field.indexes.map( i => field("[%s]".format(i)).value ).flatten.toSet) { values =>
      @options.map { v =>
        <input type="checkbox" id="@(id)_@v._1" name="@{name + "[]"}" value="@v._1" @if(values.contains(v._1)){checked="checked"} @toHtmlArgs(htmlArgs)/>
        <label for="@(id)_@v._1">@v._2</label>
      }
    }
  </span>
}

んで、改良。。。というよりは必要な部分だけ残しました。

@(field: play.api.data.Field, options: Seq[(String,String)], args: (Symbol,Any)*)(implicit handler: FieldConstructor)

@input(field, args.map{ x => if(x._1 == '_label) '_name -> x._2 else x }:_*) { (id, name, value, htmlArgs) =>
    @defining(field.indexes.map( i => field("[%s]".format(i)).value ).flatten.toSet) { values =>
        @options.map { v =>
            <input type="hidden" id="@(id)_@v._1" name="@{name + "[]"}" @if(values.contains(v._1)){value="@v._1"} @toHtmlArgs(htmlArgs)/>
      }
    }
}

これからimplicitとか触らないといけないんだけど、とりあえず、これでいいと思ったんだ。

だがしかし。

Scalaがよくわからなくて中々前にすすまないお(^ω^ = ^ω^)おっおっおっ!

ログ出して見てみるけど、本当に意味がわからないんだ!特に@xxx()の後の、xxx => これ!!なんなの?(´・ω・`)

とりあえずチュートリアルを読もうと思いました。

www.atmarkit.co.jp

よんできたお(^ω^

どうやら無名関数で使う演算子のもよう

scala> val fun = (x:Int, y:Int) => x + y
fun: (Int, Int) => Int = <function2>

scala> fun(1,2)
res4: Int = 3

っつーことは最初のプログラムは

@input(field, args.map{ x => if(x._1 == '_label) '_name -> x._2 else x }:_*) { (id, name, value, htmlArgs) => @defining() }

んで@defining()には下記値が。

@defining(field.indexes.map( i => field("[%s]".format(i)).value ).flatten.toSet) { values =>  @options.map }

そして、@options.mapには。

@options.map { v => "<intput  ... >"}

ってな別け方でいいのかな?

@input?

playframeworkで用意されているテンプレートですね。ここを確認したらわかります。

github.com

@**
 * Prepare a generic HTML input.
 *@
@(field: play.api.data.Field, args: (Symbol, Any)* )(inputDef: (String, String, Option[String], Map[Symbol,Any]) => Html)(implicit handler: FieldConstructor, messages: play.api.i18n.Messages)

@id = @{ args.toMap.get('id).map(_.toString).getOrElse(field.id) }

@handler(
    FieldElements(
        id,
        field,
        inputDef(id, field.name, field.value, args.filter(arg => !arg._1.name.startsWith("_") && arg._1 != 'id).toMap),
        args.toMap,
        messages
    )
)

ぶっちゃけ、このソースは何を意味しているのか全然わかりません。(°ω°

なので、シカトでいきます。

@defining?

こういう意味らしい

@defining(変数にする値の算出式) { 変数名 => 【出力内容】 }

ということは、field.indexes.map( i => field("[%s]".format(i)).value ).flatten.toSetが算出式で、valuesが変数名ですか。

これ => field.indexes.map( i => field("[%s]".format(i)).value ).flatten.toSetも読み解いていきます。

field.indexes.map() ?

PlayFrameworkのドキュメントを見ると、受け取ったfieldの値からindex番号をListで取得するっぽい

Form.Field (playframework)

Return the indexes available for this field (for repeated fields ad List)

そこで続けてmapというわけですか。(いまいちよくわかっていない)

まずは試してみましょう。

scala> val numbers = List(1, 2, 3)
numbers: List[Int] = List(1, 2, 3)

scala> numbers.map( i => i )
res7: List[Int] = List(1, 2, 3)

(´・ω・)?

どゆこと?

なんか、元のソースがiだったからそれにしたんだけど、aだったらどうなるの?

scala> numbers.map( a => 3 )
res12: List[Int] = List(3, 3, 3)

どゆこと?(´°ω°`)?

謎が深まってまいりました プログラマを名乗って申し訳ございません。

どうして値がかわったの? そもそもListmapっておかしくね?

まだまだ初心者なので、mapのリファレンスを探しにいく。

・・・・どうやら私はものすごく勘違いをしているんじゃないか。。。。?

Scalaでリスト処理

まだ目的はつかめていないが、どうやらmap関数は引数に関数を渡すらしい。

なんかすごくわかりにくいけど、こういうことだ。

scala> val fun = (x:Int, y:Int) => x + y
fun: (Int, Int) => Int = <function2>

scala> val numbers = List(1, 2, 3)
numbers: List[Int] = List(1, 2, 3)

scala> numbers.map(x => fun(x, x))
res19: List[Int] = List(2, 4, 6)

なるほど。map(x => fun())x自体は引数で、=>の右はその変数が使える式ってことか。

わからなすぎてゾッとする。俺コレまでどうやって組んできたんだろう。。。?(°ω°;

ということは、jQueryとかでの$.each(array, function(i, ix) { // } )のような動き方をしているんだな。ただ、返り値がリストになるってことか。

うんうん。それなら、さっきの動作な納得がいく。

scala> numbers.map( a => 3 )
res12: List[Int] = List(3, 3, 3)

ただループしているだけだから、当たり前さね。

戻りますが、となるとfield.indexes.map( i => field("[%s]".format(i)).value ).flatten.toSetはなんとなく動作イメージがつきました。field("[%s]".format(i)).valueの結果がListで帰ってくるはず。

.flatten?

flattenについては、「要素がTraversableのとき、それを外して平坦化する。」らしい。。。。?(°∀° 意味不明でワロタ

よくわかりませんが、とりあえずListで返る値にこの関数を実行してみましょう。

scala> val numbers = List(1, 2, 3)
numbers: List[Int] = List(1, 2, 3)

scala> numbers.map(i => i + 1).flatten
<console>:9: error: No implicit view available from Int => scala.collection.GenTraversableOnce[B].
              numbers.map(i => i + 1).flatten

ワロタ。もうわからん。。。(´;ω;`)ブワッ

・・・・調べたんだけど、どゆことかまだわかっていない

scala> val numbers = List(List(1,2), List(2,3), List(3,5))
numbers: List[List[Int]] = List(List(1, 2), List(2, 3), List(3, 5))

scala> numbers.map(i => i).flatten
res32: List[Int] = List(1, 2, 2, 3, 3, 5)

まだよくわかっていないんだけど、、、Listの中にList。つまり、多次元二次元の場合、flattenを使うと、それを、多次元二次元 => 1次元に変換してくれるらしい。。。

ちなみに多次元と書いたけど、二次元しか試していない。

scala> val numbers = List(List(1,2), List(2,3), List(3,List(5,6),5))
numbers: List[List[Any]] = List(List(1, 2), List(2, 3), List(3, List(5, 6), 5))

scala> numbers.map(i => i).flatten
res35: List[Any] = List(1, 2, 2, 3, 3, List(5, 6), 5)

というわけで、二次元のみに修正します。

さて、次はこいつ

toSet ?

こいつは、「Setに変換する。」らしいです。わからないこと多すぎてワロタ(°∀°

Setってなんぞや。調べます。

qiita.com

重複をゆるさないコレクション型らしい。

scala> val numbers = List(List(1,2), List(2,3), List(3,5))
numbers: List[List[Int]] = List(List(1, 2), List(2, 3), List(3, 5))

scala> numbers.map(i => i).flatten.toSet
res39: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 5)

まじでした。Setしゅごい。最近Javaでもやったけど、、、なんだっけあの型・・・?TreeMapこれだな。

field.indexes.map( i => field("[%s]".format(i)).value ).flatten.toSet

まとめると、fieldから、indexesでkeyをListで取得して、map関数で、ループして、中のfield("[%s]".format(i)).valueは。。。多分keyからvalueを取得しているのでしょう。んでそれをList。そして、二次元になっていたら、一次元にして、最後にtoSetでSet型にすると。。。

長い!( ゚∀゚)・∵. グハッ!!

まとめ

っつーことで、@definingの結果が、valuesに渡りました。あー、なんとなくだけど読めるようになりました。

=>の意味がわかっただけどもよかった。。。( ´ー`)フゥー...

まだ完璧に読めたわけじゃないので、引き続き頑張る。