namu.

1992生まれのプログラマが書くエンジニアブログ

クリーンアーキテクチャ REP, CCP, CRPを学ぶとSOLIDがアーキテクチャに対しても有効なことがわかる。

クリーンアーキテクチャ REP, CCP, CRPを学ぶとSOLIDがアーキテクチャに対しても有効なことがわかる。

クリーンアーキテクチャ第13章を読んでいる。

REP, CCP, CRPについて学んだのでメモ。

SOLIDの教えがそのままアーキテクチャに適用できると知り、記事にまとめる。

SOLIDの内、 SRP, ISPに非常によく似た原則が登場する

原文に近い表現はこちら

TL;DR

凝集性(CCP: 似たものは集め、別のものは離す)について

 - REP: 同じ変更理由のものは同じリリースで変更されるようなコンポーネントの切り方に(変更理由は1つだけ:SRP)
 - CRP: コンポーネントは切り離せない単位に限定する(不要な物に依存しない: ISP)

コンポーネント

以下、コンポーネントはデプロイの単位 だと思って欲しい。

クラスやモジュールをまとめたものである。

REP, CCP, CRP

これらはコンポーネントの分割や責務の切り分けに関する原則である。

凝集性について述べる以外はSOLIDの内、 SRP, ISPに非常によく似た原則である。

凝集性

コンポーネントの凝集性について語られている。

凝集性とは以下のような性質のことである。

似たものは集まり、別のものは離れる

再利用・リリース等価の原則(REP:The Reuse/Release Equivalence Principle)

コンポーネントのリリースに関する規則。

同じ変更理由によって変更されるクラスやモジュールは同じリリースにまとめられるべき。

つまり 変更対象のクラスやモジュールが同じコンポーネント、グループに属しているべき と説く。

閉鎖性共通の原則(CCP:The Common Closure Principle)

つまりは凝集性のこと。

冒頭で説明した凝集性 似たものは集め、別のものは離す を示している。

CCPでは、 同じ変更理由で変更されるクラスやモジュールは同じコンポーネントにあるべき と説く。

コンポーネント版、SOLIDのSRP。

単一責任の原則(SRP: Single Responsibility Principle) はクラスの変更理由は1つ(同じアクター<ユーザやプレースホルダー>) であるべき と説く。

全再利用の原則(CRP:The Common Reuse Principle)

ひとつのコンポーネントにまとめるクラスはどれも切り離せないものばかりにする(不要なものに依存しない)。

コンポーネント版、SOLIDのISP。

インタフェース分離の原則 (ISP: Interface Segregation Principle) は 使っていないメソッドを持つクラス(インタフェース)に依存しない(不要な物に依存しない) と説く。

REP, CCP, CRPを学ぶとSOLIDがアーキテクチャに対しても有効なことがわかる

凝集性(CCP: 似たものは集め、別のものは離す)について

 - REP: 同じ変更理由のものは同じリリースで変更されるようなコンポーネントの切り方に(変更理由は1つだけ:SRP)
 - CRP: コンポーネントは切り離せない単位に限定する(不要な物に依存しない: ISP)

凝集性についてSOLIDのSRP(CCP), ISP(CRP)を適用するとコンポーネント分割や責務の切り分けに有効であることがわかった。

SOLIDを理解すると2度美味しい。

以上

scala コレクションライブラリ foldLeft / foldRight

scala コレクションライブラリ foldLeft / foldRight

https://dwango.github.io/scala_text/collection.html#foldleft%EF%BC%9A%E5%B7%A6%E3%81%8B%E3%82%89%E3%81%AE%E7%95%B3%E3%81%BF%E8%BE%BC%E3%81%BF

foldLeft

foldLeft はリストの要素を左から順にfを使って「畳み込む」。

リストの要素に対して左側から関数をする、関数の結果と次の要素を取り出し関数を適用する、という風に再帰的に処理が繰り返される。

fold = 畳み込む という意味である。

codeで表すと以下のようになる

def foldLeft[B](z: B)(f: (B, A) ⇒ B): B

