- 投稿日:2019-10-12T23:45:55+09:00
「関数型デザイン&プログラミング」でのScalaでのST sモナドの実装について
書籍「関数型デザイン&プログラミング」をマイペースに読み進めています。
この書籍の14章では、局所作用を実行するためのクラスであるSTとSTRefが紹介されています。
今回はこれについてまとめてみます。ST、STRefは以下のImmutableとみなされるための条件を満たし、状態遷移を局所化する仕組みを提供するクラスです。状態遷移をローカルスコープに閉じたものとし、それをScalaの型システムを利用して強制させることで、参照透過性を保証させます。
Immutableな型とみなすための条件
以下の条件に違反するものはコンパイルされないようにScalaの型システムを利用します。
- ミュータブルなオブジェクトへの参照を保持しているもの以外は、そのオブジェクトが変更されていることを認識できない。
- ミュータブルなオブジェクトを、それが作成されたスコープの外側から参照することができない。
(「関数型デザイン&プログラミング」より引用)
STの実装
上記を踏まえたSTの実装は以下のようになっています。
(Github: fpinscala/answers/src/main/scala/fpinscala/localeffects/LocalEffects.scala
より)sealed trait ST[S,A] { self => protected def run(s: S): (A,S) //protectedにすることにより、runが実行可能なスコープを制限。変更を外部に漏らさないようにする def map[B](f: A => B): ST[S,B] = new ST[S,B] { def run(s: S) = { val (a, s1) = self.run(s) (f(a), s1) } } def flatMap[B](f: A => ST[S,B]): ST[S,B] = new ST[S,B] { def run(s: S) = { val (a, s1) = self.run(s) f(a).run(s1) } } } object ST { def apply[S,A](a: => A) = { lazy val memo = a //runが複数回呼ばれることを考慮したキャッシュ new ST[S,A] { def run(s: S) = (memo, s) } } def runST[A](st: RunnableST[A]): A = st[Unit].run(())._1 }上記で注目したいのはSTのrunメソッドです。runメソッドをprotectedで修飾することにより、外部からのアクセスを制限しています。これがpublicだと、引数のsを外部から渡せるようになってしまいます。runメソッド内で使われる可能性のあるSを外部で保持していることになるので、上記の不変条件に違反してしまいます。
また、runメソッドのシグネチャはS => (A, S)
なので、runによりA型の値が新たに生成されることを示しています。※runSTについては後述。
STRefの実装
STRef
はST
内の計算で使用されるミュータブルな参照を表現しています。STRefをST内のみでしか参照・操作できないよう実装することにより、状態遷移を局所化します。上記の不変条件を満たすよう実装されているため、ST内でSTRefがもつ値を変更したとしても、それが外部に漏れることはありません。
上記を踏まえたSTの実装は以下のようになっています。
(Github: fpinscala/answers/src/main/scala/fpinscala/localeffects/LocalEffects.scala
より)sealed trait STRef[S,A] { //sealedで修飾することにより、外部からSTRefを生成することはできない protected var cell: A def read: ST[S,A] = ST(cell) def write(a: => A): ST[S,Unit] = new ST[S,Unit] { def run(s: S) = { cell = a ((), s) } } } object STRef { def apply[S,A](a: A): ST[S, STRef[S,A]] = ST(new STRef[S,A] { var cell = a }) }STRefはsealedにより修飾されているので、外部から直接STRefを生成することはできません。
sealed trait STRef[S,A] { //sealedで修飾することにより、外部からSTRefを生成することはできない外部からSTRefを生成するには、STRef.apply呼び出す必要がありますが、これはSTによりラッピングされた形で返却されます。
//applyメソッド呼び出しにより、STRefはSTにラッピングされた形で生成される //ST[S, A]とSTRef[S, A]のSの型を一致させる def apply[S,A](a: A): ST[S, STRef[S,A]] = ST(new STRef[S,A] {ここで注目したい点は以下です。
・applyで生成されるST[S, A]とSTRef[S, A]のSの型が一致している
これにより、型Sを利用して、ST内でSTRefが操作できることになります。・STRefを外部から直接生成できず、STの内部にラップされる形でしか生成できないようにしている
これにより、STRefの生存期間はSTの生存期間に束縛されます。
STRefの状態遷移をSTの中に閉じ込めることで、外部からは副作用のないようにものとして振る舞うことが可能となります。・apply、read、writeメソッドは戻り値としてSTを返す
//以下の3つのメソッドはSTを戻り値として返すため、STの計算が継続できる def apply[S,A](a: A): ST[S, STRef[S,A]] def read: ST[S,A] def write(a: => A): ST[S,Unit]上記のようにapply、read、writeは戻り値としてSTを返します。
これにより、上記演算はSTのコンテキストに紐づくようになるため、STRefを操作する計算が継続できるようになります。ST[S, A]とSTRef[S, A]の型パラメータSについて
お気づきかもしれませんが、上記STとSTRefの実装では型Sを使用していません。
このSはSTRefをST内で操作するためのトークンのような役割しか持っていません。
なので、このSの型は実のところ何でも良いということになります。
これは、STのアクションが型Sに関して多相であると考えられます。RunnableSTの定義
上記のSTの実装ではrunSTメソッドが定義されていました。その実装を再掲すると以下のようになっています。
object ST { (中略) def runST[A](st: RunnableST[A]): A = /* val as = st[Unit].run(()) //as: (A, S) val a = as._1 // a: A */ st[Unit].run(())._1 //Sは多相であるため、Unitを指定しても問題ない。 }引数にRunnableSTなるものが指定されています。
STアクションからSTRefを取り出せないように、Scalaの型システムを利用して定義したものがRunnableSTです。RunnableSTの実装は以下のようになっています。
(Github: fpinscala/answers/src/main/scala/fpinscala/localeffects/LocalEffects.scala
より)trait RunnableST[A] { def apply[S] : ST[S, A] }RunnableSTを作成する目的は以下です。
1. ST[S, STRef[S, A]]型のアクションが実行できないようにする
外部からST[S, STRef[S, A]]のアクションの実行を許可しまうと、その結果としてSTRef[S, A]が取得できることになってしまいます。状態遷移をローカルスコープ内に局所化するためSTRefが外部にさらされることになるので、これを防ぎます。2. T型がS型に関与するようなST[S, T] のアクションが実行できないようにする
S型の変更により、T型も変更されるようなアクション、
T型の変更により、S型も変更されるようなアクションは
同様に状態遷移の局所化に反するので、これを実行できないよう防ぎます。3. ST[S, A]の型Sが多相であることを表現する
上述の通り、ST・STRefでの型Sはトークンの役割しか持っていません。
この型Sが多相であることを表現します。STのrunメソッドはprotectedで宣言されているため、外部からSTをrunするためには、このrunSTメソッドを使用しなければなりません。
具体的な使用例は以下のような感じです。
val p = new RunnableST[Int] { //A型だけを指定し、S型を指定する必要がない /* 型Sがapplyにバインドしているため、new RunnableSTを指定した場合、 型Sにアクセスすることはできない => 型SにSTRefを指定して、STRefを外部から取得することはできない */ def apply[S] = for { r <- STRef(1) x <- r.read _ <- r.write(x + 1) y <- r.read } yield y } val r = ST.runST(p) // r: Int = 2このように、STRefをSTアクションの内側だけにとどめ、参照を変化させても安全な仕組みを提供しています。綺麗ですね。
- 投稿日:2019-10-12T17:53:14+09:00
Kotlin時代のAndroid向けtooltipライブラリ「Balloon」を触ってみる
はじめに
チュートリアルによくありがちな、「特定のViewに対して吹き出しのような表示を行って使い方などを案内する」という,いわゆるtootip的な表現がしたかったので、これを実現できるライブラリを探してみました。
Androidでこのような要件に使えるライブラリといえばandroid-target-tooltipが定番かなと思っていましたが、調べてみると同種のものとしてBalloonと言うとてもいい感じのライブラリを発見することができました。使い勝手などを調査してみたかったので、本記事ではこれに触ってみます。
「Balloon」について
ライブラリのURL(https://github.com/skydoves/Balloon)
特徴
- full-kotlin
- Javaが混ざってるとぬるぽに怯えながらコードを書くことになるので個人的には嬉しいポイントです。
- 「kotlin dsl」で記述できる
- このライブラリでは「kotlin dsl」を活用して直感的に見た目を定義できます
- コードだけで見た目を定義できる
- 人によってこれをメリットと捉えるか否かは分かれそうですが、個人的にはいちいちXMLで見た目定義したりするのは面倒に感じるところがあるので、メリットにあげています。
触ってみる
まずはシンプルに触ってみる
まずはシンプルに、「特定のViewに対してテキストを含んだtooltipを出す」というのを作ってみます。
以下のようにmain_activity.xmlを記述します。activity_main.xml<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="32dp" android:text="show tip." app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="hide tip." app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/button1" />buttob1を押したらtextViewに対してtooltip表示、button2を押したら非表示、という感じで作ると以下のようになります。
MainActivity.ktclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val balloon = createBalloon(baseContext) { setArrowSize(10) setWidthRatio(0.5f) setHeight(65) setArrowPosition(0.5f) setCornerRadius(4f) setAlpha(0.9f) setText("Hello Balloon.") setBackgroundColorResource(R.color.colorPrimary) setBalloonAnimation(BalloonAnimation.FADE) setLifecycleOwner(this@MainActivity) } findViewById<Button>(R.id.button1).setOnClickListener { balloon.showAlignTop(findViewById(R.id.textView)) } findViewById<Button>(R.id.button2).setOnClickListener { balloon.dismiss() } } }
createBalloon
の部分がkotlin dslになっています。書きやすく、読みやすいですね。これは以下のように動作します。
インターフェースが直感的なので、特に解説することはないかなーと思います。とても簡単にtooltipを表示することができました。
カスタムビューを表示してみる
次は独自に定義したViewを表示させてみましょう。これができると自由度が一気に広がるので捗ります。
以下のようなViewを表示させてみます。
custom_balloon.xml<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="200dp"> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="32dp" android:text="Custom View." app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView1" /> </androidx.constraintlayout.widget.ConstraintLayout>コードはこんな感じに書きます。
MainActivity.ktclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val balloon = createBalloon(baseContext) { setArrowSize(10) setWidthRatio(0.5f) setHeight(200) setArrowPosition(0.5f) setCornerRadius(4f) setAlpha(0.9f) setLayout(R.layout.custom_balloon) setBackgroundColorResource(R.color.colorPrimary) setBalloonAnimation(BalloonAnimation.FADE) setLifecycleOwner(this@MainActivity) } findViewById<Button>(R.id.button1).setOnClickListener { balloon.showAlignTop(findViewById(R.id.textView)) } findViewById<Button>(R.id.button2).setOnClickListener { balloon.dismiss() } } }
setLayout
を使うことで独自に定義したViewを表示できます。また、ここでは触れませんが、セットしたViewを取り出すgetContentView()
というメソッドもあり、これを利用することで細かい見た目の変更を行ったり、各種リスナの登録を行ったりできます。
注意点として、setLayout
をしても、そのサイズに合わせてtooltipが伸縮したりはしないようなので、そこはsetHeight
などを活用してマニュアルでいい感じに設定してやる必要があります。ここまで実装できると以下のように動作します。
無事独自に定義したViewを表示することができました!
おわりに
今回は「Balloon」について調べました。インターフェースが使いやすいですし、見た目もいい感じなので、今後色々活用していこうかなと考えています。
個人的に改良したいところもあるので、改良を施してコントリビュートもしていきたいところです。
- 投稿日:2019-10-12T17:39:09+09:00
初めてのAndroidアプリで躓いたところ【2:リリース編】(プログラミング初学者)
前回の続きです。
初めてのAndroidアプリで躓いたところ【1:アプリ編】(プログラミング初学者)今回はaabファイルの作成やアップロードするまで道のりで詰まったことを紹介します。
※Google Playにアプリをリリースするのに「デベロッパーアカウント」を作るのですが、
こちらは何も難しくないので今回は触れません。aabファイルの作成
apkファイルとaabファイルの2種類ありますが、aabファイルの方を選びましょう。(Googleも推奨しているので)
aabファイルの方は「アプリサイズの縮小」や「ビルド時間の短縮」などのメリットがあります。
詳しくはこちらをご参照ください → Android App Bundle - Android Developersパッケージ名の変更
まず、パッケージ名から先に変更する必要があります。
デフォルトのcom.example.***
のパッケージ名では、
アップロードするときに「そのパッケージ名は禁止されている」と怒られるからです。
パッケージ名の一括変換は↓の記事を参考にしました。
「Android アプリのパッケージ名を変更するには」aabファイルの作り方
Android Studio(3.5)で作成していきます。
まず「ビルド」→「署名済みバンドル/APKの生成…」に進みます。
ダイアログが表示されるので、
上の「Android App Bundle」にチェックを入れて「次へ」をクリックします。
ここでは署名キーを作成します。
署名キーは他の新しいアプリを公開するときにも使用するので、大切に保管しましょう。
(2回目以降は「既存選択」で署名キーのファイルパスを入力します)
「新規作成」をクリックし、
キー保管パス: .jksファイルの置き場所
パスワード: バージョンアップなどでずっと使う大事なパスワード
エイリアス: 署名キーの名前
パスワード: 上のパスワードと同じが無難
有効期間(年): 25年以上が推奨
国コード: JP (日本)
※「証明書」の項目は全部は埋めなくても大丈夫です。必要な項目を埋められたら「OK」をクリックします。
前のダイアログに戻ったら、「キー保管パスワード」「キー・エイリアス」「鍵パスワード」を入力し、
「公開済みアプリを登録するための暗号化されたキーをエクスポートする」にチェックを入れ、
ファイルパスを指定します。(キー保管パスと同じが無難かと)
そして「次へ」をクリックします。最後にaabファイルの宛先フォルダーを指定して、
ビルド・バリアントの「release」を指定して「完了」をクリックします。
(アプリを公開する場合は「release」です)
Google Play Consoleでリリースの準備
画像の準備
ストアの掲載情報に最低必要な画像は以下のとおりです。
アイコン画像:512 × 512 png
スクリーンショット:1辺の長さ最小320px、最大3840px jpgかpng 2枚〜8枚
フィーチャーグラフィック 横1024 × 縦500 jpg か pngフィーチャーグラフィックとは、GooglePlayのトップに表示される画像です。
Facebookもリンクを貼るとフィーチャーグラフィックが表示されます。
アプリのリリース
次にアプリのリリースを作成します。
まず、Google Play Consoleで「アプリを作成」から掲載情報を入力していきます。サイドバーの「アプリのリリース」で「製品版トラック」の「管理」をクリックします。
「リリースを作成」で次に進みます。「Googleでアプリ証明鍵の管理、保護を行う(推奨)」の部分は「次へ」をクリックします。
「追加する Android App Bundle と APK」で、
作成したaabファイルをアップロードします。
リリース名とリリースの新機能の説明を記入したら、
「保存」をクリックし、保存ができたら「確認」をクリックします。これで「公開の準備が整いました」と表示されますが、
まだ「製品版として公開を開始」は押せません。
まだ左のバーで色が付いてないチェックマークの項目を完了させる必要があります。
それ以降の項目は特に詰まるところは無いかと思います。
左のバーのチェックマークがすべて緑色になったら、
リリースの画面に戻り、「製品版として公開を開始」をクリックすれば完了です!あとは致命的なバグさえ無ければ2日以内にはGoogle Playに公開されると思います。
(場合によっては数日かかることもあるらしいです)アップデートの方法
何かバグを修正したり、機能追加をしたときは、
アプリのバージョンコードを変える必要があります。バージョンコードの変更
バージョンコードを変えるには、build.gradleファイルの以下の数値を変更します。
build.gradle(app)versionCode 2 versionName "1.1"その上で、aabファイルを作成します。
aabファイルの作成
aabファイルの作成では「キー保管パス」で「既存選択」を選び、
作成した署名キーのファイルパスを入力します。
他の手順は上記のやり方と同じです。
(「公開済みアプリを登録するための暗号化されたキーをエクスポートする」のチェックははずして構いません)aabファイルのアップロード
Google Play Consoleの「アプリのリリース」で「製品版トラック」の「管理」をクリックし、
「リリースを作成」をクリックします。
あとは上記のリリースの手順と同じです。アップデートはこれで完了です。
おわりに
最近アプリの審査が厳しくなったのか、審査が通るのに数時間から数日かかるようです。
何か致命的なバグが残っていると数日かかったり、リジェクトされる可能性が高くなります。
自分も2回目のアップデートでバグを完全に修正したら24時間以内で通りました。
Androidアプリ開発が初めての方に参考になれば幸いです。
- 投稿日:2019-10-12T16:52:17+09:00
(もしもに備えて)Android StudioのKeymapの保存方法
先日、PCを再起動したら何故かAndroid StudioのKeymap(ショートカットの設定)が消えてしまいました。
そういったもしもの時に備えて、Android StudioのKeymapを保存する方法を記載したいと思います。(復元する方法も。)
前提
保存方法
至って簡単。
File > Export Settings...
を選択。
どの設定をExportするかどうかの選択画面になるので、
Key maps
Key maps (schemes)
をチェック。
OK
を押すと、settings.zip
としてExportされます。保存されたzipを確認
中身は↓のような感じになっていて、作成した
MyKeymap
も中に入っています。
復元方法
File > Import Settings...
を選択。
Exportした
settings.zip
を選択します。どの設定をImportするか聞かれるので、
Key maps
Key maps (schemes)
を選択します。
あとはAndroid Studioを再起動するかどうかを聞かれるので、再起動させると設定を復元可能です。