読書会(Javaによる関数型プログラミング)第3回議事録

[ 戻る ]


===============================================================
 Java読書会BOF「Javaによる関数型プログラミング」を読む会 第3回
===============================================================

.. csv-table:: 開催概要

   "日時", "2016年11月12日 10:00 - 17:00"
   "場所", "川崎市教育文化会館 第3会議室"
   "出席者(敬称略)", "高橋(徹)、高橋(智)、青山、今井、岩室、遠藤、門脇、平山、吉本、川内"

本日は、p.132「6.1.2 スレッドセーフにする」から

6.1.2 スレッドセーフにする
==========================

6.1.3 間接処理を1レベル追加する
===============================

* pp.133-134 例6-4,例6-5 Holderクラスは本当にスレッドセーフか
  
  * Javaのメモリモデルでは、finalフィールドであれば、
    インスタンス生成時のフィールド初期化の結果が、他のスレッドから見える
    ことが保証されているが、finalでないフィールドの場合は、保証されていない。
        
  * 例のHolderクラスのheavyフィールドは、finalではないため、
    インスタンスを生成したスレッドとは異なるスレッドからアクセスした場合、
    nullとなっている可能性があり、厳密にはスレッドセーフとは言えない。

6.2 遅延評価
============

* p.135 Javaの論理演算は短絡評価だが、それを遅延実行すると表現するのは、違和感がある。

6.2.1 先行評価で開始
====================

* pp.138-139 例6-7,6-8の実行時間は4秒、例6-9,6-10の実行時間は2秒とあるが根拠は?

  * 例6-6のsimulareTimeConsumingOp(2000)が2000ミリ秒を表わしていると考えられる。

6.2.2 遅延評価を設計
====================

* 本節では、メソッドのパラメータが、本体で実際に利用されるか否かによらず、
  全て評価されてからメソッドに渡されることによるパフォーマンスへの影響を
  問題にしているが、ロギングで出力前にログレベルをチェックするのが、
  これに該当する。

  * Java SE標準(java.util.logging)のLoggerに、ラムダ式をパラメータに
    取るメソッドが追加され、これを利用すれば、面倒なログレベルチェックが不要に。

  * log4j2も、パラメータにサプライヤーをとるメソッドが追加されている。
    log4j2は、Java 7から利用可能。


6.3 Streamの遅延処理を活用
==========================

6.3.1 中間処理と終端処理
========================

6.3.2 メソッド評価順
====================

* p.143 図6-2 図も本文の説明も、filter、map、findFirstの順でデータが
  受け渡しされるとなっているが、以下のように、逆順で考えた方が分かりやすい。

  * findFirstがmapにデータを要求し、

  * mapは、findFirstの要求を満たすため、filterにデータを要求し、

  * filterは、mapの要求を満たすため、streamにデータを要求する

6.3.3 「遅さ」を覗く
====================

* p.144 例6-13 慣れないうちは、stream/filter/mapまでで安心して、
  終端処理を漏らすというのも、ありそう。

* streamのパイプライン処理は、デバッガとの相性が悪そう。

* java.nio.fileのFiles.linesは、適切にfilterすれば、メモリ不足せずに、
  一度にファイル全体から必要なデータを抽出でき、便利。

6.4 無限の「遅い」コレクションを生成
====================================

6.4.1 勇気ある挑戦
==================

* p.145 サンプルコードは、2より大きい偶数であっても、
  素数か否かの判定を行っているが、さすがに効率が悪い。

  * (number % 2) != 0の場合のみ、streamを使って調べるようにすればよい。

6.4.2 星々に近づく
==================

* p.148 本文中に、例6-15のコードと矛盾した誤記あり。
  原書に誤りはなく、翻訳の誤り。

  * (誤) 5をシード値とした100個の素数を要求

  * (正) 100をシード値とした5個の素数を要求 

* p.148 本文中に以下の誤記あり。

  * (誤) primes()メソッドは、入力値から始まる総数の無限コレクションの…

  * (正) primes()メソッドは、入力値から始まる素数の無限コレクションの…

6.5 まとめ
==========

7章 再帰の最適化
================

* p.151 章冒頭の引用について「分割統治法は、デカルトに遡るんだ」との感想あり。

* p.151 メモ化は、原書のmemoizationの訳語。

7.1 末尾呼び出し最適化を使う
============================

7.1.1 最適化前の再帰
====================

7.1.2 末尾再帰に変換する
========================

7.1.3 TailCall関数型インタフェース
==================================

7.1.4 TailCallsコンビニエンスクラス
===================================

7.1.5 末尾再帰関数を使う
========================

* p.158 例7-9の実行結果が0となるのはなぜ?

  * オーバーフロー時に、どこかのタイミングで、0となるのではないか?

* p.158 本文中に以下の誤記あり。

  * (誤) 綺麗にに

  * (正) 綺麗に

7.1.6 再帰を綺麗にする
======================

7.1.7 算術オーバーフローを修正する
==================================

* ここまで複雑にするのであれば、forループでよいのでは?

  * 一般的なJavaプログラマが遭遇するツリーの操作程度なら、
    ここまでする必要がないことが多いが、関数型言語で大規模な数値演算を
    扱う場合には必要。

7.2 メモ化でスピードアップ
==========================

7.2.1 最適化問題
================

7.2.2 何の変哲もない再帰
========================

7.2.3 計算結果のメモ化
======================

7.3 まとめ
==========

8章 ラムダ式で合成
==================

8.1 関数合成の利用
==================

8.2 MapReduceの使用
===================

8.2.1 計算の準備
================