foldLeftはリストの左側からfを適用していく。zは初期値だ。

リストに加算を行う式は以下だ

scala> List(1, 2, 3).foldLeft(0)((x, y) => x + y)
res0: Int = 6

foldLeft が行う処理

List(1, 2, 3).foldLeft(0)((x, y) => x + y)

上記の式が計算する順序は以下だ。初期値を0としてListの先頭から加算を繰り返す。そしてリストの要素を一つの要素にたたみ込んで行く。

       +
      / \
     +   3
    / \
   +   2
  / \
 0   1

演習問題

foldLeftを用いて、Listの要素を反転させる次のシグニチャを持ったメソッドreverseを実装してみましょう:

def reverse[T](list: List[T]): List[T] = ???

コンスを使えばイケそう

scala> def reverse[T](list: List[T]): List[T] = {
     |   val rev: List[T] = Nil;
     |   list.foldLeft(rev)((r, x) => x :: r);
     | }
reverse: [T](list: List[T])List[T]

scala> List(1,2,3,4,5).reverse
res4: List[Int] = List(5, 4, 3, 2, 1)

なるほど。 1 :: Nil みたいにコレクション型を後置しないといけないんだね

foldRight

これはそのまま、右からの畳み込み。

codeで表現すると次になる。

def foldRight[B](z: B)(op: (A, B) ⇒ B): B

初期値とリストの右から要素を取り出し、関数を適用。その結果と1つ左の要素を取り出し関数を適用、という風に順番にたたみ込んで行く。

   +
  / \
 1   +   
    / \
   2   +   
      / \
     3   0

foldLeftが理解できているとわかりやすいね

練習問題1

Listの全ての要素を足し合わせるメソッドsumをfoldRightを用いて実装してみましょう。sumの宣言は次のようになります。なお、Listが空のときは0を返してみましょう。

def sum(list: List[Int]): Int = ???

自分の答え

これは簡単だね

scala> def sum(list: List[Int]): Int = {
     |   if (list.isEmpty) { return 0; }
     |   list.foldRight[Int](0)((x, y) => x+y);
     | }
sum: (list: List[Int])Int

scala> List(1,2,3,4,5).sum
res3: Int = 15

模範解答

def sum(list: List[Int]): Int = list.foldRight(0){(x, y) => x + y}

え、まじか。戻り値デフォルトで0なのね。これはfoldRightの仕様っぽいね

次の問題。

練習問題2

Listの全ての要素を掛け合わせるメソッドmulをfoldRightを用いて実装してみましょう。mulの宣言は次のようになります。なお、Listが空のときは1を返してみましょう。

def mul(list: List[Int]): Int = ???

自分の答え

これは模範解答通りだった。嬉しい。

scala> def mul(list: List[Int]): Int = list.foldRight(1)((x,y) => x*y);
mul: (list: List[Int])Int

scala> mul(List(1,2,3,4,5))
res4: Int = 120

練習問題3

mkStringを実装してみましょう。mkStringそのものを使ってはいけませんが、foldLeftやfoldRightなどのListに定義されている他のメソッドは自由に使って構いません。ListのAPIリファレンス を読めば必要なメソッドが載っています。実装するmkStringの宣言は

def mkString[T](list: List[T])(sep: String): String = ???

となります。残りの2つのバージョンのmkStringは実装しなくても構いません。

さて、急にレベル上がったな。

mkString はList内の要素を結合する関数だったはず。

自分の答え

あんまり綺麗な答えではないな。

scala> def mkString[T](list: List[T])(sep: String): String = {
     |   list.foldLeft("")((x, y) => { 
     |     if (x.isEmpty) {x + y.toString}
     |     else {x + sep + y.toString}
     |   });
     | }
mkString: [T](list: List[T])(sep: String)String

scala> mkString[Int](List(1,2,3))(",")
res13: String = 1,2,3

模範解答

確かに綺麗だけどまだmatch教えてくれてないやん!!!! ありかよ!!w

def mkString[T](list: List[T])(sep: String): String = list match {
  case Nil => ""
  case x::xs => xs.foldLeft(x.toString){(x, y) => x + sep + y}
}

