HaskellでAndroidアプリを書いてみた

Yuji Yamamoto (山本悠滋)

2017-11-18 DevFest Shikoku 2017

はじめまして! (^-^)

宣伝 hask(_ _)eller

今日の話

やったこと・失敗したこと

動機:

顧客が本当に必要だったもの

顧客が本当に必要だったもの

それだけ!

やったこと・失敗したこと

やったこと・失敗したこと

💡そういやこんな記事があった!

Etaって?

やったこと・失敗したこと

ほぼほぼ

  1. ⌨️書いてみる
  2. 😵Etaのバグにハマる
  3. 📣GitterやGitHubで報告
  4. 🔨直してもらう

やったこと・失敗したこと

😂いろいろ乗り越えてさあAndroidからGitHubのAPIを呼ぶぞ!というところで…

やったこと・失敗したこと

_人人人人人人人人人_
> 突然のProguard <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y ̄

やったこと・失敗したこと

やったこと・失敗したこと

一応のデモ

省略します… hask(_ _)eller

やればよかったかもしれないもの

所感・反省

おしまい

… というのは悔しいので、予告通りEtaとHaskellのお話をします!😤

先にまとめ

前提知識

題して!

雰囲気だけは分かる!
Etaを知るためのHaskellの基本!

コメントの書き方

-- これが行コメント。ここから先の例では主にこれを使う
{-
  これがブロックコメント。
  この後出てくる言語拡張を宣言するときにも使う
-}

型注釈

型注釈

aPrime :: Int
aPrime = 9109

message :: String
message = "I love Eta!!"

型注釈

型注釈

func :: Int -> Int
func x = x + 1

ジェネリクスと型引数

// Java!
Set<T>
-- Haskell!
Set a

ジェネリクスと型引数

ジェネリクスと型引数

// Java!
Set<Integer>
-- Haskell!
Set Int

ジェネリクスと型引数

// Java!
Map<K, V>
-- Haskell!
Map k v

ジェネリクスと型引数

// Java!
Map<String, Integer>
-- Haskell!
Map String Int

型クラス

class SomeTypeClass a where
  method :: a -> Bool

型クラス

記号関数

記号関数

Monadとdo記法

「文」的なもの?

「文」的なもの?

Javaで言えば…

「文」的なもの?

Javaで言えば…

「文」的なもの?

「文」的なもの?

do記法とMonad

do記法を使った例

do記法とMonad

do -- <----------------------- do記法の始まり
  putStrLn "君の名前は?"   -- この行がIO Monad
  name <- getLine           -- この行もIO Monad
  putStrLn "いい名前だね!" -- この行もやっぱりIO Monad

do記法とMonad

一般化するとこんな感じ

do
  result <- doSomeMonad
  moreMonad
  anotherMonadWith result

do記法とMonad

Monadを実装した型によって、doに書いた各「文」と「文」の間でできることが異なる。

do
  result <- doSomeMonad
  moreMonad
  anotherMonadWith result

do記法とMonad

つまり、

do記法とMonad

例えば…

do記法とMonad

(再掲)Monad型クラスに属する型によって、do記法の中の「ここ!」でできることが違う!

Foreign Function Interface (FFI)

Foreign Function Interface (FFI)

本物のプログラマはHaskellを使う - 第22回 FFIを使って他の言語の関数を呼び出す:ITpro
から拝借した例を一つ。

Foreign Function Interface (FFI)

例えば、C言語のmath.hで定義されているsin関数を呼びたいとき。

foreign import ccall "math.h sin" c_sin
  :: Double -> Double

Foreign Function Interface (FFI)

逆にplusOneというHaskellの関数をC言語で呼び出せるようにしたいとき

foreign export ccall "plusOne" plusOne :: Int -> Int

Haskellの標準とGHCの言語拡張

例えば、冒頭でも紹介した、Etaのサンプルを開くと…

Haskellの標準とGHCの言語拡張

Haskellの標準とGHCの言語拡張

例えばGHCで…

{-# LANGUAGE MultiWayIf #-}

Haskellの標準とGHCの言語拡張

{-# LANGUAGE MultiWayIf #-}

...

if | x == 1    -> "a"
   | y <  2    -> "b"
   | otherwise -> "d"

Haskellの標準とGHCの言語拡張

なんだか闇魔法感がありますが!

Haskellの標準とGHCの言語拡張

などなど

ここまでのまとめ

もっと知りたい方は

もっと知りたい方は

もっと知りたい方は

繰り返しになりますが…

閑話休題

Haskellで型を定義する方法

例: Intをラップしただけの型SomeType

data SomeType = SomeType Int

EtaでJava Wrapper Typeを定義する方法

例: おなじみAndroidのActivityクラスを使用したいとき

data Toast = Toast @android.widget.Toast deriving Class

EtaでJava Wrapper Typeを定義する方法

継承関係を表現したいとき

😞Type Familiesとかの説明がだるいのではしょるか…。

EtaでJavaのメソッドを import する

引数をとらないメソッドの場合

foreign import java unsafe "show"
  showToast :: Java Toast ()

Java Toast () とは?

Java Monad

Java Toast () とは?

Java Monadとは?

引数を受け取るメソッドを import する

foreign import java unsafe "setDuration"
  setDuration :: Int -> Java Toast ()

staticメソッドを import する

foreign import java unsafe "@static android.widget.Toast.makeText"
  makeText :: Context -> String -> Int -> Java a Toast

staticメソッドを import する

foreign import java unsafe "@static android.widget.Toast.makeText"
  makeText :: Context -> String -> Int -> Java a Toast

ここではJava Monadの二つ目の引数がToastであることから分かるとおり、Toastを戻り値の型として返す

staticメソッドを import する

👇使うときはこう

do
  toast <- makeText someContext "Hello, Widget written in Eta!" 1
  ...

Java Monadを使ってJavaのメソッドを呼ぶ

do
  ...
  toast <.> showToast
  toast <.> setDuration 0

Haskellの関数をJavaで呼べるようにする

foreign export java "@static info.igreque.keepmecontributinghs.KeepMeContributingWidgetProviderHs.onUpdate"
  onWidgetUpdate :: Context -> Java a ()

Haskellの関数をJavaで呼べるようにする

実際に私が実装した関数(一部改変)

onWidgetUpdate :: Context -> Java a ()
onWidgetUpdate c = do
  toast <- makeText c "Hello, Widget written in Eta!" 1
  toast <.> showToast

Haskellの関数をJavaで呼べるようにする

Java側ではこう呼ぶ

import info.igreque.keepmecontributinghs.KeepMeContributingWidgetProviderHs;

...
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    KeepMeContributingWidgetProviderHs.onUpdate(context);
}

まとめ