1 of 125

Scala + Android

初めて勉強

成瀬基樹

2 of 125

自己紹介

もうすぐ社会人

もうすぐプログラマ

先輩プログラマのみなさん,よろしくお願いします

Twitter: @lgfuser

Facebook: facebook.com/motoki.naruse

3 of 125

Android歴は1年半ぐらい

Scalaは半年ぐらい

GitHubViewerとかGitEditorとか作りました

4 of 125

Scala + Androidの経験は現在製作中のSlashLauncher程度

5 of 125

Scalaとは

オブジェクト指向も関数型もできる,

Javaとの親和性が非常に高い,

Javaが動く場所ならだいたい動く(GAEとかAndroidとか)

そんなとってもおいしい言語

6 of 125

Scalaのオススメの本

  • Scalaスケーラブルプログラミング第2版
    • コップ本.三冊の中では一番難しいと思う

  • プログラミングScala
    • バク本.電子書籍もあるよ

  • Scalaで学ぶ関数脳入門
    • Javaと比べながら書かれているのでAndroiderには分かりやすいと思う

7 of 125

今回はScala + Androidということで,Scalaについては詳しく見ずに,ScalaでAndroidアプリを作るのにすぐに役に立ちそうなところから攻めていこうと思います.

Scalaという言語について詳しく知りたい人はコップ本とかを読むといいと思います.

てか僕もScalaについてそこまで詳しくないんですごめんなさいごめんなさい

8 of 125

スライドを少し進めては実際に書いてみて〜またスライドを少し進めて〜と進めたいと思います

9 of 125

Scala + Android

スタート!

10 of 125

Scala + Androidの

Java + Androidに対する

メリットとは?

11 of 125

Scala + Androidのメリット

Scala メリット

検索結果が答え

12 of 125

Scala + Androidのメリット(2)

AndroidでScalaを使う場合でも,ScalaがJavaに対して持っているメリットはすべて得られるはず

僕の場合

・Javaより簡潔に書ける

・何よりも書くことが楽しい

13 of 125

Java使いをScalaに引き込むサンプル集

http://www.mwsoft.jp/programming/scala/java_to_scala.html

14 of 125

Scala + Androidのデメリット

protected staticにアクセスできない

Javaでラッパーを書いてあげると解決

Scala -> APKするのにすごく時間がかかる

X201sで50~60秒,iMacで20~30秒

APKのサイズが大きくなってしまう

HelloWorldで700KBぐらい※要チェック

ブログなどからのコピペは絶望的

IntelliJのJava -> Scala変換とか,自力変換

15 of 125

これらを「些細な問題」と言えるのなら,

Scala + Androidは

Java + Androidよりも

快適なはず

16 of 125

Scalaを書きたくなりましたか?

17 of 125

実際にScalaを書いてみましょう

% sbt

> console

ScalaのREPLが起動します

18 of 125

HelloWorld

println("HelloWorld")

HelloWorld

19 of 125

変数,valとvar

val s = "Scala"

s: java.lang.String = Scala

valはJavaのfinalで宣言された変数

よって

val s = "Scala"

s = "Android"

ができない

20 of 125

変数,valとvar

Java

final String s = "Scala"

Scala

val s = "Scala"

これらは同じ

21 of 125

変数,valとvar

var s = "Scala"

s: java.lang.String = Scala

varは再代入可能な変数

よって

var s = "Scala"

s = "Android"

ができる

22 of 125

Java

String s = "Scala"

Scala

var s = "Scala"

これらは同じ

23 of 125

型推論について

val s = "Scala"

s: java.lang.String = Scala

左辺では型を指定していないのに型を解決してくれている

24 of 125

型を明示的に書くこともできる

val s:String = "Scala"

s: String = Scala

25 of 125

varは副作用を招くため,極力valを使用

たぶん関数内でvarを使わなければならないことはそうないと思います

varはフィールドで使うぐらい?

26 of 125

関数

def plus(a: Int, b: Int): Int = {

return a + b

}

def 関数名(引数名: 型, 引数名: 型): 戻り値の型 = {

処理

}

最も丁寧に書いた例です

27 of 125

関数

Scala

def plus(a: Int, b: Int): Int = {

return a + b

}

Java

int plus(int a, int b) {

return a + b;

}

28 of 125

def plus(a: Int, b: Int): Int = {

return a + b

}

最後の処理が自動的に返されるため,returnは不要です

def plus(a: Int, b: Int): Int = {

a + b

}

29 of 125