2個目のcaseが気になった。

x::xs => xs.foldLeft(x.toString){(x, y) => x + sep + y}

x::xs は なんらかのlistが2つ連結したもの と見立てることができる。

そう考えると x = Nil, xs = 処理したいList と考えてもいい。

scala> Nil :: List(1,2,3)
res14: List[Any] = List(List(), 1, 2, 3)

こう見ると xs には処理対象としたい list が入ってくるのがわかりやすい。

ということで今回はここまで。

amazon アフィリエイトリンクをAPIで作る

amazon アフィリエイトリンクをAPIで作る

いい感じにまとまった記事がなかったので、ここにまとめる。

ちなみにwebとプログラミングの初歩がわかってればそんなむずくない。

その辺が綺麗にまとまってる記事が見当たらなかったので、書いてみる。

アフィリンクを自動生成したい

最近はamazonでアフィリエイトをやっている。

いちいち発行するのめんどくせーなーと思い、プログラムに任せたい。

ということでamazon が提供している商品検索API Product Advertising API を弄って遊んでいた。

これを使えばアフィリエイトリンクがAPI越しに取得できる。

Product Advertising APIの概要

amazon公式の記事が日本語で書いてあるので読んでほしい。 ざっくり読めばok

api越しにamazon の商品情報取得とアフィリンク生成できる ってことがわかれば一旦ok

https://images-na.ssl-images-amazon.com/images/G/09/associates/paapi/dg/index.html?CHAP_Intro_AAWS.html

めっちゃ親切なGetting Start

英語だけだけど、実は導入が書かれている。 chromeなら google翻訳プラグイン入れると全文日本語に翻訳してくれる。

もはや記事書く意味ないくらいわかりやすいので読んでほしい。

https://images-na.ssl-images-amazon.com/images/G/09/associates/paapi/dg/index.html?CHAP_Intro_AAWS.html

ここでやるべきことは3つある

- amazon アソシエイトの審査を通過すること
- amazon アソシエイトIDを取得すること
- amazon アソシエイト -> ツール -> Product Advertising API で承認キーを作ること

amazon アソシエイトの審査を通過すること

まずはAmazonアソシエイトの審査に通過する。

多分、この記事を読む人はすでに通過していると思うので読み飛ばしていい。

このブログでも通過できたから、おそらくブログ持ってる人の大半は通過できると思う。

多分、この記事において1番時間がかかり難しいのはこの審査を通す段階。

おおよそ結果が返ってくるまで1-2週間くらいかかった記憶がある。

急いでも仕方ないので待ちましょう。

https://affiliate.amazon.co.jp/home

ちなみに以下に触れる内容は審査落ちする。

何度でもリトライできるので諦めずに頑張ろう。

プログラム参加申請をお断りするサイト例 - amazonアソシエイト

amazon アソシエイトIDを取得すること

無事審査が通ればamazon アソシエイトのホームに入れる。

https://affiliate.amazon.co.jp/home

確かIDは自分で決めるはず。

amazon アソシエイトのホーム画面に行くと右上に「アソシエイトID」と書いてあるのでわかりやすい

amazon アソシエイト -> ツール -> Product Advertising API で承認キーを作ること

そのままだ。amazonアソシエイトのホームに行き、ツールバーの「ツール->Product Advertising API」で認証キーを作る。

api keyとsercret keyが生成されるので、どこかにコピってメモる。

これはあとから使うので必ず押さえておこう。

amazon Product Advertising API Scratchpadが異常に便利

ここまでやれば、準備完了だ。

じゃ、早速APIを叩いて遊ぼう。

ありがたいことに何の環境設定もせずにAPIを叩かせてくれる環境をamazonが用意してくれている。

それがこちら。amazon Product Advertising API Scratchpadだ

Product Advertising API Scratchpad (beta)

これを使えば一発でAPIを叩ける。

ItemSearch APIと ItemLookup

API の違い

一言で言うと、複数検索なのか単体検索なのかの違いだ。

複数検索: ItemSearch API
単体検索: ItemLookup API