* p.174 例8-6 コード中のstockInfo変数は、どこで定義されているの?

  * ラムダ式のパラメータなので、どこで定義されているといことはない。
    stockInfoとなっているが、aでもbでも可。
    stockInfo変数の型がStockInfoであることは、
    isPricelLessThanメソッドの戻り値の型がPredicate<StockInfo>
    となっているところから決まる。

8.2.2 命令型スタイルからの脱出
==============================

* p.176 命令型のコードは再利用できないと切り捨てられているが、
  再利用可能な書き方もあるだろうとの意見あり。

* pp.175-176 例8-8,8-9のサンプルコードでは、500ドル以下の銘柄が
  1つもない場合の実行結果は、ダミーStockInfo("", ZERO)となる。

8.2.3 そして関数型へ
====================

* pp.176-177 例8-10のサンプルコードでは、500ドル以下の銘柄が
  1つもない場合は、Optional<T>のgetメソッドで、
  NoSuchElement例外が発生する。

8.3 並列化への飛躍
==================

* パラレルストリームを使った並列化のコストはほとんどないようだ。

* ここで採り上げられた例は、ネットワーク越しのリソースへの
  アクセスを伴うので、20件程度のデータ数でも、
  並列化によってレスポンスタイムが向上しているが、
  メモリに収まるデータの場合は、データ件数が数十万〜数百万の
  オーダーでなければ、レスポンスタイムへの効果はないらしい。

* パラレルストリームの並列度は、コントロール可能か?

  * ストリーム側で直接コントロールすることはできず、
    ベースにあるfork-joinフレームワークで決まる。
    fork-joinフレームワークの並列度は、システムプロパティ
    java.util.concurrent.ForkJoinPool.common.parallelism
    で指定可能。
    
8.4 まとめ
==========

9章 全てをまとめて
==================

9.1 関数型スタイルで成功するために実践すべきこと
================================================

9.1.1 宣言的により近く、命令型からより遠く
==========================================

* p.184 分かりやすいのは、ストリームのコードより
  forループのコードという意見あり。

9.1.2 不変性の尊重
==================

9.1.3 副作用の削減
==================

* pp.185-186 図9-1 javacやJITコンパイラが、実行順に依存しない
  処理を検知して、同時に実行するまでの最適化はしてくれないのでは?

  * 本文も、可能(…することができます)と書いているだけで、
    するとはは書いていない。


9.1.4 文より式を優先
====================

9.1.5 高階関数を利用して設計
============================

* p.187 「importsの行数も減り」ではなく、「importの行数…」では?

  * 原書どおりだが、翻訳時では「importの行数」としたほうがよい。

9.2 パフォーマンスの問題
========================

* 並列処理か逐次処理かの選択をストリームの利用者が行うのではなく、
  ランタイムがやってくれるようなAPIとした方がよかったのでは?

  * 並列処理か逐次処理かを利用者が制御できないと、
    並列処理に対応していないコードはストリーム処理できなくなる。

  * 並列処理にはオーバーヘッドもあるので、その代償を払っても
    メリットがあるかどうかは、利用者が判断する必要あり。

9.3 関数型スタイルを採用
========================

付録A 基本的な関数型インタフェース
==================================

* 各セクションにある「特殊なプリミティブ」は意味がわからない

  * 原書では、primitive type specializationとなっており、
    「プリミティブ特化型」の訳が適当。

* プリミテブ特化型の説明は、Int、Long、Double以外は「など」
  となっているが、他にどのようなものがあるのか?

  * java.util.functionパッケージのJavadocを探せばわかる。
    例えば、Supplierの場合は、Boolean版あり。

A.1 Consumer<T>
===============

A.2 Supplier<T>
===============

A.3 Predicate<T>
================

p.194 以下の誤記あり。

  * (誤) nagate()

  * (正) negate()

A.4 Function<T, R>
==================

付録B 構文の基礎
================

B.1 関数型インタフェースの定義
===============================

B.2 パラメータを持たないラムダ式の生成
======================================

B.3 パラメータ1つのラムダ式の生成
=================================

B.4 ラムダ式のパラメータ型を推論する
====================================

B.5 パラメータ1つのラムダ式では括弧を省略可能
=============================================

B.6 複数パラメータを持つラムダ式の生成
======================================

B.7 複数の型のパラメータを持つメソッドを呼び出す
================================================

B.8 ラムダ式を変数に格納
========================

B.9 複数行のラムダ式を生成
==========================

* p.199 著者は複数行にまたがるラムダ式が嫌いなようだが、
  サンプルコードのレベルなら、一概に悪いとは思わないとの意見あり。

  * JavaScriptでは、もっと長いものも普通に使う。

B.10 ラムダ式を返す
===================

B.11 ラムダ式からラムダ式を返す
===============================

* p.200 ラムダ式が(a -> b -> c)のように2段階になると
  難しい、分かりにくいとの意見あり。

B.12 クロージャにおける静的スコープ
===================================

* p.200 Javaのクロージャは再代入できないが、クロージャと言ってよいのか?

  * 再代入は、クロージャの定義で要求されない。

  * 再代入がクロージャの定義で要求されるなら、
    関数型言語には、クロージャがないことになる。

B.13 インスタンスメソッドのメソッド参照を渡す
=============================================

B.14 メソッド参照をstaticメソッドに渡す
=======================================

B.15 メソッド参照を他のインスタンスのメソッドに渡す
=================================================

B.16 複数の引数を取るメソッドの参照を渡す
=========================================

B.17 コンストラクタ参照を使う
=============================

B.18 関数合成
=============

付録C Web上のリソース
=====================

* p.204 「Lambda: A Peek under the Hood」(Brian Goetz, JavaOne 2013)
  は必見との意見あり。


訳者あとがき
============

* 今回で「Javaによる関数型プログラミング」は終了

* 次回の課題図書の投票は、11/27(日) 23:59まで。


[ 戻る ]