def plus(a: Int, b: Int): Int = {

a + b

}

Int + Intなので戻り値はIntと推論されます

def plus(a: Int, b: Int) = {

a + b

}

30 of 125

def plus(a: Int, b: Int): Int = {

a + b

}

処理内容が一行の場合,{}を省略できます

def plus(a: Int, b: Int) = a + b

31 of 125

並べてみた

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

32 of 125

関数リテラル

val a = (s: String) => s + s

a: String => java.lang.String = <function1>

a("Hello")

java.lang.String = HelloHello

(引数) => 処理内容

33 of 125

((s: String) => s + s)("Hello")

java.lang.String = HelloHello

こんな書き方もできます

34 of 125

カリー化

def func(a: Int)(b: Int) = a * b

引数を一つずつ()で分けて定義できる

呼び出すときは

func(1)(2)のようになる

funcに1を適用した関数に2を適用するという動作

35 of 125

こんなことができる

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

36 of 125

さらには

val three = func(3)_

three: => Unit => Unit = <function1>

three {

print("Hoge")

}

HogeHogeHoge

37 of 125

Javaのswitchに不満はないですか?

Javaのswitchはintとenumしか出来なくて不便ですよね

Java7はStringができるようですけど

38 of 125

match

ScalaのmatchはJavaのswitchを超強力にした感じ

39 of 125

def isHello(s: String) = s match {

case "Hello" => true

case _ => false

}

isHello: (s: String)Boolean

isHello("Hello")

Boolean = true

isHello("Hoge")

Boolean = false

40 of 125

型によるmatch

def what(a: Any) = a match {

case _: String => "文字列"

case _: Int => "整数"

case _: Double => "小数"

case _ => "不明"

}

AnyはJavaでいうObject

41 of 125

what("a")

java.lang.String = 文字列

what(1)

java.lang.String = 整数

what(0.1)

java.lang.String = 小数

42 of 125

def what(a: Any) = a match {

case _: String => "文字列"

case i: Int if(0 <= i) => "正の整数"

case i: Int => "負の整数"

case _: Double => "小数"

case _ => "不明"

}

match + ifが可能

43 of 125

what(1)

java.lang.String = 正の整数

what(-1)

java.lang.String = 負の整数

44 of 125

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

45 of 125

先程極力valと言いましたが

varを使わないとforが書けなくなります

拡張forは可能です

むしろ拡張forしかないです

for(int i = 0;i < size; i++)

ここが再代入

46 of 125

例えばこんなの

int sum = 0;

for(int i = 0; i < 10; i++) {

sum += i

}

47 of 125

再帰で解決です

def sum(i: Int): Int = i match {

case 0 => 0

case _ => sum(i - 1) + i

}

sum: (i: Int)Int

sum(10)

Int = 55

負の数を指定すると異常動作なのは目をつぶってください

48 of 125

しかし大きな数を指定するとstack overflowが起きます

sum(10000)とか

49 of 125

そこでアキュムレータですよ

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に変換されます

50 of 125

関数内に関数を作って使いやすくする

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)

51 of 125

forを書いてはいけないというわけではないです

forも便利です

52 of 125

for(i <- 1 to 9; j <- 1 to 9) print((i * j) + ", ")

1, 2, 3, ... , 63, 72, 81,

二重,三重のループをこんな風に書けます

53 of 125

List

val l = 1::2::3::4::5::Nil

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

::(コンス)でListが作れます

JavaのListの上位互換のイメージ

54 of 125

任意の要素の取り出し

l(1)

Int = 2

合計

l.sum

Int = 15

全要素を処理

l.foreach(print(_))

12345

55 of 125

要素に名前をつける

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)

56 of 125

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;

全部書くには余白が足りんわ

57 of 125

Scalaならね

class A(val a: Int, var b: Int)

これで終わり

勝手にsetter(mutator), getter(accessor)が作られます

valだとaccessorのみ

varだとmutatorとaccessorの両方

58 of 125

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

59 of 125

全然Androidに入れない

キーワード

Map

Tuple

60 of 125

REPLから抜ける

:q

61 of 125

Scala + Androidの開発環境

  • Eclipse
  • IntelliJ
  • Emacs

62 of 125

Scala + Androidの開発環境(2)

  • Eclipse
    • Androidアプリの開発で多くの人が使っていると思うので,慣れでは最強
  • IntelliJ
    • Scalaのサポートがたぶん最も厚いIDE
  • Emacs
    • 使う人が使えば最強になれる?