と考えればいい。

詳しくはdocsを読んでほしい。

ItemSearch - Product Advertising API

ItemLookup - Product Advertising API

用途に応じて使い分ける。

今回は1つの商品についての情報をとりたかったので ItemLookup API を使う。

amazon Product Advertising API Scratchpadで商品情報をAPI越しに取得する

では、こちらにアクセスしよう。

Product Advertising API Scratchpad (beta)

認証キーの入力

こんな感じで認証キーを求められる。

marketplaceはあなたが日本人を相手にするなら日本を選ぼう。

認証キーは先ほどコピーしたやつを貼り付ければ終わりだ。

m

f:id:namu_r21:20190721002348p:plain
クレデンシャル

検索パラメタとASIN

続いて、検索パラメータの入力である。

ItemLookupでは基本的に ASIN を使って検索することになる。

f:id:namu_r21:20190721002256p:plain
amazon Product Advertising API Scratchpad ItemLookupの画面

これは amazon内における商品の固有識別子 である。 商品ページにも乗っている(sony wf-1000xm3を例にした)

f:id:namu_r21:20190721002559p:plain

ItemLookup APIでは、これを使って検索することが多いらしい。

完全自動化するなら以下の流れになると思う。

  1. ItemSearch API でASIN取得
  2. ItemLookup API で ASINから商品検索

今回はめんどくさいのでやらなかった。

ResponseGroupは返却する情報のカテゴリを選択する

ResponseGroupは返却する情報のカテゴリを選択する。

返してほしい情報のカテゴリを選択する。

今回は Images, ItemAttributes を選択する。

f:id:namu_r21:20190721002959p:plain

Run request した結果にアフィリンクが含まれる

run requestするとAPIの結果が返ってくる。

response -> XML responseをクリックするとこんな感じのxmlが返ってくる。

<ItemLookupResponse
    xmlns="http://webservices.amazon.com/AWSECommerceService/2011-08-01">
    <OperationRequest>
        ...
    </OperationRequest>
    <Items>
        <Request>
           ...
        </Request>
        <Item>
            <ASIN>B07TKGGZ47</ASIN>
            <ParentASIN>B07TW941DB</ParentASIN>
            
<DetailPageURL>https://www.amazon.co.jp/%E3%82%BD%E3%83%8B%E3%83%BC-SONY-%E3%83%AF%E3%82%A4%E3%83%A4%E3%83%AC%E3%82%B9%E3%83%8E%E3%82%A4%E3%82%BA%E3%82%AD%E3%83%A3%E3%83%B3%E3%82%BB%E3%83%AA%E3%83%B3%E3%82%B0%E3%82%A4%E3%83%A4%E3%83%9B%E3%83%B3-WF-1000XM3-Bluetooth/dp/B07TKGGZ47?psc=1&SubscriptionId=xxxx&tag=xxx&linkCode=xxx&camp=xxxx&creative=xxxx&creativeASIN=B07TKGGZ47</DetailPageURL>
            <ItemLinks>

アフィリンクは xpathでいう ItemLookupResponse/Items/Item/DetailPageURL がそれに当たる。

これを使えばアフィ報酬をもらえるだろう。

ちなみにページ下部に行くと PHP / Java のコードが自動生成される。

マジで便利だわこのツール。

amazon アフィリエイトリンクをAPIで作る

ということで、amazon アフィリエイトリンクをAPIで作る方法について書いた。

amazon アソシエイト登録が1番むずいと思う。

では。

scalaのコレクションライブラリについて 前編

scalaのコレクションは mutableとimmutableがある

immutableは安全で気にすることが減る scalaではあまりarrayを使わないらしい

Listはimmutable

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

scala> lst(0) = 1
       ^
       error: value update is not a member of List[Int]
       did you mean updated?

scalaの空リストはNil

rubyのnilとは特性が大きく異なる。ただのカラリスト
Nilはコンス :: と合わせて使うことが多い

scala> val a1 = 1 :: Nil
a1: List[Int] = List(1)

