scala on android
TRANSCRIPT
Транспорт Новосибирска
https://play.google.com/store/apps/details?id=net.kriomant.gortrans
https://github.com/kriomant/gortrans
〉
〉
5
Но почему?
Вывод типов
Локальные функции и типы
Кортежи и прочая функциональщина
«==» работает как положено
〉
〉
〉
〉
7
Анонимные функции
9
class Items { def mapSum(func: Item => Int): Int = { var sum = 0 for (item <- this.items) sum += func(item) sum }}
mapSum(item => item.weight)mapSum(_.weight)mapSum { item => item.weight}
Анонимные функции
Ну лямбды, а где их использовать?
10
val shownRoutes = infoWindowMarkers .filter(_.isShown) .map { m => val info = vehicleMarkers(m).info info.routeId }
Анонимные функции
Больше сахара!
11
val shownRoutes = for { m <- infoWindowMarkers if m.isShown info = vehicleMarkers(m).info} yield info.routeId
Анонимные функции
Изменяемый scope
12
val items = Array(1, 2, 4)var sum = 0items.foreach { item => sum += item}
Java: null
14
Route getRoute(String name, Integer order);
String getSchedule(String route) {Route route = getRoute("13a", null);String schedule = null;if (route = null) { schedule = route.getSchedule() }return schedule;}
Scala: Option
15
def getRoute(name: String, order: Option[Int]): Option[Route]
def getSchedule(route: String): Option[String] = {getRoute(route, None).map(_.schedule)}
for { first <- getOptValue1() if first >= 0 second <- getOptValue2()} yield first + second
Java: read-only collections
16
List<String> getStops() { return Collections.unmodifiableList(this.stops);}
List<String> list = route.getStops();list.add("stop");// BOOM! UnsupportedOperationException
List<String> getStops() { return new ArrayList<String>(this.stops);}
Scala: read-only interface / immutable collection
17
def getStops: collection.Seq[String] = stops
val stops = route.getStopsstops += "stop"// error: value += is not a member of Seq[String].
val extendedStops = stops :+ "stop"
Scala: immutable by default
18
collection.List
collection.immutable.List = List
collection.mutable.List
Simple Java class
21
class File { String name; long size; bool executable;
File(String name, long size, bool executable) { this.name = name; this.size = size; this.executable = executable; }
@Override bool equals(Object other) { … }
…
Initializers
22
class File(name: String, size: Long, executable: Boolean) { def getName: String = name def getSize: Long = size def isExecutable: Boolean = executable}
val file = new File("autoexec.bat", 1337, false)println(file.getName)
Initializers
23
class File(val name: String, val size: Long, val executable: Boolean)
val file = new File("autoexec.bat", 1337, false)println(file.name)
Case class
24
case class File(name: String, size: Long, executable: Boolean)
val file = File("autoexec.bat", 1337, false)println(file.name)val updated = file.copy(size = 1400)
file match { case File("autoexec.bat", _, _) => nostalgia.on() case File(name, _, _) => ordinary_file()}
Mixins: добавляем NavigationDrawer
27
@Override public void onCreate(Bundle savedInstanceState) { mDrawerToggle = new ActionBarDrawerToggle(…);}@Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); mDrawerToggle.syncState();}@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mDrawerToggle.onConfigurationChanged(newConfig);}
@Override public boolean onOptionsItemSelected(MenuItem item) {
Mixins: добавляем NavigationDrawer
28
trait HavingSidebar extends Activity { // Interface var drawerLayout: Int def onDrawerShown(): Unit def onDrawerHidden(): Unit
// Implementationoverride def onCreate(savedInstanceState: Bundle): Unit = { super.onCreate(savedInstanceState) mDrawerToggle = new ActionBarDrawerToggle(…)}var mDrawerToggle: ActionBarDrawerToggle = null …
Mixins: добавляем NavigationDrawer
29
class MyActivity extends Activity with HavingSidebar { var drawerLayout: Int = R.layout.drawer
override def onDrawerShown(): Unit = { … }
… }
Curried function: closing
31
type Closable = {def close()}
def closing[R <: Closable, T](resource: R)(block: R => T): T = { try { block(resource) } finally { if (resource != null) resource.close() }}
closing(db.query(…)) { cursor => … }
Curried function: edit
32
def edit(sp: SharedPreferences) (block: SharedPreferences.Editor => Unit) = { val editor = sp.edit() block(editor) editor.commit()}
edit(prefs) { editor => editor.putLong(…) editor.remove(…)}
Call-by-name-evaluation: retry
33
def retryOnceIfEmpty(f: => String): String = { val res = f if (res.nonEmpty) res else f}
val jsonStr = retryOnceIfEmpty { client.fetchRouteJson(id)}
Макросы: sqlite.insert
35
def addEvent(timestamp: Date, kind: String, ref: Option[Long], data: String): Long = { import EventsTable.Columns._ val values = new ContentValues values.put(DATE_TIME, timestamp.getTime) values.put(KIND, kind) values.put(REF, ref.orNull) values.put(DATA, data) db.insertOrThrow(EventsTable.NAME, null, values)}
Макросы: sqlite.insert
36
def addEvent(timestamp: Date, kind: String, ref: Option[Long], data: String): Long = { import EventsTable.Columns._ EventsTable.insert( DATE_TIME -> timestamp.getTime, KIND -> kind, REF -> ref, DATA -> data )}
Макросы: inject view
37
class MyActivity extends AppCompatActivity { override def onCreate(savedInstanceState: Bundle): Unit = { super.onCreate(savedInstanceState)
setContentView(R.layout.account_list_activity) AutoInjector.injectViews() … }
@InjectView(R.id.account_list) private[this] var accountList: ListView = _}
Макросы: inject view
38
def injectViewsImpl(c: Context)(): c.Expr[Unit] = { val selfClass = c.internal.enclosingOwner.asMethod.owner.asType val theType = selfClass.toType val injections = for { decl <- theType.decls annotation <- decl.annotations.find { a => a.tree.tpe =:= c.weakTypeOf[InjectView] } } yield { ... q"""this.$decl = this.findViewById($id).asInstanceOf[$viewType]""" } c.Expr[Unit](q"{..$injections}")}
Implicits: преобразование типов
40
okButton.setOnClickListener(new OnClickListener { def onClick(view: View) { doSmth() }})
Implicits: преобразование типов
41
implicit class Function0ToOnClickListener(f: () => Unit) extends OnClickListener {
override def onClick(v: View): Unit = f()}
okButton.setOnClickListener { () => doSmth() }
Implicits: extension methods
42
implicit class ViewExt(view: View) {
override def onClick(block: => Unit): Unit = { view.setOnClickListener(new OnClickListener { def onClick(v: View) { block } }) }
}
okButton.onClick { doSmth() }
Implicits: неявные параметры
43
def du(arg: Int, logger: Logger): Foo = { … }def duHast(arg: String, logger: Logger): Bar = { … }def duHastMich(foo: Foo, bar: Bar, logger: Logger): Baz = { … }
val logger = Logger(…)val baz = duHastMich(du(2, logger), duHast("yes", logger), logger)
Implicits: неявные параметры
44
def du(arg: Int)(implicit logger: Logger): Foo = { … }def duHast(arg: String) (implicit logger: Logger): Bar = { … }def duHastMich(foo: Foo, bar: Bar) (implicit logger: Logger): Baz = {…}
implicit val logger = Logger(…)val baz = duHastMich(du(2), duHast("yes"))
SBT + Android
Плагины:
https://github.com/jberkel/android-plugin (устарел)
https://github.com/pfn/android-sdk-plugin
〉
〉
48
Пример конфигурации SBT
50
lazy val core = Project("core", file("core"), settings = Defaults.defaultSettings ++ Seq( apiLevel := 15, platformName in Android := s"android-${(apiLevel in Android).value}", useProguard in Android := true, proguardOption in Android ~= { _ + " -dontnote scala.** " } libraryDependencies += "org.ccil.cowan.tagsoup" % "tagsoup" % "1.2", …
Gradle
Популярная система сборки для Java ине толькоhttp://gradle.org
Android-плагин поддерживается Google
Есть сторонний Scala-плагин
〉
〉
〉
51
Gradle
http://developer.android.com/intl/ru/tools/building/plugin-for-gradle.html
https://github.com/saturday06/gradle-android-scala-plugin
52
Конфигурация Gradle
53
buildscript { dependencies { classpath 'com.android.tools.build:gradle:1.3.1' classpath 'jp.leafytree.gradle:gradle-android-scala-plugin:1.4' }}apply plugin: 'com.android.application'apply plugin: 'jp.leafytree.android-scala'
android { compileSdkVersion 'android-23' buildToolsVersion '23.0.1'
defaultConfig {
IntelliJ IDEA
Поддерживает SBT-проекты
Поддерживает Gradle-проекты
Официальная IDE для Android
Официальный Scala-плагин от JetBrains
〉
〉
〉
〉
56
IntelliJ IDEA: радости
Автодополнение, включая implicits
Отладка, поддерживается Scala-специфика
Рефакторинг
Fast Scala Compiler — демон компиляции
〉
〉
〉
〉
62
IntelliJ IDEA: печали
Сброс настроек, не поддерживаемых системой сборки
Scala SDK
Настройки путей
〉
〉
63
Переопределение vararg-методов
https://issues.scala-lang.org/browse/SI-1459
65
public abstract class AsyncTaskBridge<Progress, Result> extends AsyncTask<Void, Progress, Result> { @Override public Result doInBackground(Void... params) { return doInBackgroundBridge(); } protected abstract Result doInBackgroundBridge(); …
Доступа к protected static членам
https://issues.scala-lang.org/browse/SI-1806 (закрыт)
ItemizedOverlay, View.mergeDrawableStates
66
abstract public class ViewStaticBridge extends View { public ViewStaticBridge(Context context) { super(context); }
public static int[] mergeDrawableStates(int[] baseState, int[] additionalState) { return View.mergeDrawableStates(baseState, additionalState); }}
Parcellable::CREATOR
67
case class User(age: Int, name: String) extends Parcelable { protected this(source: Parcel) = this(source.readInt(), source.readString()) override def describeContents() = 0 override def writeToParcel(destination: Parcel, flags: Int) { … }}
object User{ override val CREATOR = new Parcelable.Creator[User] { override def createFromParcel(source: Parcel) = new User(source) override def newArray(size: Int) = new Array[User](size) }}
65K Reference Limit
Scala поможет быстрее достичь высот!
68
Conversion to Dalvik format failed:Unable to execute dex: method ID not in [0, 0xffff]: 65536
65K Reference Limit
Стандартная библиотека: много классов, много методов;в 2.10 стандартную библиотеку разделили
ProGuard нужен даже для debug-сборок
Multi-DEX — не использовал
linearAlloc bug
Размер gortrans — 1,5 Мб
〉
〉
〉
〉
69
ProGuardТребуется несколько настроек, чтобы не вырезалисьнужные части стандартной библиотеки
〉
70
-dontwarn scala.concurrent.forkjoin.**-dontwarn scala.concurrent.impl.AbstractPromise-dontwarn scala.concurrent.util.Unsafe-dontwarn scala.reflect.**
# See https://issues.scala-lang.org/browse/SI-5397-keep class scala.collection.SeqLike { public protected *;}…
Скорость компиляции
Scala компилирует медленнее Java в 2-10 раз
Частично проблема решается FSC (Fast Scala Compilerdaemon) и sbt incremental compilation
Усугубляется необходимостью запуска ProGuard (можнорешить предустановкой Scala в эмуляторе)
〉
〉
〉
71
Потребление ресурсов
Scala провоцирует использовать много ClosuresНо никто не мешает использовать Java-like код вкритичных точках
Макросы позволяют создавать zero-cost абстракции
〉
〉
72
Библиотечные зависимости
Source-level совместимость между минорными версиямиScala
Требуется пересборка библиотек для каждой минорнойверсии
〉
〉
73
Что мы получаем
Более выразительный язык
Более типизированный
Компактный код
Дополнительные возможности переиспользования кода
〉
〉
〉
〉
75
Чем за это расплачиваемся
Медленная сборка
Поддержка новых возможностей системы сборки Androidзапаздывает
〉
〉
76