63 of 125

Scala + Androidの開発環境(3)

  • Eclipse
    • abstractなメソッドを自動で書いてくれなかったのが残念
  • IntelliJ
    • たぶん現状最もScala + Androidが捗る
  • Emacs
    • 補完が上二つと比べるとちょっと残念

64 of 125

僕の開発手順

  1. sbtでAndroidアプリプロジェクトのテンプレートを作成
  2. IntelliJでプログラムを書く
  3. sbtでビルド

LayoutXMLはEclipseで書くと良いかも

65 of 125

sbtのインストール

https://github.com/harrah/xsbt/wiki

ここを読んでインストール

66 of 125

IntelliJのインストール

http://www.jetbrains.com/idea/

ここからダウンロード

67 of 125

IntelliJでScalaを書いてみる

68 of 125

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つが推奨されています

69 of 125

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

70 of 125

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)になる

71 of 125

戻り値の型を書くのならこんな感じ

object HelloWorld {

def main(args: Array[String]): Unit = {

println("Hello World")

}

}

72 of 125

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型になる

73 of 125

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で出力を行う

行末の;は不要

74 of 125

もはや上もScalaだけど

object HelloWorld {

def main(String[] args) {

println("Hello World")

}

}

object HelloWorld {

def main(args: Array[String]) = println("Hello World")

}

一行だけなら{}なしで=で接続できる

ifなどの例外あり

=が使用されていて,かつ戻り値の型が宣言されていない場合,型推論される

75 of 125

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")

}

76 of 125

IntelliJでScalaを書いてみる

package naruse.scala.helloworld

object HelloWorld {

def main(args: Array[String]) = println("Hello World")

}

77 of 125

簡単な関数を書いてみる

package naruse.scala.helloworld

object HelloWorld {

def main(args: Array[String]) = printHello

def printHello = println("Hello")

}

引数の無い関数,関数呼び出しは()を省略可能

78 of 125

簡単な関数を書いてみる(2)

package naruse.scala.helloworld

object HelloWorld {

def main(args: Array[String]) = println(plus(1, 2))

def plus(a: Int, b: Int) = a + b

}

79 of 125

関数渡し

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)

}

80 of 125

部分適用

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

}

81 of 125

そろそろAndroidに入ろうと思います

82 of 125

export ANDROID_SDK_HOME=~/local/lib/android-sdks

ANDROID_SDK_HOMEという名前でAndroidSDKにパスを通します

83 of 125

まずはgiter8をインストールします

https://github.com/n8han/giter8

sbt用のScalaプロジェクトのテンプレートを作ってくれます

Androidだけではなく,Scalaを使ったウェブアプリのテンプレートなども用意してくれてとっても便利です

84 of 125

curl https://raw.github.com/n8han/conscript/master/setup.sh | sh

cs n8han/giter8

ここまででインストール完了

g8 jberkel/android-app

これでAndroidのテンプレートを対話的に作成

85 of 125

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]:

86 of 125

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")

87 of 125

先程g8で作成したsbt用のAndroidアプリのプロジェクトのルートに移動して

sbt

> gen-idea

あとはIntelliJのプロジェクトを開くから開いちゃえば幸せになれるね!

88 of 125

IntelliJで開くと,Rが見つからなくて警告になっていますが,これはRが自動生成されないためで,ビルドしちゃえば生成されるのでエラーが消えます

89 of 125

sbt

apkの作成

> android:package-debug

エミュレータへのインストール

> android:install-emulator

90 of 125

HelloWorldが表示されましたか?

91 of 125

Emacsで開発したい人はensimeで,

Eclipseで開発したい人はScala-IDEで検索すると幸せになれると思います

どちらもAndroidのAPKを作るときにはsbtを使うのが良さそう

92 of 125

ソースコードを読んでみる

93 of 125

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を継承する必要はないです

94 of 125

テンプレートはちょこちょこ更新されているので,近いうちにActivity withが最初から書かれていないという状態になるかも

僕の想像です

95 of 125

class MainActivity extends TypedActivity {

override def onCreate(bundle: Bundle) {

super.onCreate(bundle)

setContentView(R.layout.main)

findView(TR.textview).setText("hello, world!")

}

}

オーバーライドするときにはoverrideが必須

96 of 125

class MainActivity extends TypedActivity {

override def onCreate(bundle: Bundle) {

super.onCreate(bundle)

setContentView(R.layout.main)

findView(TR.textview).setText("hello, world!")

}

}

findViewってなんだろう.