scala> val a2 = 2 :: a1
a2: List[Int] = List(2, 1)

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

なるほど、concatとかmergeとかそういうやつか コンスは特殊な関数だそう

scala> 1 :: 2 :: 3 :: 4 :: Nil
res13: List[Int] = List(1, 2, 3, 4)
scala> Nil.::(4).::(3).::(2).::(1)
res14: List[Int] = List(1, 2, 3, 4)

昔、フォールドライトとかフォールドレフトとかやったな

実行順序が記述順序と違うらしい リストの連結

scala> List(1, 2) ++ List(3, 4)
res15: List[Int] = List(1, 2, 3, 4)

++ で書くと繋がる + ではないんだね

mkString:文字列のフォーマッティング

非常によく利用されるらしい

scala> val arr = Array(1,2,3,4,5)
arr: Array[Int] = Array(1, 2, 3, 4, 5)

scala> arr.mkString
res10: String = 12345

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

scala> l.mkString
res11: String = 12345
mkString() と呼び出すとこける
scala> l.mkString()
         ^
       error: overloaded method value mkString with alternatives:
         => String <and>
         (sep: String)String <and>
         (start: String,sep: String,end: String)String
        cannot be applied to ()

セパレータの宣言ができる

scala> l.mkString(":")
res13: String = 1:2:3:4:5

scala> l.mkString("[", ",", "]")
res14: String = [1,2,3,4,5]

便利

なんだか気分が乗らないので今日はここまで

MySQLはtransaction中でもselectできる

本番DBへクエリを流す

手で本番DBにクエリを流すのは人生で初めてだった。

これまではmigrateツールがよしなにやってくれていたので、あんまりそんな機会がなかった。

migrationツールがないチームに異動して初の体験。

mysqlでtransactionを使った

今日、業務中に何気なく transactionを初めて使った。

> begin;
> update ...
> commit;

トランザクションを開始するとcommitを適用するまでは操作がデータベースに適用されない。

当たり前だよね。

rollback; を実行すれば、操作はなかったことになる。

トランザクション中のselectは結果を反映している

実はこの時、トランザクション中にselectを叩くと、操作が反映された状態のテーブルにクエリがかかる。

なので、commit; 前に操作結果を反映した状態のテーブルを見ることができる。

当然、まだ本物のテーブルには操作が反映されていないので仮想的なテーブルに対してクエリしているんだろう。

何気に知らなかったのでメモっておこう

scalaで高階関数を学ぶ

scalaで関数を学ぶ

いつものドワンゴscala講座を読む

関数 · Scala研修テキスト

今日は 関数

関数

scalaでは関数もオブジェクト。

メソッドと関数は異なるらしい。

本来はdefで始まる構文で定義されたものだけがメソッド。
関数オブジェクトを関数と呼ぶ。
便宜上、メソッドも関数と呼ぶことがある。

こんな感じ。

scalaでは無名関数の宣言をカジュアルに行える。以下みたいに

// オブジェクト名: 引数型 => 戻り値型 = 引数名 => 処理
val f: String => Int = argValue => argValue.toInt * 2

と書けば無名関数の宣言と同時に変数に代入できる。

高階関数

関数を引数に取ったり関数を返すメソッドや関数のことを高階関数と呼ぶ。

以下の例みたいに、外から値と関数を渡して実行できる。

// 宣言
scala> def double(n: Int, f: Int => Int): Int = {
     |   f(f(n))
     | }
double: (n: Int, f: Int => Int)Int

// 実行
scala> double(1, m => m * 2)
res4: Int = 4

この例はintを返しているけど、関数を返すこともできるっぽい。

全然読めなかった高階関数の宣言

以下の関数はドキュメントからそのまま持ってきたものである。

scala> import scala.io.Source
import scala.io.Source

scala> def withFile[A](filename: String)(f: Source => A): A = {
     |   val s = Source.fromFile(filename)
     |   try {
     |     f(s)
     |   } finally {
     |     s.close()
     |   }
     | }
withFile: [A](filename: String)(f: scala.io.Source => A)A

引数が (引数1)(引数2) となっている。

