2015-12-30 『ふつうのHaskellプログラミング』 もくもく読書会(第8章) の振り返り
ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門
- 作者: 青木峰郎,山下伸夫
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2014/10/05
- メディア: オンデマンド (ペーパーバック)
- この商品を含むブログを見る
第8章 関数
値としての関数
関数束縛
- 関数名を変数とし、その引数及び処理の定義の関数を束縛すること
例:
square n = n * n
- 変数:square
- 関数:n = n * n
高階関数
- 関数を引数として受け取ったり、戻り値として返したりする関数
無名関数
- 変数を束縛しない関数
- 最初の「\」をラムダと言う
- 引数は複数設定可
\引数1 引数2 … -> 式
例:
\ x y -> x + y
無名関数のパターンマッチ
- 無名関数の引数でもパターンマッチが使える
- 無名関数で使えるパターンは一つだけ
関数合成
- 複数の関数を合わせて新しい関数を作ること
- 「.」を使って2つの関数を合成できる
numberOfLines :: String -> Int numberOfLines cs = length $ lines cs
は、関数合成を使うと下記に書き換えれる
numberOfLines :: String -> Int numberOfLines = length . lines
(.)関数の定義
(.) :: (b -> a) -> (a -> b) -> (a -> c)
- (f . g) にxを与えた
(f . g)x
と、f(g(x))
は同じことを意味する
コメント
numberOfLines cs = length $ lines cs
と
numberOfLines = length . lines
で、なぜ関数合成のほうは引数「cs」がなくていいのか理由がわからない
引数「cs」が不要なら
numberOfLines = length $ lines
でも、良さそうな気がするが、これだとコンパイルエラーになる
それにその前にでてきた関数束縛と無名関数の説明に従うと
numberOfLines = \() -> length . lines
となるのでは? ただ、これはコンパイルエラーになるし、そもそも引数なしで処理ができるはずないからおかしい。
↓
など考えていたが「部分適用」のとこで説明があった
部分適用
- 引数を一度にすべて渡さず、一部だけ渡しておくこと
- Haskellでは複数の引数を取る関数に1つだけ引数を渡してもエラーにはならない
- Haskellでは真の意味で複数の引数を取る関数はない
→ 1つの引数を取り他の引数を取る関数を返す高階関数とみなすことができる - セクション:部分適用を使い引数をひとつだけ渡した二項演算子
- 「-」は
(-1)
でも(- 1)
でも数値「-1」とみなされセクションをつくることはできない (subtract 1)
で1を引くセクションができる
コメント
関数合成の
numberOfLines :: String -> Int numberOfLines = length . lines
ではlength . lines
がセクションとなり引数を省略できるのはわかった。
関数束縛と無名関数で
numberOfLines = length . lines = \cs -> length . lines cs
と言うことなのだろう。
ただ、そうなると
numberOfLines = length $ lines
これがダメな理由がわからない。
length $ lines
もセクションと見れるのでは?
コメントより
($) :: (a -> b) -> a -> b (.) :: (b -> c) -> (a -> b) -> a -> c
型から見ると($)は、最初の引数が、関数を示す(a -> b)であり 最後の引数が、最初の引数の関数の引数の型であるa型であるような、二項演算子となる。
と言うことで($)の場合、lengthが(Foldable t => t a -> Int)だがlinesはa型を持つFoldableでないといけないが関数なので、引数の省略は不可。
> :t length length :: Foldable t => t a -> Int
ポイントフリースタイル
- ポイントフリースタイルとは、関数を関数で定義するコーディングスタイル
- 「point」は圏論の用語でHaskellでいう値を示す
- ポイントフリースタイルの場合、型宣言はつけるべき
→ 引数がないので、コードが非常にわかりづらくなる
練習問題
- 空白かどうかについて独自に関数を定義していたが
import Data.Char
でisSpace
と言う関数があった - 問題にでてくる文字列はここでは読み込んだファイルの複数行の文字列の意味ではなく一行の文字列のこと
※ 最初に文字列がファイルから読み込んだ文字列のことかと思い、下記のように書いていた
lstrip = unlines . (fmap (dropWhile isSpace)) . lines
でハマったのが
lstrip = unlines $ (fmap (dropWhile isSpace)) . lines
unlines の後の「$」に気づいていなくてエラーがでていたこと。 「$」演算子があるとポイントフリースタイルが使えないのは何故?
これも上記同様に($)の定義が下記であるため
($) :: (a -> b) -> a -> b
2015-12-29 『ふつうのHaskellプログラミング』 もくもく読書会(第7章) の振り返り
ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門
- 作者: 青木峰郎,山下伸夫
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2014/10/05
- メディア: オンデマンド (ペーパーバック)
- この商品を含むブログを見る
第7章 基本的な構文
コメント
1行コメント
-- コメント
-
は演算子にも使えるので--
の後に記号を入れるとコメントとして解釈されない- 通常は
--
の後の空白を入れる
square n = n * n -- nの2乗
ブロック形式のコメント
{-- 複数行のコメント --}
- コメントのネストも可能
{-- 関数をコメントアウト square n = n * n {-- nの2乗 --} --}
リテレイト形式
- リテレイト形式とはコメントの中にコードを描く形式
- 慣例的に拡張子は「.lhs」※ 「.hs」だとエラーになる
- 先頭に「>」をつけた行だけがコードになる
\begin{code}
から\end{code}
で囲む形式もある- 「>」か
\begin{code}
から\end{code}
のどちらかしかダメなもよう
下記はコンパイルは通る
> main = print $ square 5 関数 square は数値の2乗をあらわす > square :: Int -> Int > square n = (n * n)
下記のコードはコンパイルエラーを起こす
\begin{code} main = print $ square 5 \end{code} 関数 square は数値の2乗をあらわす > square :: Int -> Int > square n = (n * n)
実行すると…
>ghc literateNG.lhs [1 of 1] Compiling Main ( literateNG.lhs, literateNG.o ) literateNG.lhs:8:12: parse error on input ‘=’
レイアウト
ブレース構文
;
で式を区切り{}
で囲むことでコードブロックを表現できる
例えば、インデントを使ったdo式を
main = do cs <- getContents putStr cs
下記に書き換えることが可能
main = do { cs <- getContents; putStr cs; }
レイアウト
- 複数の式の桁をそろえることで式をまとめる方式をレイアウトもしくはオフサイドルールという
- 束ねたい式のインデントをそろえると、それらが1つのコードブロックに属すると解釈される
- ただし行頭には書けない ※ 最低1文字の空白が必要
do式は下記のように書ける
main = do cs <- getContents putStr cs
- インデントより右側なら前の行の続きとみなされる
main = do cs <- getContents putStr cs
そのため式の途中でインデントを揃えると別の式とみなされ粉ピルエラーになるもよう
main = do cs <- getContents putStr cs
※ 最低でも2行はインデント
コメント
- 自動でインデントするエディタなら改行したほうがインデントを揃えやすい
if式
- 式1と式2は式でありコードブロックでないことに注意
if 条件式 then 式1 else 式2
パターンマッチ
- 値のパターンの場合分けのこと
- パターン分けと同時に値の一部の変数を束縛することもできる
- 関数定義などで使われる
- すべてのパターンマッチに失敗したエラー
- ghcで
-W
のオプションをつけると実行時エラーが出る場合は警告が出る - パターンには具体的な値、タプル、リスト、データコンストラクタが使える
コメント
@(アズパターン:as-pattern)
- パターンで使っている変数名とは別にパターン自体に変数名をつける方法
変数名(パターン)
例:
str@(c:cs)
とある場合、値に[1,2,3]だと
- str = [1,2,3]
- c = 1
- cs = [2, 3]
ガード
- 変数の値の条件によって処理する定義を分ける方法
- ガードは
|
と=
の間のことをあらわす - 条件は複数設定でき、上から順に判定される
- 最後に「otherwise」で全ての条件にあわない場合の処理を行うことが可能
関数名 変数 | 条件1 = 定義1 | 条件2 = 定義2 | … | otherwise = 定義…
- パターンマッチと合わせて使うことも可能
case式
- 関数の引数以外でパターンマッチやガードを使う方法
- パターンやマッチに合った場合に実行される処理の定義は
=
ではなく->
の右に記述する - case式のogの後に
{}
で囲う記述も可能
case 式 of パターンA | ガード1 -> 定義A1 | ガード2 -> 定義A2 パターンB | ガード1 -> 定義B1 | ガード2 -> 定義B2 …
関数定義
- 関数は引数を使って何かする変数とみなすこと可能(関数束縛)
関数や変数の識別子
使える文字
- アルファベット(a~z、A~Z)
- 数字(0~9)
_
'
規約
- 1文字目は小文字か
_
※ ただし公式に_
は避けるようにかいてある '
は補助的な関数につけられることが多い- 予約語もあり、それは使えない
二項演算子
- 引数と引数の間に記号を書くことで表現できる関数
- 使える記号がいくつか用意され、独自に定義できる
- 予約語もあり、それは使えない
- 演算子をむやみに作るとコードが読みづらくなるので使う前に検討すべき
- 識別子を使った通常の2引数とる関数も「`」で囲うことで二項演算子として使用できる
- 逆に
()
で囲って二項演算子を前置形式で記述することも可能
優先順位と結合性
- 優先順位には0~9があり、0が最低、9が最高
- 左結合:
1+1+1
とある場合、(1+1)+1
と左側の式から結合していく演算子 - 右結合:
x=y=1
とある場合、x=(y=1)
と右側の式から結合していく演算子 - 非結合:
x>y>z
と結合することができない演算子 - 独自で定義した演算子にも優先順位と結合性を定義できる
独自で定義した演算子の優先順位と結合性の設定
結合性 優先順位 演算子
結合性には下記がある
infixl
:左結合infixr
:右結合infixx
:非結合
優先順位は省略可能(その場合デフォルトで9)
let式
- その中でだけ有効な変数の定義(束縛の定義)方法
- 式なのでそれ自体も値を持つ
- 変数名が重複した場合、関数とlet式ならlet式の変数、ネストしたlet式ならネストした変数と、内側の変数が外側の変数を覆い隠す(シャドウイング)
- パターンを使って束縛できる
- よく新しい変数を導入するために使われる
例:
f n = let x = n + 1 y = n + 2 z = n + 3 in x * y * z
where節
- 特定の定義内でだけ有効な変数の定義(束縛の定義)方法
- 節なので値を持たない
- 変数がガードにまたがって使える
- 変数がパターンにまたがって使えない
- パターンを使って束縛できる
- よく関数内で使うための関数を定義するのに使われる
例
expandTab width cs = concatMap translate cs where translate '\t' = replicate width ' ' translate c = [c]
練習問題
- 特に問題なし
2015-12-29 『Learning Reactive Programming with Java 8』 もくもく読書会(Chapter 5)の振り返り
Learning Reactive Programming With Java 8
- 作者: Nickolay Tsvetinov
- 出版社/メーカー: Packt Publishing
- 発売日: 2015/06/24
- メディア: ペーパーバック
- この商品を含むブログを見る
Chapter 5: Combinators, Conditionals, and Error Handling
Introduction
- 多くのプログラムでは複数の違うソースからのデータをまとめて使うことがある
- プログラムではこれらの違うソースからのデータ同士の依存関係は必要になることがある
- エラーに対して対応できるプログラムは耐性が強いものである
この章で学ぶことは下記のことである
Combining the Observable Operator
※ Combinatorsはマーブルダイアグラム見たほうがわかりやすいので本で確認しならがら読んだほうが良い
The zip operator
- 複数のOservableインスタンスを要素の順毎に組み合わせて処理をしていくメソッド
- 生成される組み合わせは2つのみだけではない
- ひとつのObservableインスタンスが全ての要素を発行してもObservableインスタンスのその順(Index)の要素が揃うまで待つ
- ひとつのObservableインスタンスが要素を全て使ったら処理は終わり
Observable<String> zip = Observable.zip( // Observable.from(Arrays.asList("Z", "I", "P", "P")), // Observable.interval(1L, TimeUnit.SECONDS), // (a, b) -> a + "/" + b); Helpers.subscribePrint(zip, "zip"); Thread.sleep(5000L);
実行結果
zip:Z/0 zip:I/1 zip:P/2 zip:P/3 zip ended!
Observable<String> zipWith = Observable .from(Arrays.asList("a", "b"))// .zipWith(Observable.interval(1L, TimeUnit.SECONDS), // (a, b) -> a + "/" + b); Helpers.subscribePrint(zipWith, "zip"); Thread.sleep(5000L);
実行結果
zip:a/0 zip:b/1 zip ended!
The combineLatest operator
- 複数のOservableインスタンスを要素を発行されるたびに組み合わせて処理をしていくメソッド
- zipと違い要素の順ではなく処理時点のすれぞれの要素を使う
- Oservableインスタンスの要素が揃う前に既に発行された要素は使われない
Observable<String> combineLatest = Observable .combineLatest( // Observable .from(Arrays.asList("A", "B", "C", "D")), // Observable.interval(1L, TimeUnit.SECONDS), // (a, b) -> a + "/" + b); Helpers.subscribePrint(combineLatest, "combineLatest"); Thread.sleep(5000L);
実行結果
combineLatest:D/0 combineLatest:D/1 combineLatest:D/2 combineLatest:D/3 combineLatest:D/4
上の例ではintervalメソッドのObservableインスタンスが最初の要素を発行する前にfromメソッドのObservableインスタンスは既にすべての要素を発行しているので、最後の要素は「D」になっている。
- 本のサンプルも長いが十分わかりやすい
T The merge operator
- それぞれのObservableインスタンスが発行する要素を発行した順に一つObservableインスタンスにまとめるメソッド
- Observableインスタンスの一つが途中でOnErrorになるとエラー処理をしmerge処理を終了する
- エラーが発生してもそれ以外のObservableインスタンスの処理は続けたい場合は
mergeDelayError
メソッドを使う
public static void main(String[] args) throws InterruptedException { Observable<String> alphabets = Observable .just("A", "B", "C", "D") .zipWith(Observable.interval(1L, TimeUnit.SECONDS), // MergeSample::onlyFirstArg); Observable<?> merge = Observable.merge(alphabets, Observable.interval(400L, TimeUnit.MILLISECONDS)); Helpers.subscribePrint(merge, "merge"); TimeUnit.SECONDS.sleep(5L); } static <T, R> T onlyFirstArg(T arg1, R arg2) { return arg1; }
実行結果
merge:0 merge:1 merge:A merge:2 merge:3 merge:4 merge:B merge:5 merge:6 merge:C merge:7 merge:8 merge:9 merge:D merge:10 merge:11
concat
- 引数のObservableインスタンスの要素を途中で混ぜることなく1つにまとめたObservableインスタンスを生成する
- [1,2,3]の要素と['a','b','c']の要素を持つObservableインスタンスがある場合、concatは[1,2,3,'a','b','c']の要素を持つObservableインスタンスを生成する
startWith
- 呼び出し元のObservableインスタンスに引数のObservableインスタンスの要素を追加したObservableインスタンスを生成する。
- よくある使われ方としてObservableインスタンスに初期値の要素を追加する場合に使われる
The conditional operators
- Observableインスタンスが他のObservableインスタンスが処理を行っているときにしか処理をしないなどのケースがある
- コンディショナルなメソッドは特定の条件で実行するしないを管理することができる
The amb operator
コメント
- このメソッドの使い道がわからない…
The takeUntil(), takeWhile(), skipUntil(), and skipWhile() conditional operators
The takeUntil
Observable<String> words = Observable .just("one", "way", "or", "another", "I'll", "learn", "Rxjava") .zipWith(Observable.interval(500L, TimeUnit.MILLISECONDS), (x, y) -> x); Observable<Long> interval = Observable.interval(2000L, TimeUnit.MILLISECONDS); Helpers.subscribePrint(words.takeUntil(interval), "takeUntil"); TimeUnit.SECONDS.sleep(5L);
実行結果
takeUntil:one takeUntil:way takeUntil:or takeUntil ended!
※ intervaleが2秒後に要素を発行するためwords.takeUntilの処理は終了する
takeWhile
- 引数の関数がtrueを開けす限り処理を実行する
Observable<String> words = Observable .just("one", "way", "or", "another", "I'll", "learn", "Rxjava") .zipWith(Observable.interval(500L, TimeUnit.MILLISECONDS), (x, y) -> x); Helpers.subscribePrint( words.takeWhile(word -> word.length() > 2), "takeWhile"); TimeUnit.SECONDS.sleep(5L);
実行結果
takeWhile:one takeWhile:way takeWhile ended!
skipUntil
- 引数のObservableインスタンスが最初の要素を発行するまで要素は発行しない
- 引数のObservableインスタンスが最初の要素を発行したら元のObservableインスタンスの要素がそれまで発行していた分を除いて発行される
Observable<String> words = Observable .just("one", "way", "or", "another", "I'll", "learn", "Rxjava") .zipWith(Observable.interval(500L, TimeUnit.MILLISECONDS), (x, y) -> x); Observable<Long> interval = Observable.interval(2000L, TimeUnit.MILLISECONDS); Helpers.subscribePrint(words.skipUntil(interval), "skipUntil"); TimeUnit.SECONDS.sleep(5L);
実行結果
skipUntil:another skipUntil:I'll skipUntil:learn skipUntil:Rxjava skipUntil ended!
※ 最初の要素からではなく、それまで処理していたと思われるのを除いて、結果が発行される
コメント
The defaultEmpty() operator
Handling errors
- エラーが発生するとObservableインスタンスは処理のチェインを終了する
- ただし、通常のプログラムのcatchブロックでの処理のように、エラー時に何かするロジックは実行可能
主なメソッドは3つに分類できる
- return*
- retry*
- resume*
The return and resume operators
onErrorReturn
- エラー発生時にOnErrorに行かずにonErrorReturnメソッドで処理を行い、Observableインスタスの処理を終了する
p.87のサンプル
誤:Observable<String> numbers = … ↓ 正:Observable<Integer> numbers = …
Observable<Integer> numbers = Observable .just("1", "2", "Three", "4") // .map(Integer::parseInt) // .onErrorReturn(e -> -1); Helpers.subscribePrint(numbers, "Error returned");
実行結果
Error returned:1 Error returned:2 Error returned:-1 Error returned ended!
onExceptionResumeNext
- 例外発生時にOnErrorに行かずにonExceptionResumeNextメソッドの引数のObservableインスタンスの要素を使ってそれ以降の処理を行う
- 対応するのはExceptionとそのサブクラスのみ
- ErrorはonErrorに通知される
Observable<Integer> numbers = Observable .just("1", "2", "Three", "4") // .map(Integer::parseInt) // .onExceptionResumeNext( Observable.just(50, 40, 30, 20, 10)); Helpers.subscribePrint(numbers, "Exception resume");
実行結果
Exception resume:1 Exception resume:2 Exception resume:50 Exception resume:40 Exception resume:30 Exception resume:20 Exception resume:10 Exception resume ended!
onErrorResumeNext
その他の副作用オペレータ(side effect operator)
- 要素の変更は行わない
主に次のものがある
- doOnNext
- doOnError
- doOnCompleted
- finallyDo
コメント
- finallyDo以外はOnNext、OnError、OnCompletedらの処理をに追加されるので煩雑になるので?(ロジックが一か所ではない)
The retry technique
- 例えば複数サーバが並列的に稼働している場合、ネットワークの問題などのエラーで全ての処理が行えなくなるより、別のサーバに再実行するなど重要な技術である
retry
- エラーが発生したらObservableインスタンスの処理のチェインを最初から行う
- retryメソッドに引数がない場合、永遠に再実行する
- retryメソッドの引数に数値を渡した場合、その回数だけ再実行する
retryWhen
- 引数にエラー情報を持ったObservableインスタンスが与えられ、それを使ったRetryロジックを持ったObservableインスタンスを返すことで最初から再実行するメソッド
- 返すObservableインスタンスが空だった場合、処理は終わる
retry(Func2)
- 第1引数が再実行回数
- 第2引数がエラー情報
- 戻り値に再実行ならtrue、OnErrorで終了ならfalseを返す
An Http Client Example
下記のJarが必要
- gson
- http-async-client
- rx-apache-http
- commons-logging
- http-client
- http-core
コメント
- 意図した挙動にならない際のデバッグが難しい
2015-12-28 『ふつうのHaskellプログラミング』 もくもく読書会(第6章)の振り返り
ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門
- 作者: 青木峰郎,山下伸夫
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2014/10/05
- メディア: オンデマンド (ペーパーバック)
- この商品を含むブログを見る
第6章 基本的な値
Bool型
- True
- False
関数
- not
- &&
- ||
数値
値 :: 型
例:
defaultWidth (16 :: Int)
- マイナス値はかっこ()をつけないとエラー
(-1)
整数型
- Int
- Integer
- 10進表記、8進表記、16進表記の3つのリテラルで表現できる
Int
- 比較的小さな範囲の符号付整数値
- 範囲は環境によって異なる(32ビットのGHCなら32ビットの符号付整数値)
Integer
- 表現範囲に制限のない整数値
浮動小数点数型
- Float
- Double
Float
単精度(32ビット)の浮動小数点数
Double
倍精度(64ビット)の浮動小数点数
Rational
- 有理数(二つの整数でa/b という分数で表せる数)を表す
import Data.Ratio
でインポートする ※ 本ではRatioモジュールをインポートとあるが今はData.Ratioに移動したもよう- Rational型を表す場合はカッコをつけないエラーになる
Data.Ratio> toRational 0.5 1 % 2 Data.Ratio> fromRational (1 % 2) 0.5
Data.Ratio> fromRational 1 % 2 <interactive>:56:1: No instance for (Fractional a0) arising from a use of ‘it’ The type variable ‘a0’ is ambiguous Note: there are several potential instances: instance Integral a => Fractional (Ratio a) -- Defined in ‘GHC.Real’ instance Fractional Double -- Defined in ‘GHC.Float’ instance Fractional Float -- Defined in ‘GHC.Float’ In the first argument of ‘print’, namely ‘it’ In a stmt of an interactive GHCi command: print it
Complex
- 複素数を表す
import Data.Complex
でインポートする ※ 本ではComplexモジュールをインポートとあるが今はData.Complexに移動したもよう
コメント
複素数(ふくそすう、complex number)は、実数 a, b と虚数単位 i を用いて a + bi と表せる数のことである。
とあるが、よくわからないのと使うことはないと思うので放置。(2乗するとマイナスになる値???)
→ 2乗するとマイナスになるのはiとのこと
→ 結論:『数学ガール』を読もう!
- 作者: 結城浩
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2007/06/27
- メディア: 単行本
- 購入: 58人 クリック: 1,055回
- この商品を含むブログ (974件) を見る
※ 色々でてるので、どれから読み始めればよいのかわからないが、とりあえず最初からか…
数値の演算
> (1.0 :: Double) + (0.3 :: Float) <interactive>:2:20: Couldn't match expected type ‘Double’ with actual type ‘Float’ In the second argument of ‘(+)’, namely ‘(0.3 :: Float)’ In the expression: (1.0 :: Double) + (0.3 :: Float) In an equation for ‘it’: it = (1.0 :: Double) + (0.3 :: Float)
> 1 + 0.3 1.3
加算(足し算)
式 | 備考 |
---|---|
x + y |
減算(引き算)
式 | 備考 |
---|---|
x - y | |
abtract y x | x - y となる |
乗算(掛け算)
式 | 備考 |
---|---|
x * y |
除算(割り算)
式 | 備考 |
---|---|
x / y | 結果は浮動小数点数となる |
x div y |
結果は割り算の整数部分。値は負の無限大向かってに丸められる。 |
x quot y |
結果は割り算の整数部分。値は0に向かって丸められる。 |
x mod y |
x div y のあまり |
x rem y |
x quot y のあまり |
> 1/3 0.3333333333333333
> 12`div` 5 2 > (-12) `div` 5 -3
> 12`quot` 5 2 > (-12) `quot` 5 -2
その他の演算
式 | 備考 |
---|---|
x ^ y | x の y乗 |
abs x | x の絶対値 |
odd x | x が奇数ならTrue |
even x | x が偶数ならTrue |
数値から別の数値への型変換
> :t toInteger toInteger :: Integral a => a -> Integer
> :t toInteger toInteger :: Integral a => a -> Integer
> :t fromIntegral fromIntegral :: (Integral a, Num b) => a -> b
丸めの関数
関数 | 備考 |
---|---|
ceiling x | 切り上げ。マイナス値の場合は0に近い方になる。 |
floor x | 切り捨て。マイナス値の場合は0に遠い方になる。 |
truncate x | 切り捨て。マイナス値の場合は0に近い方になる。 ※ 本だと「trancate」になっているので注意 |
round x | 四捨五入。マイナス値の場合は0に遠い方になる。 |
> :t ceiling ceiling :: (Integral b, RealFrac a) => a -> b > ceiling 1.3 2 > ceiling (-1.3) -1
> :t floor floor :: (Integral b, RealFrac a) => a -> b > floor 1.5 1 > floor (-1.5) -2
> :t truncate truncate :: (Integral b, RealFrac a) => a -> b > truncate 1.5 1 > truncate (-1.5) -1
> :t round round :: (Integral b, RealFrac a) => a -> b > round 1.5 2 > round 1.4 1 > round (-1.5) -2 > round (-1.4) -1
文字と文字列
タプル
例:
(3, "aaaa") :: (Int, String)
zip関数
- 2つのリストを1つのペア(タプル)のリストにする
- リストの長さが一致しない場合は短いほうのリストの長さになる
> :t zip zip :: [a] -> [b] -> [(a, b)] > zip [1,2,3,4,5] ['a', 'b', 'c'] [(1,'a'),(2,'b'),(3,'c')]
unzip関数
- ペアのリストを第1要素のリスト第2要素のリストの二つを持つリストを生成する
> :t unzip unzip :: [(a, b)] -> ([a], [b]) > unzip [(1,'a'),(2,'b'),(3,'c')] ([1,2,3],"abc")
※ "abc"は[Char]
リスト
- 同じ型の要素を格納するもの
- 一方向のみで前から後ろにたどれるが逆は不可
- リストは要素をカンマ区切りにし「[]」でくくる
:演算子
- リストを生成する演算子
要素 : リスト
- 第1引数に要素、第2引数にリストをいれる
- リストの最後は空のリスト
> :t (:) (:) :: a -> [a] -> [a] > 1 : 2 : 3 : [] [1,2,3]
リストの数列表記
..
を使って連番などを表現できる- 無限リストの場合は最後の要素を指定しない。例:
[1..]
> [1..7] [1,2,3,4,5,6,7] > ['a'..'e'] "abcde" > [1,3..11] [1,3,5,7,9,11]
null関数
- 空リストならTrue
> :t null null :: Foldable t => t a -> Bool > null [1,2,3] False > null [] True
リスト内包表記
- map関数やfilter関数をあらわせる
- 複数のリストとその要素もあつかえる
- 汎用すると読みづらくなるので複雑なリスト内包表記は書かないほうが良い
map関数を表す場合
[ 要素の変数を使った式や関数 | 要素の変数 <- リスト ]
例:
> [x*2 | x <- [1,2,3]] [2,4,6]
filter関数を表す場合
[ 要素の変数を使った式や関数 | 要素の変数 <- リスト, Boolの式 ]
例:
> [x*2 | x <- [1,2,3,4,5], (x `mod` 2) == 1] [2,6,10]
複数のリストを扱う場合
> [(x,y)|x <- [1,2,3], y <-['a', 'b']] [(1,'a'),(1,'b'),(2,'a'),(2,'b'),(3,'a'),(3,'b')]
catn.h
- ファイルを読み込み内容を行番号をつけて標準出力するサンプル
zipLineNumberの定義
- 引数に渡したリストから1から始まる数値とリストの要素のタプルにしたリストを生成
zipLineNumber :: [String] -> [(Int, String)] zipLineNumber xs = zip [1..] xs
formatの定義
- 行数と行を持つタプルを受け取り行数と行を合わせて1行にする
- 行数は6文字の数日と前空白の文字列にする
- 行数の文字列と行の間に空白をいれる
format :: (Int, String) -> String format (n, line) = rjust 6 (show n) ++ " " ++ line
show n
は数値を文字列にするrjust 数値 文字列
は" 文字列"という感じで引数の数値の文字数分の文字列にする関数
show関数
show :: (Show a) => a -> String
- Show型の値を文字列変換する
練習問題
- 答えに関しては特に問題はない
コメント
- 「基本的な値」の章で関数が操作の論理的な結合順序の問題が出てくる意図がわからない。
→ 第8章で関数を扱ってるので、そこで出題するのならわかるが?
2015-12-28 『ふつうのHaskellプログラミング』 もくもく読書会(第5章)の振り返り
ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門
- 作者: 青木峰郎,山下伸夫
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2014/10/05
- メディア: オンデマンド (ペーパーバック)
- この商品を含むブログを見る
第5章 遅延評価
評価
- プログラミングの世界では「実行」、「計算」とほぼ同じ意味
- ラテン語の「ex + value」より来ている
→ 「値をひねり出す」の意味 - Haskellの評価モデルは置き換えモデル(substitution model)で表現できる
- Haskellの評価は遅延評価(lazy evaluation)である
- 必要な式しか評価されない
最外簡約(outermost reduction)
Haskellの評価過程を段階的に表現する
square(1 + 3) ↓ (1 + 3) * (1 + 3) ↓ 4 * 4 ↓ 16
この評価の仕方を最外簡約(outermost reduction)と言う
逆にJavaなどのように
square(1 + 3) ↓ square(4) ↓ 4 * 4 ↓ 16
と評価されるのを最内簡約(innermost reduction)と言う
グラフ簡約(graph reduction)
- square関数の引数の
(1 + 3)
は1度だけ評価され、それ以降は結果が共有される - この式を共有しながら簡約する方法をグラフ簡約(graph reduction)と言う
if.hs
※ Windowsのコマンドプロンプトでghcでif.hsをコンパイル後に下記で実行しようとしたらエラーになる
> if コマンドの構文が誤っています。
if.exe
もしくは本のように.\if
とする必要がある
データ構造の遅延評価
- Haskellでは実際にプログラムで使われる要素だけ作成される
例1:
take 10 lines <= getContents
では、何万行もあるファイルを指定したとしても10行分しか文字列処理をしない
例2:
length[(1+1), (2+2), (3+3)]
では、リスト自体は全て評価されるが、各要素の式は評価されない
遅延評価で式の値が必要とされるタイミング
Haskellでは以下の2か所
- パターンマッチ
- 組み込み演算
myIf :: Bool -> a -> a -> a myIf True t e = t myIf False t e = e
では、myIf関数を使う際に第1引数は必ず評価されるが第2引数と第3引数は必要になるまで評価されない
参照透明性
- 同じ引数を渡せば毎回同じ結果が返る性質
- Haskellは参照透明である
遅延評価の利点
- 不要な計算を減らせる
- 無限の長さのリストが扱える
- インターフェイスが統一できる
無限リストの使い道
- 数学の問題でも解く状況下でない限り、無限リストは使いどころがないと思われがち
- 標準入力は論理的に無限リスト
- WEBアプリケーションでHTTPリクエストの無限リストを受け取ってHTTPレスポンスの無限リストを返す関数を考えるときれいにモデル化できる
インターフェイスを統一できる
- 様々な処理をリスト処理にできる
- 例えば巨大なツリー構造の全要素をリストにする場合
- getContentsアクションもリスト処理で対応できる
コメント
遅延評価の欠点
- 思った順番で操作を実行するのが難しい
- デバッグしにくい
デバッグしにくい理由
- 意味があるスタックトレースがとれない
- どのような順序で評価されるのか予想がつかない
コメント
2015-12-27 『JUnit実践入門』読書会(第4章)の振り返り
JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)
- 作者: 渡辺修司
- 出版社/メーカー: 技術評論社
- 発売日: 2012/11/21
- メディア: 単行本(ソフトカバー)
- 購入: 14人 クリック: 273回
- この商品を含むブログ (68件) を見る
第4章 アサーション
assertThatメソッド
- 値の比較検証のほとんどを行う
- 第1引数=実測値
- 第2引数=期待値との比較を行うMatcherオブジェクト
- 比較検証で値が一致しない場合、AssertionErrorが発生する
コメント
- assertThatかJUnit3までのassertXXXのほうが好みかは人によって分かれるもよう
→ assertThatだと途中でis
とか入れたくない
→ 旧メソッドの期待値を第1引数に入れるのが好きではない
などなど
failメソッド
- 無条件にテストを失敗させるメソッド
- テストを実装していないことを明らかにしたい場合に使われる
- あるブロックが実行されないことをテストしたい場合に使われる
Matcher API
- 何かの値に対して等価かを柔軟に比較するためのライブラリ
- ユニットテストのほとんどは等価かの比較
- 要素を含むか(hasItems)などの比較方法も提供し、一貫した書式でテストの意図を伝えやすい
→assertTrue(actual.contains(expected))
だとactualにexpectedを含むテストなのか、containsメソッドの意味なのかわかりにくい
→ hasItemメソッドは内部でcontains使ってる?
→ forループを使ってる
IsCollectionContaining.java
protected boolean matchesSafely( Iterable<? super T> collection, Description mismatchDescription) { boolean isPastFirst = false; for (Object item : collection) { if (this.elementMatcher.matches(item)) { return true; } if (isPastFirst) { mismatchDescription.appendText(", "); } this.elementMatcher.describeMismatch(item, mismatchDescription); isPastFirst = true; } return false;
- カスタムMatcherの作成が可能
- Matcher APIを使用するにはワイルドカードを使ったstaticインポートが楽
→ Eclipseではワイルドカードを使ったstaticインポートするのに設定の変更が必要
コメント
- assertEqualsメソッドだと、どのフィールドが一致しないかの詳細な情報が伝わらない?
→ カスタムのMatcher APIを使わなくてもassertEqualsメソッドだけでも実装次第でできるのでメリットを感じられない?
→ 単にassertEqualsメソッドでもフィールド単位で較すればいいだけでは? - オブジェクトにequalsメソッドをきちんと実装してテストを行うのは問題?
→ あとでフィールドが追加された際にテスト実装するときにオブジェクトのequalsメソッド修正時に実装漏れに気づく?
→ フィールドが追加された場合に、実装がプロダクションでもテストでも抜けてしまっった場合、どっちにしろテストは失敗しないので気づかれないのでは?
CoreMathers
is
nullValue
assertThat(astual, is(nullValue()));
not
assertThat(actual, is(not(0)));
notNullValue
assertThat(actual, is(notNullValue()));
これは下記と同じ
assertThat(actual, is(not(nullValue())));
sameInstance
- 値の比較ではなく同じインスタンスかの比較
- equalsではなく
==
の比較 - 基本データ型はボクシング変換されるので使うべきではない
instanceOf
- 実測値(actual)が比較するクラスと同じクラスもしくは比較クラスを継承したクラスならtrue
- 実測値(actual)がnulの場合は失敗になる
JUnitMathers
hasItem
- 実測値に期待値が含まれているか判定する
- 実測値は配列やIterableのオブジェクト
hasItems
カスタムMatcherの作成
日付の比較を行うカスタムMathcerの作成
次の要件を満たすカスタムMatcherを作る
- 年月日をそれおぞれintで指定して比較できる
- 時分秒は無視する
- 失敗したらyyyy/mm/dd表記で確認できるようにする
手順
独自Matcherクラスの生成
org.hamcrest.baseMathcerを継承する
- matchesメソッドとdescribeToメソッドを実装する
- matchesメソッドは比較検証を行う
describeToメソッドは失敗時の情報を作成する
ファクトリメソッドを作成する
Matcherを使いやすくするためstaticなファクトリメソッドを用意する
Matchesメソッドを実装する
boolean値で結果を返すmatchesメソッドを実装する
describeToメソッドを実装する
matchesメソッドがfalseの際に呼び出される
- 引数のDescriptionインスタンスに情報を追加する
- appendText:文字列を追加するメソッド
- appendValue:値を追加するメソッド。出力時に「"」で囲まれる
- 本のフォーマットと違い4.12だと実測値が
but: was
になっている - Descriptionに追加した情報は
Expected is
に表示される
テスト失敗時のメッセージ
Expected is "2000/01/01" but actual is "2015/12/27" but was <Sun Dec …略>
- 期待値にエラー情報含めて出すのに抵抗ある
→ メソッドが実績値、期待値含めたメッセージがつくれない点が微妙
→ 期待値にエラー情報含めて出すのはメッセージとして破たんしている - そもそも標準で用意されているメッセージ自体も実績値と期待値のインデントをそろえて出力したい(長文の文字列比較などやりやすいように)
2015-12-26 『Learning Reactive Programming with Java 8』 もくもく読書会(Chapter 4)の振り返り
Learning Reactive Programming With Java 8
- 作者: Nickolay Tsvetinov
- 出版社/メーカー: Packt Publishing
- 発売日: 2015/06/24
- メディア: ペーパーバック
- この商品を含むブログを見る
Chapter 4: Transforming, Filtering, and Accumulating Your Data
Introduction
この章で見ていくこと
- マーブルダイアグラムの紹介
- マッピングを使ったデータ整形
- データのフィルタリング
- scanオペレータを使った値の集計
Observable transformations
- 入ってくる値を何か別のものに変える高階関数をtransformationと呼ぶ
- Observableインスタンスから実行された高階関数は新しい別のObservableインスタンスを生成する
- mapオペレータは全ての要素をfunctionを通して別の何かにする
Transformations with the various flatMap operators
flatMap
- flatMapオペレータは受け取った値をfunctionを通してObservableインスタンスに変換する
- functionを通って結果として生成されたいくつものObservaleインスタンスは一つのObservableインスタンスにマージされる
- 結果のObservableインスタンスが持つ値は順序通りなっていない可能性があることに注意する
サンプルコード
本ではresourcesフォルダのlorem.txtとletters.txtの2ファイルを読み込み内容を出力するサンプルコードが提示されている。
listFolderメソッド
- 対象の2ファイルのDirectoryStreamから生成したObservableインスタンスを生成する
Subsrcriber#add(Subscription)
でSubscriberがunsubcribeされたらSubscritionを実行するようにしている- ここではfinlly句のようにSubscriptionをStream#closeを呼ぶために使っている
fromメソッド
- Pathからファイルを読み込むObservableインスタンスを生成する
- onNextメソッドに用見込んだ行を渡している
- ここも同様にSubscriptionはReader#closeを呼び出すfinally句の用に使われている
実行パート
Observable<String> fsObs = listFolder( Paths.get("src", "resources"), "{lorem.txt,letters.txt}") .flatMap(path -> from(path)); Helpers.subscribePrint(fsObs, "FS");
- listFolderのPathを要素に持つObservableインスタンスが生成される
- flatMapで要素のPathをfromメソッドに渡している
- Helpers#subscribePrintメソッドで受けとったObservableを起動し、onNextの値を標準出力している
※ Chpter 6で説明するようだが、Schedularを使ったObservableインスタンスの場合、出力結果が順不同になる
flatMapのオーバーロードメソッド
flatMap(Action1 onNext, Action1 onError, Action0 onCompleted)
flatMap(Func1<T, Observable<U>>, Func2<T, U, R>)
flatMap(Func1<T, Observable<U>>, Func2<T, U, R>)
Observable<Integer> flatMapped = Observable.just(5, 432) .flatMap(v -> Observable.range(v + 1, 3), (x, y) -> { System.out.println("x=" + x + ", y=" + y); return x + y; }); Helpers.subscribePrint(flatMapped, "flatMap");
※ 本のサンプルを若干修正
実行結果
x=5, y=6 flatMap:11 x=5, y=7 flatMap:12 x=5, y=8 flatMap:13 x=432, y=433 flatMap:865 x=432, y=434 flatMap:866 x=432, y=435 flatMap:867 flatMap ended!
- 5と432の要素を持つObservableインスタンスを生成
- 5の要素が渡されたときrangeメソッドで{6, 7, 8}の要素を持つObservableインスタンスを生成
2のObservableインスタンスの要素がx+yを実行される関数に渡されxにjustメソッドの値、yにranngeの値が渡される
このflatMapのオーバーロードはソースとなった要素にアクセスしないといけない場合に使いやすい(タプルなどに値を格納してそれを通じて値を渡さなくてよい)
flatMapIterable
Observable<?> flatMapped = Observable .just(Arrays.asList(2, 4), Arrays.asList("two", "four")) .flatMapIterable(v -> { System.out.println("v=" + v); return v; }); Helpers.subscribePrint(flatMapped, "flatMapIterable");
※ 本のサンプルを若干修正
実行結果
v=[2, 4] flatMapIterable:2 flatMapIterable:4 v=[two, four] flatMapIterable:two flatMapIterable:four flatMapIterable ended!
flatMap(v -> Observable.from(v))
と同じ
concatMap
A significant different between the flatMap and concatMap operator is that the flatMap operator uses the inner Observable instances in parallel, whereas the concatMap operator only subscribes to one of the Observable instances at a time.
- flatMapとの違いはflatMapが内部のObservableインスタンスを並列に使うのに対し、concatMapはObservableインスタンスを一つずつ使う
→ とあるが、flatMapも一つずつやってるようにしか見えないのだが?
Observable<Integer> flatMapped = Observable .from(Arrays.asList(5, 6, 7)).flatMap(v -> { try { Thread.sleep(1000L); return Observable.just(v * 2); } catch (InterruptedException e) { e.printStackTrace(); return Observable.empty(); } }); Helpers.subscribePrintWithThreadName(flatMapped, "flatMap"); Observable<Integer> concatMapped = Observable .from(Arrays.asList(5, 6, 7)).concatMap(v -> { try { Thread.sleep(1000L); return Observable.just(v * 2); } catch (InterruptedException e) { e.printStackTrace(); return Observable.empty(); } }); Helpers.subscribePrintWithThreadName(concatMapped, "concatMap");
実行結果
main:flatMap:10 main:flatMap:12 main:flatMap:14 flatMap ended! main:concatMap:10 main:concatMap:12 main:concatMap:14 concatMap ended!
と思っていたのだが、intervalから生成したObservableインスタンスの場合、結果が違っていた。
Observable<Object> concatMapped = Observable .interval(1000L, TimeUnit.MILLISECONDS) .concatMap(v -> Observable .interval(300L, TimeUnit.MILLISECONDS) .map(u -> "Observable <" + (v) + "> : " + (v + u))); Subscription subscription = Helpers .subscribePrint(concatMapped, "concatMap"); Thread.sleep(5000L); subscription.unsubscribe(); Observable<Object> flatMapped = Observable .interval(1000L, TimeUnit.MILLISECONDS) .flatMap(v -> Observable .interval(300L, TimeUnit.MILLISECONDS) .map(u -> "Observable <" + (v) + "> : " + (v + u))); Helpers.subscribePrint(flatMapped, "flatMap"); Thread.sleep(5000L);
実行結果
concatMap:Observable <0> : 0 concatMap:Observable <0> : 1 concatMap:Observable <0> : 2 concatMap:Observable <0> : 3 concatMap:Observable <0> : 4 concatMap:Observable <0> : 5 concatMap:Observable <0> : 6 concatMap:Observable <0> : 7 concatMap:Observable <0> : 8 concatMap:Observable <0> : 9 concatMap:Observable <0> : 10 concatMap:Observable <0> : 11 concatMap:Observable <0> : 12 flatMap:Observable <0> : 0 flatMap:Observable <0> : 1 flatMap:Observable <0> : 2 flatMap:Observable <0> : 3 flatMap:Observable <1> : 1 flatMap:Observable <0> : 4 flatMap:Observable <1> : 2 flatMap:Observable <0> : 5 flatMap:Observable <1> : 3 flatMap:Observable <0> : 6 flatMap:Observable <1> : 4 flatMap:Observable <2> : 2 flatMap:Observable <0> : 7 flatMap:Observable <1> : 5 flatMap:Observable <2> : 3 flatMap:Observable <0> : 8 flatMap:Observable <1> : 6 flatMap:Observable <2> : 4 flatMap:Observable <0> : 9 flatMap:Observable <1> : 7 flatMap:Observable <2> : 5 flatMap:Observable <3> : 3 flatMap:Observable <0> : 10 flatMap:Observable <1> : 8 flatMap:Observable <2> : 6 flatMap:Observable <3> : 4 flatMap:Observable <0> : 11 flatMap:Observable <1> : 9 flatMap:Observable <2> : 7 flatMap:Observable <3> : 5 flatMap:Observable <0> : 12 flatMap:Observable <1> : 10
このようにconcatMapの場合、最初の要素が終わらないと次の要素の処理を始めない。
switchMap
- flatMapとの違いはswitchMapはソースのObservableインスタンスから新しい要素が発行されるとその前の要素の処理をやめてしまう
Observable<Object> switchMapped = Observable .interval(1000L, TimeUnit.MILLISECONDS) .switchMap(v -> Observable .interval(300L, TimeUnit.MILLISECONDS) .map(u -> "Observable <" + (v) + "> : " + (v + u))); Subscription subscription = Helpers .subscribePrint(switchMapped, "switchMap"); Thread.sleep(3000L); subscription.unsubscribe(); Observable<Object> flatMapped = Observable .interval(1000L, TimeUnit.MILLISECONDS) .flatMap(v -> Observable .interval(300L, TimeUnit.MILLISECONDS) .map(u -> "Observable <" + (v) + "> : " + (v + u))); Helpers.subscribePrint(flatMapped, "flatMap"); Thread.sleep(3000L);
実行結果
switchMap:Observable <0> : 0 switchMap:Observable <0> : 1 switchMap:Observable <0> : 2 switchMap:Observable <1> : 1 switchMap:Observable <1> : 2 switchMap:Observable <1> : 3 flatMap:Observable <0> : 0 flatMap:Observable <0> : 1 flatMap:Observable <0> : 2 flatMap:Observable <0> : 3 flatMap:Observable <1> : 1 flatMap:Observable <0> : 4 flatMap:Observable <1> : 2 flatMap:Observable <0> : 5 flatMap:Observable <1> : 3
Grouping items
groupBy(Func1)
- groupByオペレータはキーでグループ化されたGroupedObservableインスタンスを生成する
- 引数のFunc1でキーを生成する
- GroupedObservableインスタンスを生成するとそのインスタンスは要素をバッファするため、気を付けないとメモリーリークが発生する
- onNextで発行される順番はソースと同じだがGroupedObservbleインスタンスはキー毎に異なる
The order the items are emitted in is the same, but they are emitted by different GroupedObsevable instances.
※ とあるが実行結果のonCompleted時のメッセージがキー毎なのでキー毎に違うインスタンスと言いたいのでは?
- ソースが終了した時にGroupedObservableも終了になる
List<String> albums = Arrays.asList( "The Piper at the Gates of Dawn", "A Sauceful of Secrets", "More", "Ummagumma", "Atom Heart Mother", "Meddle", "Obscured by Clouds", "The Dark Side of the Moon", "Wish You Were Here", "Animals", "The Wall"); Observable.from(albums) .groupBy(album -> album.split(" ").length) .subscribe(obs -> Helpers.subscribePrint(obs, obs.getKey() + " word(s)")); Thread.sleep(2000L);
実行結果
7 word(s):The Piper at the Gates of Dawn 4 word(s):A Sauceful of Secrets 1 word(s):More 1 word(s):Ummagumma 3 word(s):Atom Heart Mother 1 word(s):Meddle 3 word(s):Obscured by Clouds 6 word(s):The Dark Side of the Moon 4 word(s):Wish You Were Here 1 word(s):Animals 2 word(s):The Wall 1 word(s) ended! 2 word(s) ended! 3 word(s) ended! 4 word(s) ended! 6 word(s) ended! 7 word(s) ended!
groupBy(Func1, Func2)
- 第1引数でキーを作成
- 第2引数で値を生成
Additional useful transformation operators
cast
- 要素をキャストする
List<Number> list = Arrays.asList(1, 2, 3); Observable<Integer> observable = Observable.from(list) .cast(Integer.class);
timetamp
- 要素を格納したタイムスタンプ情報を持ったTimestampedインスタンスを持ったObservableインスタンスを生成する
- Timestamped#getTimestampMillisメソッドでLong値のタイムスタンプを取得できる
- Timestamped#getValueメソッドで値を取得できる
Observable<Timestamped<Integer>> observable = Observable .from(Arrays.asList(1, 2, 3)).timestamp(); Helpers.subscribePrint(observable, "timestamp"); observable.subscribe(item -> { long timestamp = item.getTimestampMillis(); int value = item.getValue(); System.out.println("timestamp = " + timestamp + ", value = " + value); });
timeinterval
Filtering data
filter
- ソースから対象がtrueになる要素しか持たないObserverableインスタンスを生成する
その他のfilter系のメソッド
Observable<Integer> observable = Observable.range(1, 5); // 複数要素を持つ可能性があるもの Helpers.subscribePrint(observable.takeLast(3), "takeLast"); Helpers.subscribePrint(observable.take(3), "take"); Helpers.subscribePrint(observable.skipLast(3), "skipLast"); Helpers.subscribePrint(observable.skip(3), "skip"); Helpers.subscribePrint(observable.takeLastBuffer(3), "takeLastBuffer"); // 要素はList<Integer> // 唯一の要素を取得するもの Helpers.subscribePrint(observable.last(), "last"); Helpers.subscribePrint(observable.first(), "first"); Helpers.subscribePrint( observable.takeFirst(value -> value % 2 == 0), "takeFirst"); Helpers.subscribePrint(observable.elementAt(3), "elementAt"); // デフォルト値ありのもの Helpers.subscribePrint(observable.lastOrDefault(99), "lastOrDefault:値あり"); Helpers.subscribePrint( Observable.empty().lastOrDefault(99), "lastOrDefault:値なし"); Helpers.subscribePrint(observable.firstOrDefault(99), "firstOrDefault:値あり"); Helpers.subscribePrint( Observable.empty().firstOrDefault(99), "firstOrDefault:値なし"); Helpers.subscribePrint( observable.elementAtOrDefault(3, 99), "elementAtOrDefault:値あり"); Helpers.subscribePrint( observable.elementAtOrDefault(10, 99), "elementAtOrDefault:値なし"); // 重複を除く Helpers.subscribePrint( Observable.from(Arrays.asList(1, 2, 3, 4, 1, 2, 3)) .distinct(), "distinct"); // 型のフィルター Helpers.subscribePrint(Observable .from(Arrays.asList("1", 2, 3L, 4.0, 1, "2", 3.0)) .ofType(Integer.class), "ofType");
実行結果
takeLast:3 takeLast:4 takeLast:5 takeLast ended! take:1 take:2 take:3 take ended! skipLast:1 skipLast:2 skipLast ended! skip:4 skip:5 skip ended! takeLastBuffer:[3, 4, 5] takeLastBuffer ended! last:5 last ended! first:1 first ended! takeFirst:2 takeFirst ended! elementAt:4 elementAt ended! lastOrDefault:値あり:5 lastOrDefault:値あり ended! lastOrDefault:値なし:99 lastOrDefault:値なし ended! firstOrDefault:値あり:1 firstOrDefault:値あり ended! firstOrDefault:値なし:99 firstOrDefault:値なし ended! elementAtOrDefault:値あり:4 elementAtOrDefault:値あり ended! elementAtOrDefault:値なし:99 elementAtOrDefault:値なし ended! distinct:1 distinct:2 distinct:3 distinct:4 distinct ended! ofType:2 ofType:1 ofType ended!
Accumulating data
scan
- scanメソッドは2引数を取る関数を設定し要素を使った集計処理を行う。
- Observableインスタンスは各要素ごとの結果を持ち、最後のものが最終的な集計結果
- 関数の第1引数は以前までの結果(最初は第1要素)
- 関数の第2引数はソースの要素
- このような関数はaccumulatorと言われる
reduce(Func2)
はscan(Func2).last
と同じ
下記は1~10までの足し算をするサンプル
Observable<Integer> observable = Observable.range(1, 10) .scan((p, v) -> { System.out.println("p=" + p + ", v=" + v); return p + v; }); Helpers.subscribePrint(observable, "scan");
実行結果
scan:1 p=1, v=2 scan:3 p=3, v=3 scan:6 p=6, v=4 scan:10 p=10, v=5 scan:15 p=15, v=6 scan:21 p=21, v=7 scan:28 p=28, v=8 scan:36 p=36, v=9 scan:45 p=45, v=10 scan:55 scan ended!
コメント
- マーブルダイアグラムは全部がわかりにくいわけではないが細かいことがわかりにくい(flatMapとconcatMapの違いとか)