Scala + Android
初めて勉強
成瀬基樹
自己紹介
もうすぐ社会人
もうすぐプログラマ
先輩プログラマのみなさん,よろしくお願いします
Twitter: @lgfuser
Facebook: facebook.com/motoki.naruse
Scala + Androidの経験は現在製作中のSlashLauncher程度
Scalaとは
オブジェクト指向も関数型もできる,
Javaとの親和性が非常に高い,
Javaが動く場所ならだいたい動く(GAEとかAndroidとか)
そんなとってもおいしい言語
Scalaのオススメの本
今回はScala + Androidということで,Scalaについては詳しく見ずに,ScalaでAndroidアプリを作るのにすぐに役に立ちそうなところから攻めていこうと思います.
Scalaという言語について詳しく知りたい人はコップ本とかを読むといいと思います.
てか僕もScalaについてそこまで詳しくないんですごめんなさいごめんなさい
スライドを少し進めては実際に書いてみて〜またスライドを少し進めて〜と進めたいと思います
Scala + Android
スタート!
Scala + Androidの
Java + Androidに対する
メリットとは?
Scala + Androidのメリット
Scala + Androidのメリット(2)
AndroidでScalaを使う場合でも,ScalaがJavaに対して持っているメリットはすべて得られるはず
僕の場合
・Javaより簡潔に書ける
・何よりも書くことが楽しい
Java使いをScalaに引き込むサンプル集
Scala + Androidのデメリット
protected staticにアクセスできない
Javaでラッパーを書いてあげると解決
Scala -> APKするのにすごく時間がかかる
X201sで50~60秒,iMacで20~30秒
APKのサイズが大きくなってしまう
HelloWorldで700KBぐらい※要チェック
ブログなどからのコピペは絶望的
IntelliJのJava -> Scala変換とか,自力変換
これらを「些細な問題」と言えるのなら,
Scala + Androidは
Java + Androidよりも
快適なはず
Scalaを書きたくなりましたか?
実際にScalaを書いてみましょう
% sbt
> console
ScalaのREPLが起動します
HelloWorld
println("HelloWorld")
HelloWorld
変数,valとvar
val s = "Scala"
s: java.lang.String = Scala
valはJavaのfinalで宣言された変数
よって
val s = "Scala"
s = "Android"
ができない
変数,valとvar
Java
final String s = "Scala"
Scala
val s = "Scala"
これらは同じ
変数,valとvar
var s = "Scala"
s: java.lang.String = Scala
varは再代入可能な変数
よって
var s = "Scala"
s = "Android"
ができる
Java
String s = "Scala"
Scala
var s = "Scala"
これらは同じ
型推論について
val s = "Scala"
s: java.lang.String = Scala
左辺では型を指定していないのに型を解決してくれている
型を明示的に書くこともできる
val s:String = "Scala"
s: String = Scala
varは副作用を招くため,極力valを使用
たぶん関数内でvarを使わなければならないことはそうないと思います
varはフィールドで使うぐらい?
関数
def plus(a: Int, b: Int): Int = {
return a + b
}
def 関数名(引数名: 型, 引数名: 型): 戻り値の型 = {
処理
}
最も丁寧に書いた例です
関数
Scala
def plus(a: Int, b: Int): Int = {
return a + b
}
Java
int plus(int a, int b) {
return a + b;
}
def plus(a: Int, b: Int): Int = {
return a + b
}
最後の処理が自動的に返されるため,returnは不要です
def plus(a: Int, b: Int): Int = {
a + b
}
def plus(a: Int, b: Int): Int = {
a + b
}
Int + Intなので戻り値はIntと推論されます
def plus(a: Int, b: Int) = {
a + b
}
def plus(a: Int, b: Int): Int = {
a + b
}
処理内容が一行の場合,{}を省略できます
def plus(a: Int, b: Int) = a + b
並べてみた
int plus(int a, int b) {
return a + b;
}
def plus(a: Int, b: Int): Int = {
return a + b
}
def plus(a: Int, b: Int) = a + b
関数リテラル
val a = (s: String) => s + s
a: String => java.lang.String = <function1>
a("Hello")
java.lang.String = HelloHello
(引数) => 処理内容
((s: String) => s + s)("Hello")
java.lang.String = HelloHello
こんな書き方もできます
カリー化
def func(a: Int)(b: Int) = a * b
引数を一つずつ()で分けて定義できる
呼び出すときは
func(1)(2)のようになる
funcに1を適用した関数に2を適用するという動作
こんなことができる
def func(i: Int)(f: => Unit){ for(j <- 1 to i) f }
func: (i: Int)(f: => Unit)Unit
func(3){println("Hello")}
Hello
Hello
Hello
さらには
val three = func(3)_
three: => Unit => Unit = <function1>
three {
print("Hoge")
}
HogeHogeHoge
Javaのswitchに不満はないですか?
Javaのswitchはintとenumしか出来なくて不便ですよね
Java7はStringができるようですけど
match
ScalaのmatchはJavaのswitchを超強力にした感じ
def isHello(s: String) = s match {
case "Hello" => true
case _ => false
}
isHello: (s: String)Boolean
isHello("Hello")
Boolean = true
isHello("Hoge")
Boolean = false
型によるmatch
def what(a: Any) = a match {
case _: String => "文字列"
case _: Int => "整数"
case _: Double => "小数"
case _ => "不明"
}
AnyはJavaでいうObject
what("a")
java.lang.String = 文字列
what(1)
java.lang.String = 整数
what(0.1)
java.lang.String = 小数
def what(a: Any) = a match {
case _: String => "文字列"
case i: Int if(0 <= i) => "正の整数"
case i: Int => "負の整数"
case _: Double => "小数"
case _ => "不明"
}
match + ifが可能
what(1)
java.lang.String = 正の整数
what(-1)
java.lang.String = 負の整数
if
ifは文ではなく式なので値を返します
def big(a: Int, b: Int) = if(a > b) a else b
big: (a: Int, b: Int)Int
big(1,2)
Int = 2
big(2,1)
Int = 2
先程極力valと言いましたが
varを使わないとforが書けなくなります
拡張forは可能です
むしろ拡張forしかないです
for(int i = 0;i < size; i++)
ここが再代入
例えばこんなの
int sum = 0;
for(int i = 0; i < 10; i++) {
sum += i
}
再帰で解決です
def sum(i: Int): Int = i match {
case 0 => 0
case _ => sum(i - 1) + i
}
sum: (i: Int)Int
sum(10)
Int = 55
負の数を指定すると異常動作なのは目をつぶってください
しかし大きな数を指定するとstack overflowが起きます
sum(10000)とか
そこでアキュムレータですよ
def sum(a: Int, i: Int): Int = i match {
case 0 => a
case _ => sum(a + i, i - 1)
}
sum: (a: Int, i: Int)Int
sum(0, 10000)
Int = 50005000
これはコンパイラによりforに変換されます
関数内に関数を作って使いやすくする
def sum(i: Int) = {
def sum(a: Int, i: Int): Int = i match {
case 0 => a
case n => sum(a + i, i - 1)
}
sum(0, i)
}
sum: (i: Int)Int
sum(10000)
forを書いてはいけないというわけではないです
forも便利です
for(i <- 1 to 9; j <- 1 to 9) print((i * j) + ", ")
1, 2, 3, ... , 63, 72, 81,
二重,三重のループをこんな風に書けます
List
val l = 1::2::3::4::5::Nil
l: List[Int] = List(1, 2, 3, 4, 5)
::(コンス)でListが作れます
JavaのListの上位互換のイメージ
任意の要素の取り出し
l(1)
Int = 2
合計
l.sum
Int = 15
全要素を処理
l.foreach(print(_))
12345
要素に名前をつける
l.foreach(i => print(i))
12345
全項目処理して新しいList
l.map(_ * 2)
List[Int] = List(2, 4, 6, 8, 10)
条件に合う要素を抽出する
l.filter(_ % 2 == 0)
List[Int] = List(2, 4)
setter, getterを書くの面倒じゃない?
class A {
private int a;
private int b;
public A(int aa, int bb) {
a = aa;
b = bb;
}
public int getA() {
return a;
全部書くには余白が足りんわ
Scalaならね
class A(val a: Int, var b: Int)
これで終わり
勝手にsetter(mutator), getter(accessor)が作られます
valだとaccessorのみ
varだとmutatorとaccessorの両方
class A(val a: Int, var b: Int)
defined class A
val a = new A(0, 0)
a: A = A@5c8aca5e
a.a = 1
エラー
a.b = 1
a.b: Int = 1
a.a
Int = 0
a.b
Int = 1
全然Androidに入れない
キーワード
Map
Tuple
REPLから抜ける
:q
Scala + Androidの開発環境
Scala + Androidの開発環境(2)
Scala + Androidの開発環境(3)
僕の開発手順
LayoutXMLはEclipseで書くと良いかも
sbtのインストール
https://github.com/harrah/xsbt/wiki
ここを読んでインストール
IntelliJのインストール
http://www.jetbrains.com/idea/
ここからダウンロード
IntelliJでScalaを書いてみる
HelloWorld書き比べ
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
object HelloWorld {
def main(args: Array[String]) = println("Hello World")
}
※スライドにすると見にくいのでインデントをスペース4つにしていますが,スペース2つが推奨されています
Javaを少しずつScalaへ
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
object HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
staticなメソッドを持つ場合objectとして宣言
デフォルトでpublicなので,publicは書かなくてOK
Javaを少しずつScalaへ(2)
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
object HelloWorld {
def main(String[] args) {
System.out.println("Hello World");
}
}
メソッド(関数)の宣言はdef
戻り値の型を書かない場合はUnit(Javaのvoid)になる
戻り値の型を書くのならこんな感じ
object HelloWorld {
def main(args: Array[String]): Unit = {
println("Hello World")
}
}
Javaを少しずつScalaへ(3)
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
object HelloWorld {
def main(args: Array[String]) {
println("Hello World")
}
}
引数や変数は「名前: 型」の順に書く
また,配列はArray型になる
Javaを少しずつScalaへ(4)
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
object HelloWorld {
def main(args: Array[String]) {
println("Hello World")
}
}
System.out.printlnではなく,printlnで出力を行う
行末の;は不要
もはや上もScalaだけど
object HelloWorld {
def main(String[] args) {
println("Hello World")
}
}
object HelloWorld {
def main(args: Array[String]) = println("Hello World")
}
一行だけなら{}なしで=で接続できる
ifなどの例外あり
=が使用されていて,かつ戻り値の型が宣言されていない場合,型推論される
HelloWorld書き比べ
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
object HelloWorld {
def main(args: Array[String]) = println("Hello World")
}
IntelliJでScalaを書いてみる
package naruse.scala.helloworld
object HelloWorld {
def main(args: Array[String]) = println("Hello World")
}
簡単な関数を書いてみる
package naruse.scala.helloworld
object HelloWorld {
def main(args: Array[String]) = printHello
def printHello = println("Hello")
}
引数の無い関数,関数呼び出しは()を省略可能
簡単な関数を書いてみる(2)
package naruse.scala.helloworld
object HelloWorld {
def main(args: Array[String]) = println(plus(1, 2))
def plus(a: Int, b: Int) = a + b
}
関数渡し
package naruse.scala.helloworld
object HelloWorld {
def main(args: Array[String]) {
val ans = calc(plus, 2, 3)
println(ans)
}
def plus(a: Int, b: Int) = a + b
def calc(f: (Int, Int) => Int, a: Int, b: Int) = f(a, b)
}
部分適用
package naruse.scala.helloworld
object HelloWorld {
def main(args: Array[String]) {
val plus2 = plus(2, _: Int)
println(plus2(3))
}
def plus(a: Int, b: Int) = a + b
}
そろそろAndroidに入ろうと思います
export ANDROID_SDK_HOME=~/local/lib/android-sdks
ANDROID_SDK_HOMEという名前でAndroidSDKにパスを通します
まずはgiter8をインストールします
https://github.com/n8han/giter8
sbt用のScalaプロジェクトのテンプレートを作ってくれます
Androidだけではなく,Scalaを使ったウェブアプリのテンプレートなども用意してくれてとっても便利です
curl https://raw.github.com/n8han/conscript/master/setup.sh | sh
cs n8han/giter8
ここまででインストール完了
g8 jberkel/android-app
これでAndroidのテンプレートを対話的に作成
package [my.android.project]: naruse.scala.android.example
name [My Android Project]:example
main_activity [MainActivity]:
scala_version [2.9.1]:
api_level [10]: 8
useProguard [true]:
IntelliJで使うためにsbt-idea
https://github.com/mpeltonen/sbt-idea
~/.sbt/plugins/plugin.sbtを作成する
resolvers += "sbt-idea-repo" at "http://mpeltonen.github.com/maven/"
addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.0.0")
先程g8で作成したsbt用のAndroidアプリのプロジェクトのルートに移動して
sbt
> gen-idea
あとはIntelliJのプロジェクトを開くから開いちゃえば幸せになれるね!
IntelliJで開くと,Rが見つからなくて警告になっていますが,これはRが自動生成されないためで,ビルドしちゃえば生成されるのでエラーが消えます
sbt
apkの作成
> android:package-debug
エミュレータへのインストール
> android:install-emulator
HelloWorldが表示されましたか?
Emacsで開発したい人はensimeで,
Eclipseで開発したい人はScala-IDEで検索すると幸せになれると思います
どちらもAndroidのAPKを作るときにはsbtを使うのが良さそう
ソースコードを読んでみる
class MainActivity extends Activity with TypedActivity {
override def onCreate(bundle: Bundle) {
super.onCreate(bundle)
setContentView(R.layout.main)
findView(TR.textview).setText("hello, world!")
}
}
TypedActivityはActivityを継承しているのでMainActivityはActivityを継承する必要はないです
テンプレートはちょこちょこ更新されているので,近いうちにActivity withが最初から書かれていないという状態になるかも
僕の想像です
class MainActivity extends TypedActivity {
override def onCreate(bundle: Bundle) {
super.onCreate(bundle)
setContentView(R.layout.main)
findView(TR.textview).setText("hello, world!")
}
}
オーバーライドするときにはoverrideが必須
class MainActivity extends TypedActivity {
override def onCreate(bundle: Bundle) {
super.onCreate(bundle)
setContentView(R.layout.main)
findView(TR.textview).setText("hello, world!")
}
}
findViewってなんだろう.
trait TypedActivity extends Activity with TypedActivityHolder
trait TypedActivityHolder extends TypedViewHolder
trait TypedViewHolder {
def findViewById( id: Int ): View
def findView[T](tr: TypedResource[T]) =
findViewById(tr.id).asInstanceOf[T]
}
Activityで実装される
def findView[T](tr: TypedResource[T]) =
findViewById(tr.id).asInstanceOf[T]
object TR {
val textview =
TypedResource[android.widget.TextView](R.id.textview)
}
引数にR.id.hogeの代わりにTR.hogeを渡すことで型を解決する
TRはコンパイル時に自動生成される
val textView = findView(TR.textview)
しかも型推論があるため,型情報を自分で一切書かずにHogeViewのインスタンスをLayoutXMLから得られる
部分適用をAndroidで活用する
private static final String TAG =
Hoge.class.getSimpleName();
Log.d(TAG, "HogeHoge");
↓
val logd = Log.d(classOf[Hoge].getSimpleName, _: String)
logd("HogeHoge")
trait
実装を持てるinterfaceという表現が近いかも
trait Hoge {
val DEFINE= 1
def print(s: String) = println(s)
}
trait + 部分適用でLog._が便利
例えばこんなtraitを用意してみる
trait Log {
val TAG = getClass.getSimpleName
val logd = android.util.Log.d(TAG, _:String)
}
class HogeActivity extends Activity with Log {
def onCreate(bundle: Bundle) {
logd("onCreate")
}
}
これでどこでもログが簡単に出力できるね!
インテントで渡されたURLをWebViewで開くことを考える
Javaの場合
String url = getIntent().getStringExtra("url");
if(null == url) {
mWebView.loadUrl("http://www.google.co.jp");
} else {
mWebView.loadUrl(url);
}
もしくはこんな感じ?
mWebView.loadUrl(getUrl());
private String getUrl() {
final String url = getIntent().getStringExtra("url")
return (null == url) ? "http://www.google.co.jp") : url;
}
nullチェックめんどくさいよね
かっこよくないよね(´・ω・`)
Scalaの場合
mWebView.loadUrl(Option(getIntent.getStringExtra) match {
case Some(u) => u
case None => "http://google.co.jp"
}
)
もしくはこんな感じ
mWebView.loadUrl(
Option(getIntent.getStringExtra("url")).
getOrElse("http://www.google.co.jp/")
)
一行で書きたいけど書ききれなかったので改行しています
暗黙的型変換
Log.d(String, String)なので,Log.d("piyo", 1)って書くと型が違うって怒られますよね
そこで暗黙的型変換ですよ
暗黙的型変換
implicit def hoge(i: Int) = String.valueOf(i)
Int型が渡されているが,String型が要求されている場合にこれが働きます
暗黙的型変換
implicit def int2String(i: Int) = String.valueOf(i)
~~~
Log.d("implicit", 1)
implicitな関数の名前は変換前の型名2変換後の型名とすることが多いようです
OnClickListenerめんどくさくない?
わかめさんに「OnClickListenerとかのためにimplicitでまくりかと思ってた」って言われるまで
僕はOnClickListenerとかはIDEがある程度書いてくれるから,implicitを使っていませんでした
implicit def func2OnClick(f: View => _) =
new OnClickListener {
override def onClick(v: View) = f(v)
}
変数名
引数の型
戻り値の型
関数渡しである
型推論によりOnClickListener型と決定する
暗黙的型変換に用いる関数である
渡された関数を実行する
implicit def func2OnClick(f: () => _) = new OnClickListener {
override def onClick(v: View) = f
}
view.setOnClickListener(() =>
startActivity(new Intent(getBaseContext, classOf[HogeActivity])
)
これで幸せ
戻り値の型を明示的に書くと() =>を省略できる
implicit def func2OnClick(f: => Intent) = new OnClickListener {
override def onClick(v: View) = f
}
view.setOnClickListener(new Intent(Intent.ACTION_MAIN) {
startActivity(setClassName(app.packageName, app.name))
})
implicit def func2OnClick(f: View => _) =
new OnClickListener {
override def onClick(v: View) = f(v)
}
view.setOnClickListener((v: View) =>
v.setVisiblity(View.INVISIBLE)
)
引数のViewが必要ならこう
これは便利ですね!
わかめさんありがとうございます
view.setOnClickListener(new Intent(Intent.ACTION_MAIN) {
startActivity(setClassName(app.packageName, app.name))
})
しかしこれ,IDEを使っても処理がどうなっているのか追いにくい
黒魔術かもしれない
テストについて
src/test下にテストを書く
これで実行
tests/android:package-debug
tests/android:install-emulator�tests/android:test-emulator
Androidの補完をしてくれる.ensime
(
:project-name "プロジェクト名"
:project-package "パッケージ名"
:sources ("src/main/scala"
"src_managed/main/scala"
"/home/fgluser/workspace/Scala/Android/quicksearch/target/scala-2.9.1/src_managed")
:compile-jars ("/home/fgluser/local/lib/android-sdks/platforms/android-4/android.jar")
:target "./"
)
Scalaの問題集
S-99: Ninety-Nine Scala Problems