これまで書いてきた、C, PHP, RubyではこのSyntaxを見たことがなかったので戸惑った。

qiita.com

これを読む感じだとカリー化っぽいね。

ということでカリー化と同時実行の両方をやってみた。

ioで使うファイルはこれ

$ cat hoge
hogepiyohuga

さて、色々試してみよう

scala> def withFile[A](filename: String)(f: Source => A): A = {
     |   val s = Source.fromFile(filename)
     |   try {
     |     f(s)
     |   } finally {
     |     s.close()
     |   }
     | }

// -- カリー化 --
// fにファイル名だけ固定した関数を代入

scala> val f = withFile[String]("hoge")_
f: (scala.io.Source => String) => String = $$Lambda$917/0x000000080061c840@5896cb9c

// 関数内で使われる関数を宣言

scala> val ff: Source => String = s => s.getLines.mkString
ff: scala.io.Source => String = $$Lambda$920/0x0000000800629040@1de30c31

// hogeの中身を読み取って ffが実行される
scala> f(ff)
res3: String = hogepiyohuga

// -- 同時実行 --
// filenameと実行関数を同時に渡す

scala> val f = withFile[String]("hoge")(ff)
f: String = hogepiyohuga

そこそこ理解できた。今日はこれで

scala 型パラメータ 非変、共変、反変について調べた

お仕事でscalaを使うのでscalaに入門

型パラメータと変位指定 · Scala研修テキスト

型パラメータについて

今日は型パラメータについて学んでいる。

動的型付け言語ばっかり書いてきたので、型パラメータについて学ぶのは初。

何の事は無い、クラスを作る時点では型が不明の場合に役に立つ。

例えば、コレクションクラス(配列とか)を作るときに便利らしい。

rubyやphpは代入された物をよしなに動的に判断してくれたり、肩にかかわらず突っ込めたりするが、scalaはそうではない。

例えばIntが入ったシーケンスはこんな感じで定義する

scala>  val seqInt = Seq[Int]();
seqInt: Seq[Int] = List()

replで遊んでみた。

// A が型パラメータ
// Aには任意の型が入る

scala> class Cell[A](var value: A) {
     | def put(newValue: A): Unit = {
     | value = newValue
     | }
     | def get(): A = value
     | }
defined class Cell

// Intで宣言する

scala> val cell = new Cell[Int](1)
cell: Cell[Int] = Cell@47ed3f81

scala> cell.put(2)

scala> cell.get()
res1: Int = 2

// Int で宣言したcellはStringを受け付けない

scala> cell.put("2")
<console>:13: error: type mismatch;
 found   : String("2")
 required: Int
       cell.put("2")
                ^

// Stringで宣言してみる

scala> val cell = new Cell[String]("1")
cell: Cell[String] = Cell@60b68ffb

scala> cell.get()
res3: String = 1

scala> cell.put("2")

scala> cell.get()
res5: String = 2

実用的な使われ方では、戻り値で複数の値を返したいときに使われるらしい。

tapleのような複数個の値をひとまとめにしたクラスを作ることが多いそう。

変位指定(variance)

型パラメータ 自体はわかりやすいがこれがとても難しかった。

というか今もあんまり理解できてない。

variance は 2つのオブジェクト(関数も含む) について型の扱いをどうするか を指定するための物である。

型の継承によって扱われ方が異なる。

ちょっと集合論というか、概念の話が多くなる。

variance には 3種類の特性がある。

  • 非変 (invariant)
  • 共変 (covariant)
  • 反変 (contravariant)

非変

これはめっちゃわかりやすい。 非変は、同じ型じゃないと代入を許さないというものだ。

ドワンゴから引用

型パラメータを持ったクラスG、型パラメータAとBがあったとき、A = Bのときにのみ

val : G[A] = G[B]
というような代入が許されるという性質を表します。

scalaは非変がデフォルト。そらそうだよね、って挙動。 (javaは共変がデフォルトらしい)

共変

ドワンゴ曰く、以下とのこと。わ...わからん...。

