自作JVM言語Karaffeについて
nokoです。2014年ぐらいから趣味でKaraffe(カラフェと読みます)という自作のJVM言語を開発しています。
この記事は、Karaffeの開発のきっかけと、何をゴールとしているのか?をまとめます。
細かい実装についてはほとんど触れないのでまた別の機会に。
Karaffeとは
Karaffeは、オブジェクト指向(クラスベース)の静的型付けなプログラミング言語です。
Javaバイトコード(classファイル)を生成するので、Java VMで実行することができます。
実行用のコマンドを持っていないので、生成されたclassファイルをそのままjavaコマンドで実行します。
何回か文法の再設計レベルの書き直しをしており、現在も書き直している最中なので、今は文法チェッカのみ存在します。
Karaffe開発のきっかけ
(おそらく)全てはぱらつり氏のこのツイートから始まりました。
プログラミング言語notepad?(?)
— ぱらつり (@paralleltree) 2014年8月30日
このツイートと、その場の雰囲気のノリと勢いでPaLTreeというOrganizationが作られました。 色んな人がいてワイワイと開発やら議論をしていました。(確認したらメンバーは9人も居ました。意外と多かった。。)
Karaffeの前身 = notepad-java
PaLTreeでは「メモ帳言語(notepad-lang)」という枠で様々な実装が出てきました。その中で、notepad-langをJavaで実装したのが私でした。(Javaの他にC#やRubyによる実装がありました)
メモ帳でも書けると言っても普段の環境と違いすぎてあんまりピンと来ないかもしれませんが、現代のプログラミングでは当たり前となっている機能がありません。 例えば、シンタックスハイライトや補完はもちろん、オートインデントも無いのでインデントをしようものならTabやSpaceを連打する必要があります。 なので、インデントやハイライトが無くとも読みやすく、補完なしで全部手打ちしても記述が可能である必要がありました。
その結果生まれたのが、インタプリタにより実行するシンプルな(?)手続き型言語でした。 メモ帳で記述することを前提に考えられていたのと、コンパイラもインタプリタも開発したことが無い中での設計だったので、とても奇抜な仕様が爆誕したのは言うまでもありません。
notepad-javaが持っていた仕様の中でも、他のメモ帳言語にも無い一番ユニークだった仕様は「{}や()を使っても入れ子になるとメモ帳だと読みにくいので、改行2つでブロックを直列的に構成する」という仕様だったと思います。
このあたりを記した当時のドキュメントは、現在のnokok/Karaffeには入っていません。 動的言語/静的言語、静的型付け/静的型付け、インタプリタやら何やらを混同したような記述が多数あり、かつ実装もほぼ無く恥ずかしいのでここには載せません。
notepad-langから派生したり、触発されて開発されたプログラミング言語の中で、現在まで開発が続いているのはKaraffeのみです。 他にはLuryというプログラミング言語がありますが、現在開発が休止しているようです。
Karaffeの誕生
手続き型 -> 関数型言語っぽいものにするか? -> やっぱオブジェクト指向だな、という迷走試行錯誤を経て今の形になりました。
この試行錯誤の過程で、notepad-javaは早々にKaraffeという名前になりました。
Karaffeになった頃にはメモ帳の要素は薄まり、だんだん自分が作りたい言語に寄ってくるようになりました。
このKaraffeのロゴは、コーヒーがカラフェに貯まって揺らいでいる様子を表現しています。(本来カラフェにコーヒーは入れないような気もするのですが気にしないようにします)
Karaffeが目指しているもの
Karaffeは、Javaにはまだ存在しない現代的な文法や仕様を取り入れながら、よりシームレスなJavaとの相互運用性を実現することを第一に目指しています。
(主語が大きい気がしますが)既存のJVM言語は「JVM言語からJavaを使うこと」は考えていても、「JavaからそのJVM言語を使うこと」はあまり前提とされていない感じがしています。真の 相互 運用性を考えたときに「対象のライブラリがJava製なのか他のJVM言語製なのか」を考えなくても良いというのが理想像ではないかと考えています。 Karaffeが吐き出すクラスファイルやjarファイルが、(たとえKaraffeにしか無い言語仕様を使ったとしても)Karaffe製であることが言われないと分からないレベルにまで、Javaにとって極力自然なものとなるように作るというのが今の所の方針です。
そもそもKaraffeのような個人開発の言語は、普及して一般の人にまで使ってもらえることは通常考えにくいと思っています。既存の言語を置き換える意図もありません。なので、あくまで自分だけが使う(普段使っている)Javaと組み合わせることを前提とした高度な相互運用性を持つ言語にしたいと思ったのもあります。
Karaffeはだいぶ後発の言語なので、Javaのモジュールシステムにネイティブで対応したいなぁとか色々ありますが、まずは相互運用性が第一で、他は二の次、三の次です。とりあえずはシンプルなオブジェクト指向言語にできればと思っています。
Karaffeの今
今のKaraffeは多分5回目ぐらいの書き直しをしたバージョンです。 「ぐらい」と言ったのは、それだけ何回も失敗して書き直しているために覚えてないからです。 今のバージョンになったのは2018年11月です。半年弱ですね(今回は歴史ごと飛ばして書き直しましたのでfirst commitです) first commit · nokok/Karaffe@0d62de1 · GitHub
夢はでかいのですが、コンパイラを開発できるレベルの知識もなければ実装力も全然ついてきてないので、書き直した実装の中でまともに動作するものを作り出せた試しがありません。 まだまだ絵に描いた餅状態なので、どうにかしていきたい感じですね。
初期のバージョンはJavaCCとか色々使ってましたが、今はレキサ/パーサーにANTLRv4を使用しています。
この記事はこのぐらいにしておきつつ、今後はKaraffeのことをもっと書いていきたいと思います。
Javaのinstanceofについて言語仕様を見てみる
Javaにおけるinstanceof
について言語仕様がどうなっているかを調べた。
文法上の話
instanceof演算子 (引用 https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.20.2 )
RelationalExpression: ShiftExpression RelationalExpression < ShiftExpression RelationalExpression > ShiftExpression RelationalExpression <= ShiftExpression RelationalExpression >= ShiftExpression RelationalExpression instanceof ReferenceType
これを見ると、instanceof
は左辺に式、右辺に参照型を受け取ることと、関係演算子と同等の優先度として処理されることがわかる。言語仕様を読む限り、左側に記述する式は参照型にキャスト可能である必要がある。
キャスト不可能な場合は、instanceof
の右側は常に参照型を受け取ることから、式を評価しても常にfalse(=デッドコード)となるためエラーとしている模様。つまり1 instanceof Integer
は(RelationalExpression > ... > Literal > IntegerLiteralという規則が適用できるため)文法上パースはできるものの、コンパイル時エラーとなる。
ここでオートボクシングは効かない。 また、instanceof
は当然実行時に判定が行われるが、Java VMでバイトコードが実行される際に全ての型で完全な型情報を利用できるわけではない。そのため、instanceof
演算子に記述する参照型は、実行時に完全な型情報を利用できる具象化可能型(Reifiable Types)である必要がある。具象化可能型であるには、次の6つの条件のうち1つ以上満たす必要がある。
例
class A<T> { } var a = new A<Integer>(); a instanceof A<?> // true a instanceof A<Integer> //エラー。 A<Integer>は非具象化可能型 a instanceof A<Object> //エラー。 a instanceof A<? extends Object> //エラー。 境界も記述できない。
- ネストされた型で、
.
で区切られたそれぞれの型が、全て具象化可能型である
例
class A<T> { class B {} } var a = new A<Object>() var b = a.new B() b instanceof A<?>.B // これは多分こういうことだと思われる。A<?>とBはどちらも具象化可能型なので比較可能。結果はtrueとなる b instanceof A<Object>.B // エラー。A<Object>は非具象化可能型
ここまでが文法上の話。
instanceofのバイトコード
次のコードは、
Integer i = 1; if(i instanceof Integer) { // 処理A } else { // 処理B }
次のようなバイトコードに変換される
0: iconst_1 1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 4: astore_1 5: aload_1 6: instanceof #3 // class java/lang/Integer 9: ifeq 23 // 処理A 20: goto 31 // 処理B 31: return
Java上のinstanceof
は、バイトコードに変換されてもそのままinstanceof
となる。
Java VMが実行するバイトコードのinstanceof
は、スタックの一番上に積まれている参照の型(この場合iの型)と、(コンパイル時にinstanceofに渡された)java.lang.Integer
を比較する。
型の比較
https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-6.html#jvms-6.5.instanceof
s instanceof T
という式で、s
の型をS
としたときに次のいずれかの条件を満たせばこの式の結果はtrue
となる。
1. T
と S
がどちらもクラス型で、 S
がT
と同じクラスまたはT
のサブクラスである
Number s = 1; s instanceof Integer // true. SはNumber(=クラス型)であり、Tが同じIntegerである s instanceof Number // true. IntegerはNumberのサブクラスである s instanceof String // false. IntegerはStringのサブクラスではない
2. T
がインターフェース型のとき、S
は インターフェースT
を実装している
List<String> s = new ArrayList<>(); s instanceof List<?> // true. ArrayListはListを実装している s instanceof Set<?> // false. ArrayListはSetを実装していない
3. S
が配列型で、T
がクラス型であれば、T
はObject
である
Object s = new Integer[1]; s instanceof Object // true. Sが配列型かつTはクラス型で、TがObjectである。 s instanceof Integer // false. Tはクラス型であるが、Objectではない。
4. S
が配列型で、T
がインターフェース型であれば、T
が配列によって実装されているインターフェースである
配列によって実装されているインターフェースというのはつまりCloneable
とSerializable
のインターフェースのことである。
Object[] s = new Integer[1]; s instanceof Cloneable // true. Cloneableは配列によって実装されている s instanceof java.io.Serializable // true. Serializableも配列によって実装されている s instanceof List<?> // false. 配列はListを実装していない
5. S
とT
が配列型のとき、配列型 T
と 配列型 S
の要素の型が同じプリミティブ型である
Object s = new int[0]; s instanceof int[] // true. TとSがどちらも配列型であり、要素の型がintである s instanceof float[] //false. 要素の型が異なる。
6. S
とT
が配列型かつ要素の型がどちらも参照型のとき、要素の型がS
から T
へ実行時にキャスト可能である
Object s = new Integer[1]; s instanceof Integer[] // true. IntegerからIntegerは同じ型のためキャスト可能である s instanceof Number[] //true. IntegerからNumberはサブタイプの関係にあるためキャスト可能である s instanceof String[] //false. IntegerからStringはキャスト不可能である。
以上6条件が、instanceof
でtrueが返る条件となっている。
それ以外の場合
上記の条件が適用されるのは、t
が非nullかつS
の型が解決された後となる。
逆に言うと、t
がnullのときにはinstanceof
はS
の型解決すら行わずにfalse
をプッシュする。(※もちろんコンパイル時には解決できている必要がある。)
instanceofって式がnullのときは型検査せずに固定でfalseをpushするから、classファイルを削除してもエラー落ちしないの面白いな。 pic.twitter.com/tSZcoM47hj
— noko🐶 (@noko_k) January 13, 2019
このように、判定する式がnullであれば、実行時にそのクラスが存在するかどうかは(JVMにとっては)関係ないことであることがわかる。
もっと深い話
よくよく思い出したらJJUG CCCでセッションがあった。 Java VMの実装にまで踏み込んだ深い話。
www.slideshare.net
まとめ
比較する型はなんでも良いわけではない(参照型かつ具象化可能型である必要がある)ことがわかってよかった。 一つ一つの判定条件は考えてみれば当たり前かもしれないが、このように言語化できるとすっきりする。
参考文献
バックエンドエンジニアが事前知識なしでReact Native(+ Expo)に入門して詰まったところ
この記事は?
ウェブクルー Advent Calendar22日目の記事です。
普段はJavaを書いているバックエンドエンジニアが、事前知識なしでReact NativeにExpoで入門したときに詰まったところについて振り返ります。
この記事に書いてあるものは正解じゃないものも混じってそうなのですが、基本的にはこんな感じの気持ちでJSを書いています。※ あと、英語のドキュメント頑張って読めば書いてあることがほとんどです。積極的にリンクを貼っておりますのでそちらを確認してください。
誰向け?
私みたいな人
- JavaScript自体はそれなりに書けて、npmぐらいならJavaScriptのツールが使える
- プログラミングスキルはそれなりにある(他の言語とかで)
- ターミナルの扱い自体は大丈夫
- JSがっつり書いたことない
- React NativeどころかReact自体ほとんどやったことない
そもそも
セットアップ
ExpoのGetting started的なページがあるので、この通りセットアップすれば 5番目の expo start
の手順でもうアプリが動く状態になっています。
Simulatorとかのインストールがまだなのであれば実機で確認するのがアプリを入れてQRコードを読み込むだけなのでお手軽です。Expo Clientをインストールしましょう。AndroidとiOS両方いけます。
JSX is 何
React Nativeを始めて最初に躓くのがJSXだと思います。こういうやつです。
最初はJSXを「JavaScriptに埋め込めるつよいHTML」ぐらいにしか考えていなかったのですが、このJSXコードはBabelによって関数の呼び出しにトランスパイルされます。
とてもシンプルですね。入れ子になっても基本的にはこの形になると思います。
なんとなくでプロジェクトを作成すると、テンプレ的に入っているJSXやら何やらが急に押し寄せてきて、「なんかよくわからんけど動いてる〜!」という感じになってしまうのですが、ただの関数呼び出しと考えればだいぶハードルは下がると思います。
このあたりの話は、 JSX In Depthに書いてあります。英語ですがコードが多いので雰囲気はつかめるでしょう。 JSXの文法的なところでうまくいかないときはこのドキュメントを読むことで解決することが多いです。
開発環境は何を選ぶ?
Reactの場合、ES Modulesと呼ばれるモジュールシステムを活用したコードを記述する必要があります。
私は普段、JavaScriptは(シンタックスハイライトと簡単補完が効くぐらいの)ほぼ素のVimで書ける程度のJavaScriptしか書きませんが、importやらJSXやらを記述するとなると、素のVimだとかなりのつらみがあります。平成もあと数カ月で終わるというのに、importしたいものを人力で探すという行為はもはや人間がする仕事ではありません。
なので、私はReact NativeでコーディングするときはWebStormを使うようにしました。Vimの環境を汚したくなかったのと、Javaを書くときはIntelliJ IDEAを使っていたためです。プラグインとかを新たに入れなくともデフォルトで補完とかが効いたような気がします。当たり前ですがあると便利です。WebStormは有償ですが、無償のものを選ぶのであればVS Codeとかでも良いと思います。
react-native linkの謎
セットアップ手順にreact-native link
をしているnpmモジュールがありますが、Expoだとnpm installすればだいたいすぐに使えるので、Expoが持っている機能でアプリを作っているうちは特に気にする必要は無いのかなぁと思います。(というより、expoを普通にセットアップした場合react-native
コマンドはPATHに居ないですし。。)
逆に言うと、Native Modulesを使いたいときには意識する必要があります。そのときはこのあたりから参照しましょう。
Detaching to ExpoKit - Expo Documentation
画面の作り方
Button
よりも TouchableOpacity
Button
に良い感じにスタイルを当てたかったのですが(サイズを変えたり画像を入れたり。あとはテキストを小文字にしたかった)あまりうまく行かなかったですね。
どうやらこの Button
には結構制約があるらしく、ちょっと凝ったことをするならば TouchableOpacity
が推奨されているみたいです。ちょっと込み入ったことをしたときはTouchableOpacity
、そうでないときは Button
のシンプルさがマッチします。
React Nativeの画面遷移
画面遷移は react-navigation
を使います。
使い方はこのあたりにドキュメントがあります。
例ですがApp.jsが下記のようにデフォルトのまま記述されているとして、分解してゴニョっとやるとすぐに画面遷移が使えるようになります。
元のApp.js
書き換えて画面遷移が出来るようになったもの
React Nativeにおいて画面のコンポネントはScreenで終わる名前にするのが良いらしいです。 App.jsの中に記述されていたコードが、screen/HomeScreen.jsに移動したことがわかると思います。 具体的なソースコードは長くなるので割愛しますが、遷移先の画面を用意すれば画面遷移ができる状態になっています。
SwitchNavigatorやStackNavigatorを使ったときにどのような画面遷移が実現できるかはNavigationPlaygroundというアプリを動作させることでイメージがつかめると思います。 実機確認するときと同じようにQRコードを読ませると動きます。
バックエンド
React NativeでのRESTful APIコール
何かJSONを返すようなAPIを叩きたいときはaxios使うと通信できます。Promiseベース!便利ですね。
READMEにも同じようなものが載ってますがこんな感じです。
Firebaseの利用
Firebaseを使いたいときは、とりあえず npm install firebase
をすれば使えるようになります。
react-native-firebaseとかいろいろあってどれを使うべきか迷いますが、今の所firebaseであまり困ってないですね。。
Expoを使ったアプリ開発の場合、実行しているのはNode.jsで動作するJavaScriptでFirebaseのライブラリを動作させるため、参照すべきドキュメントは、ウェブで使ってみるのドキュメントです。
https://firebase.google.com/docs/web/setup
ローカルストレージの利用
バックエンドではないですが、端末にあるローカルのストレージを使うには、 AsyncStorage
が使えます。
WebStorageに近いAPIが使えます。値にオブジェクトを突っ込むと壊れるっぽいので、JSON.stringify
で雑にJSONにシリアライズして雑に setItem
してやると雑に良い感じになります。
ただ、考えて使わないとグローバル変数化して頭悪い感じになりそうです。
なんかおかしくなった
Expo(expo start
している方)がおかしそうなとき
expo start
するときに、 --clean
オプションを付けることでキャッシュをクリアできます。
expo start --clean
みたいな感じです。
あとは node_modules
を消して npm install
し直すとうまく行くことがあります。
Expo Client側が原因っぽいとき
Expo Client側が何かおかしいとき(一般に公開されているものも使えないとか)は、アプリのデータを削除するとうまく行ったりします。
よくわからないとき
シュミレーター開いてたら閉じて(完全に閉じる)リセットしつつ、node_modulesを削除して、コーヒーとか飲んでからもう一度npm install すればなんかよくわからないときもなんとかなります。ならなかったらすみません。
まとめ
たまに赤い画面が出ることもありますが、だいたいググって気合いでどうにかなる感じです。よくわからないときは
/)
///)
/,.=゙''"/
/ i f ,.r='"-‐'つ___ まぁなんでも動きゃあいいんだよ!!!
/ / ,.-‐'~/⌒ ⌒\
/ ,i ,二ニ⊃( ●). (●)\
/ ノ il゙フ::::::⌒(人)⌒::::: \
,イ「ト、 ,!,!| |r┬-| |
/ iトヾヽ_/ィ"\ `ー'´ /
というマインドでどうにかしますが。 React Nativeみたいな、目の前で画面が組み上がっていくものを作っていくのは楽しいですね。
普段はJavaとかですが、React Native(+ Expo)は新しいものをもっともっと触っていきたいなぁという気持ちにしてくれます。