97 of 125

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で実装される

98 of 125

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はコンパイル時に自動生成される

99 of 125

val textView = findView(TR.textview)

しかも型推論があるため,型情報を自分で一切書かずにHogeViewのインスタンスをLayoutXMLから得られる

100 of 125

部分適用をAndroidで活用する

private static final String TAG =

Hoge.class.getSimpleName();

Log.d(TAG, "HogeHoge");

val logd = Log.d(classOf[Hoge].getSimpleName, _: String)

logd("HogeHoge")

101 of 125

trait

実装を持てるinterfaceという表現が近いかも

trait Hoge {

val DEFINE= 1

def print(s: String) = println(s)

}

102 of 125

trait + 部分適用でLog._が便利

例えばこんなtraitを用意してみる

trait Log {

val TAG = getClass.getSimpleName

val logd = android.util.Log.d(TAG, _:String)

}

103 of 125

class HogeActivity extends Activity with Log {

def onCreate(bundle: Bundle) {

logd("onCreate")

}

}

これでどこでもログが簡単に出力できるね!

104 of 125

インテントで渡されたURLをWebViewで開くことを考える

105 of 125

Javaの場合

String url = getIntent().getStringExtra("url");

if(null == url) {

mWebView.loadUrl("http://www.google.co.jp");

} else {

mWebView.loadUrl(url);

}

106 of 125

もしくはこんな感じ?

mWebView.loadUrl(getUrl());

private String getUrl() {

final String url = getIntent().getStringExtra("url")

return (null == url) ? "http://www.google.co.jp") : url;

}

107 of 125

nullチェックめんどくさいよね

かっこよくないよね(´・ω・`)

108 of 125

Scalaの場合

mWebView.loadUrl(Option(getIntent.getStringExtra) match {

case Some(u) => u

case None => "http://google.co.jp"

}

)

109 of 125

もしくはこんな感じ

mWebView.loadUrl(

Option(getIntent.getStringExtra("url")).

getOrElse("http://www.google.co.jp/")

)

一行で書きたいけど書ききれなかったので改行しています

110 of 125

暗黙的型変換

Log.d(String, String)なので,Log.d("piyo", 1)って書くと型が違うって怒られますよね

111 of 125

そこで暗黙的型変換ですよ

112 of 125

暗黙的型変換

implicit def hoge(i: Int) = String.valueOf(i)

Int型が渡されているが,String型が要求されている場合にこれが働きます

113 of 125

暗黙的型変換

implicit def int2String(i: Int) = String.valueOf(i)

~~~

Log.d("implicit", 1)

implicitな関数の名前は変換前の型名2変換後の型名とすることが多いようです

114 of 125

OnClickListenerめんどくさくない?

わかめさんに「OnClickListenerとかのためにimplicitでまくりかと思ってた」って言われるまで

115 of 125

僕はOnClickListenerとかはIDEがある程度書いてくれるから,implicitを使っていませんでした

116 of 125

implicit def func2OnClick(f: View => _) =

new OnClickListener {

override def onClick(v: View) = f(v)

}

変数名

引数の型

戻り値の型

関数渡しである

型推論によりOnClickListener型と決定する

暗黙的型変換に用いる関数である

渡された関数を実行する

117 of 125

implicit def func2OnClick(f: () => _) = new OnClickListener {

override def onClick(v: View) = f

}

view.setOnClickListener(() =>

startActivity(new Intent(getBaseContext, classOf[HogeActivity])

)

これで幸せ

118 of 125

戻り値の型を明示的に書くと() =>を省略できる

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))

})

119 of 125

implicit def func2OnClick(f: View => _) =

new OnClickListener {

override def onClick(v: View) = f(v)

}

view.setOnClickListener((v: View) =>

v.setVisiblity(View.INVISIBLE)

)

引数のViewが必要ならこう

120 of 125

これは便利ですね!

わかめさんありがとうございます

121 of 125

view.setOnClickListener(new Intent(Intent.ACTION_MAIN) {

startActivity(setClassName(app.packageName, app.name))

})

しかしこれ,IDEを使っても処理がどうなっているのか追いにくい

黒魔術かもしれない

122 of 125

テストについて

src/test下にテストを書く

これで実行

tests/android:package-debug

tests/android:install-emulator�tests/android:test-emulator

123 of 125

124 of 125

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 "./"

)

125 of 125

Scalaの問題集

S-99: Ninety-Nine Scala Problems

http://aperiodic.net/phil/scala/s-99/