共変というのは、型パラメータを持ったクラスG、型パラメータAとBがあったとき、A が B を継承しているときにのみ、

val : G[B] = G[A]
というような代入が許される性質を表します。

この記事がわかりやすかったので引用。

qiita.com

共変:型の制限を強める(対象範囲を狭める) → 継承先の派生クラスに変更すること

らしいです。

例えば、クラスHoge、型パラメータA = String、B = Any(全ての型の親クラス)とすると

val hoge: Hoge[Any] = Hoge[String]

になる。実際にこれを宣言するとコンパイルエラーになる(非変だから)。

共変として扱うときは型パラメータの前に + を宣言する。

val hoge: Hoge[+Any] = Hoge[String]

こうやってみてみると、 型の制限を強める っていうのは妥当な表現に見える。

定義域が広いAnyに対して、より定義域の狭いStringを渡す。

これは、本来扱える空間はすっごく広い(Any)けど、実際に扱えるのはある一部(String)の空間だけという風に型の制限を強めるを意味している。

元記事の図がとてもわかりやすいのでお借りする。

https://camo.qiitausercontent.com/8eb17f1a577a359bff9476a2249f99dcad611e3a/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f33323035372f32343562373035302d616662332d366339622d396266362d3532393939373038323362382e706e67

型の定義域を集合の空間と同じように扱うとわかりやすいのかも。

親クラスが全集合で、子クラスが部分集合みたいな感じで。

子クラスになると自ずと表現できる幅や制約がかかるので、型の制約が厳しくなる。

immutableなら安全に共変できるらしい。なんで危険なのか詳しくは知らない。

反変

qiita.com

の言葉を借りると、

反変:型の制限を弱める(対象範囲を広げる) → 継承元の基底クラスに変更すること

https://camo.qiitausercontent.com/8eb17f1a577a359bff9476a2249f99dcad611e3a/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f33323035372f32343562373035302d616662332d366339622d396266362d3532393939373038323362382e706e67

共変は型の制約を強める働きをした。反変は型の制約を弱めるらしい。

ドワンゴから抜粋。

共変とちょうど対になる性質である反変です。簡単に定義を示しましょう。反変というのは、型パラメータを持ったクラスG、型パラメータAとBがあったとき、A が B を継承しているときにのみ、

val : G[A] = G[B]
というような代入が許される性質を表します。

んーなんだかわからない。

実際のコードをみてみよう。

ちなみにコードリードの前に細く。

=> は関数定義の意味があるらしい。

つまり、argType => returnType と読める。jsでもよくみるよね。

scalaは関数もObjectなのでx1に関数定義する感じになる。

val x1: String => AnyRef = AnyRef => AnyRef型の値

読みづら...ということで補足する。

ここは個人的な考えなのだが、scalaのsyntaxは次のように読み替えてもいいと思う

val 変数名 期待する引数/戻り値のインタフェース = 内部実装(引数/戻り値)

こう読むと、以下のように読むことができる。

// x1はStringを受け取ってAnyRefを返す関数を期待している
// 内部実装はAnyRefを受け取ってAnyRefを返す関数

val x1: String => AnyRef = AnyRef => AnyRef型の値

こうやってみてみると、以下のように考えられた。

1. x1はStringを受け取ってAnyRefを返せる関数
2. 代入された関数はAnyRefを受け取ってAnyRefを返す
3. 引数として取れる型が狭いだけで、関数内ではAnyRef相当の定義域で扱える。
4. つまり, x1が期待するインタフェースの方が型の定義域が厳しく、内部実装が扱える型の定義域が広い

入り口がめっちゃ狭いだけで、実装上扱える型空間が広いときに反変を使えば良さそうである。

AnyRefは全ての型の親クラスなのでこう書き換えることもできる。

val x1: Int => AnyRef = AnyRef => AnyRef型の値

つまり、型の制約を弱めていることになるらしい。

scalaでは反変をこう定義する。

class ClassName[-A]

なんとなく雰囲気を理解したので今日はここまで。

型パラメータの境界 なんて考え方もあるらしいが、難しかったのでまた明日やる。

では。