DataBinding + RxJavaでMVVMパターンな設計を考える
今更感がすごいが、DataBindingを使うことによってAndroidアプリケーションの実装でMVVMパターンな設計を考えやすくなったし、DroidKaigi 2017のアプリがMVVMで実装されていたりするので、自分なりに設計をまとめてみる。
全体図
他で実装されている記事を見るとDDDなりと混ぜ合わせた感じの設計がちらほら見えて、一番シンプル(かつ集合知的な知見が溜まっている)と感じたDroidKaigi/conference-app-2017のアーキテクチャを丸パクリする形になった。
何をしているかざっと書くと
- View
- Activity/Fragment/Adapter ItemといったViewは1対1で対応するViewModelを持つ
- 各Layout XMLには対応するViewModelをDataBindingでbindする
- ViewModel
- Viewの要素をクリックしたときの処理の定義やAPI/DBとのModelのやり取り、Viewへの反映を行う
- ViewModelが取り扱うRepositoryはDagger2のDIを用いてインジェクトして利用する
- Repository
- Local/RemoteDataSource
- 実際にModelのCRUD操作を行う
- DataSourceからViewModelまでの処理はRxJavaでストリーミングに扱う
という感じ。
View
Viewは対応するViewModelをInjectして、Layout XMLにbindする。
public class MainActivity extends BaseActivity { @Inject MainActivityViewModel viewModel; private ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getComponent().inject(this); bindViewModel(viewModel); binding = DataBindingUtil.setContentView(this, R.layout.activity_main); binding.setViewModel(viewModel); } // 以下省略 }
ちなみにBaseActivityの中は次のような感じ。ViewModelにもライフサイクル系メソッドを用意して、ActivityやFragmentのライフサイクルと同期して呼び出すようにしている。
public abstract class BaseActivity extends AppCompatActivity { private ActivityComponent component; private ActivityViewModel viewModel; @NonNull public ActivityComponent getComponent() { if (component == null) { MyApplication application = (MyApplication) getApplication(); component = application.getComponent().plus(new ActivityModule(this)); } return component; } protected void bindViewModel(ActivityViewModel viewModel) { this.viewModel = viewModel; } @Override protected void onStart() { super.onStart(); checkViewModel(); viewModel.onStart(this); } // 以下省略 }
ViewModel
ViewModelの実装は次のようにする。画面遷移は、Navigatorという画面遷移を取り扱うクラスを用意して、それをViewModelにinjectして行うようにしてみた。今回は直面していないがContextが必要な処理があるときはEventBusを使ってActivityにイベントとして流したほうがいい気がする…。
public class MainActivityViewModel extends ActivityViewModel { private final Navigator navigator; private final TaskRepository taskRepository; private ObservableList<TaskViewModel> taskViewModels; @Inject public MainActivityViewModel(Navigator navigator, TaskRepository taskRepository) { this.navigator = navigator; this.taskRepository = taskRepository; this.taskViewModels = new ObservableArrayList<>(); } @Override public void onStart() { } @Override public void onResume() { taskRepository.findAll() .map(tasks -> Stream.of(tasks) .sorted((o1, o2) -> (int)(o1.deadlineEpoch - o2.deadlineEpoch)) .toList()) .map(tasks -> convertToViewModel(tasks)) .observeOn(AndroidSchedulers.mainThread()) .subscribe(taskViewModels1 -> { this.taskViewModels.clear(); this.taskViewModels.addAll(taskViewModels1); }); } // 以下省略 }
Navigatorの実装はこんな感じ。
@ActivityScope public class Navigator { private final Activity activity; @Inject public Navigator(AppCompatActivity activity) { this.activity = activity; } public void navigateToCreateTask() { activity.startActivity(CreateTaskActivity.createIntent(activity)); } public void navigateToTaskDetail(int taskId) { activity.startActivity(TaskDetailActivity.createIntent(activity, taskId)); } public void closeActivity() { activity.finish(); } }
Repository
RepositoryにはLocal/RemoteDataSourceを持たせて、それらDataSourceにてCRUD操作を行う。例はローカルDBしかおいてないのでほぼ効果はないが、「ローカルDBにデータが有るときはローカルDBから、そうでないときはAPIから」という風に処理を分ける際にはここで分岐させる。
@Singleton public class TaskRepository { private final TaskLocalDataSource taskLocalDataSource; @Inject public TaskRepository(TaskLocalDataSource taskLocalDataSource) { this.taskLocalDataSource = taskLocalDataSource; } public Single<List<Task>> findAll() { return taskLocalDataSource.findAll(); } // 以下、省略 }
DataSource
DataSourceからの返り値はRxJavaのSingleで包んでストリーミングに流すようにする。例はLocalDataSourceだけだが、Retrofit等を用いてRemoteDataSourceを作るときはRetrofitのClientをinjectしてClientのアクセス結果をSingleで包む形になるはす。
public class TaskLocalDataSource { private final OrmaDatabase ormaDatabase; @Inject public TaskLocalDataSource(OrmaDatabase ormaDatabase) { this.ormaDatabase = ormaDatabase; } public Single<List<Task>> findAll() { return ormaDatabase.relationOfTask() .selector() .executeAsObservable() .toList() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } // 以下省略 }
終わりに
とりあえずこれを踏襲してサンプルでToDoアプリを作ってみた。
先人の知識に頼りっぱなしで実装したが、いざ組んで見てわかること、記事にして思い違いだったと気づくことがたくさんあるのでとても重要だと思った(感想)
ObservableList#OnListChangedCallbackでListViewやRecyclerViewのAdapterを更新する
TL;DR
- ObservableList#OnListChangedCallbackでObservableListに格納されているデータの変更を通知できる
- 変更を通知できるので「通知のあったitemのみViewを更新する」と言った処理も可能
- RecyclerViewなら各コールバックメソッドに対応した変更通知が使える
- ListViewには変更通知として
notifyDataSetChanged()
しかないので同様のことをしたい場合、getView
を独自に叩くことになるのでActivity/FragmentにObservableList#OnListChangedCallbackを配置するのが早そう - チャットUI等の実装にはListViewではなくてRecyclerViewを使ったほうがViewの更新回数が減りそう
ObservableList
DataBindingで実装されているObservableなコレクションの一種で、イメージ的には「データ変更を通知する機能を備えたList」。DataBindingにおける各データの通知方法(BaseObservable、ObservableField等)はこの辺を参照
ObservableListは、コールバックにOnListChangedCallbackを持っており、(RecyclerViewについてきた)SortedListよろしくリスト内の要素の変更を通知することができる。というわけで、ListViewやRecyclerViewのAdapterに持たせるListにコールバックを実装したObservableListを与えることで、リストの要素が変更されたときや追加されたときなどにその変更をUIに反映させることができる。
Listの変更をListViewやRecyclerViewに通知したい
ListViewの場合
色々見ているとAdapterのコンストラクタ内でObservableList#OnListChangedCallbackをセットしていることが多そう。Adapterの変更通知は notifyDataSetChanged()
しかないので、基本的にOnListChangedCallbackのどのコールバックメソッドが走っても(表示されている)全部の要素に対して getView()
が実行され、描画されることになる。
public class ContentsAdapter extends ArrayAdapter<Content> { public ContentsAdapter(Context context, ObservableArrayList<Content> objects) { super(context, 0, objects); objects.addOnListChangedCallback(new ObservableList.OnListChangedCallback<ObservableList<Content>>() { @Override public void onChanged(ObservableList<Content> contents) { // リストそのものが変更されたとき notifyDataSetChanged(); } @Override public void onItemRangeChanged(ObservableList<Content> contents, int i, int i1) { // iを始点としてi1までの範囲の要素が変更されたとき notifyDataSetChanged(); } @Override public void onItemRangeInserted(ObservableList<Content> contents, int i, int i1) { // iを始点としてi1までの範囲に要素が挿入されたとき notifyDataSetChanged(); } @Override public void onItemRangeMoved(ObservableList<Content> contents, int i, int i1, int i2) { // iからi1へi2の数だけの要素が移動したとき notifyDataSetChanged(); } @Override public void onItemRangeRemoved(ObservableList<Content> contents, int i, int i1) { // iを始点としてi1までの範囲の要素が削除されたとき notifyDataSetChanged(); } }); } // 以下省略... }
変更が通知された要素のみViewを再描画したい場合、 notifyDataSetChanged()
せずに、直接AdapterのgetViewを叩くことで指定のViewのみを更新できる。しかしCallbackをAdapterのコンストラクタ内で定義するとうまくいかないのでListViewが表示されるActivity/FragmentでCallbackを定義してあげる必要があるし、実行するにしても getFirstVisiblePosition()
や getLastVisiblePosition()
で表示されている範囲内の要素か判定する必要がありそう。 「ListViewの中に更にListViewやRecyclerViewがあって、再描画するとListView/RecyclerViewの表示が崩れたり一瞬消えたりする」場合に使うと言ったケースはあるかもしれない。
RecyclerViewの場合
ObservableList#OnListChangedCallbackのセットはListViewのAdapterと同様、Adapterのコンストラクタ内でやっているのが多い感じ。ただ、RecyclerViewのAdapterはListViewのAdapterとは違って、notify系メソッドが用途別に揃っているっぽい。
notifyDataSetChanged()
notifyItemChanged(int position)
- 指定したpositionのitemが変更されたことを登録されているすべてのobserverに通知する。そのpositionのItemのみを更新する
notifyItemInserted(int position)
- 指定したpositionのitemが挿入されたことを登録されているすべてのobserverに通知する。そのpositionのItemのみを更新する
notifyItemRemoved(int position)
- 指定したpositionのitemが削除されたことを登録されているすべてのobserverに通知する。そのpositionのItemのみを削除する
notifyItemMoved(int fromPosition, int toPosition)
- fromPositionのitemがtoPositionに移動したことを登録されているすべてのobserverに通知する。移動に関係のあるItemのみを更新する
notifyItemRangeChanged(int positionStart, int itemCount)
- positionStartからitemCountの範囲だけのitemが変更されたことを登録されているすべてのobserverに通知する。該当範囲のItemのみを更新する
notifyItemRangeInserted(int positionStart, int itemCount)
- positionStartからitemCountの範囲だけのitemが挿入されたことを登録されているすべてのobserverに通知する。該当範囲のItemのみを更新する
notifyItemRangeRemoved(int positionStart, int itemCount)
- positionStartからitemCountの範囲だけのitemが削除されたことを登録されているすべてのobserverに通知する。該当範囲のItemのみを削除する
(引数にObject payloadを与えるものもあるが今回は特に使う機会がないので見なかったことにする)
RecyclerView.Adapter | Android Developers
この中からObservableList#OnListChangedCallbackのそれぞれのコールバックメソッドに相当するものを選ぶと、
onChanged
-notifyDataSetChanged
onItemRangeChanged
-notifyItemRangeChanged
onItemRangeInserted
-notifyItemRangeInserted
onItemRangeMoved
-notifyItemRangeRemoved
になる。onItemRangeRemoved(T sender, int positionStart, int itemCount)
だけは対応するnotify系メソッドがないというか、notifyItemRangeMoved
が存在しないので困ったところだが、
for (int i = 0; i < itemCount; i++) { notifyItemMoved(fromPosition + i, toPosition + i); }
でforでちまちま回すか notifyItemRangeChanged(fromPosition, toPosition + itemCount)
で範囲変更として扱うかで対応できそう。(ここに関しては試していないので自信がない)
public class ContentsRecyclerAdapter extends RecyclerView.Adapter<ContentsRecyclerAdapter.BindingViewHolder> { private ObservableArrayList<Content> objects; public ContentsRecyclerAdapter(ObservableArrayList<Content> objects) { this.objects = objects; this.objects.addOnListChangedCallback(new ObservableList.OnListChangedCallback<ObservableList<Content>>() { @Override public void onChanged(ObservableList<Content> contents) { notifyDataSetChanged(); } @Override public void onItemRangeChanged(ObservableList<Content> contents, int i, int i1) { notifyItemRangeChanged(i, i1); } @Override public void onItemRangeInserted(ObservableList<Content> contents, int i, int i1) { notifyItemRangeInserted(i, i1); } @Override public void onItemRangeMoved(ObservableList<Content> contents, int i, int i1, int i2) { for (int j = 0; j < i2; i++) { notifyItemMoved(i + j, i1 + j); } } @Override public void onItemRangeRemoved(ObservableList<Content> contents, int i, int i1) { notifyItemRangeRemoved(i, i1); } }); } // 以下省略... }
このようにしてあげることでListViewのAdapterのnotifyDataSetChanged()
ではできなかった「変更があった要素のみViewを更新する」ことが可能になる。ついでに何かアニメーションもしてくれる。このようにすると、単純にListViewに比べてもViewの描画回数が減るので、ポーリングでリストの内容を更新したり、チャットUIの実装を行うときにDataBindingを扱う場合、RecyclerViewを使ったほうが良さそう。
東京に来ました(就職)
やべぇブログ書いてねぇ
広告に目をやられました。ブログに広告が出っぱなしなのはつらいのでとにかく書いていこうかなと思います。
そういえば大学を卒業していました
いわゆる「17新卒社会人」になりました。明石高専から編入した神戸大学を学部で卒業し、就職して東京に住むことになりました。今は渋谷駅へのアクセスがし易いところ(曖昧)に住んでいます。今後もずっとプログラムを書いていく事になりそうなので当面の目標は 渋谷のイケイケパリピプログラマ です。嘘です。ちゃんと真面目にプログラミングしたいです。
高専から大学に編入した人だと、だいたい大学院への進学が多いような印象があるのですが、僕は色々考えた上で院への進学を諦めました。学力面とかではない…はずです。究極的に言うと
「研究室のM1の先輩方が大学院の授業関連でめちゃくちゃ忙しそうにされていた」
のが決め手でした。大学院に進んだ編入生の同期もなんだかんだ忙しそう。
東京来て変わったこと
- イベントとかに参加するのに全く苦労しなくなった
- 地方でイベント、あまりないしやるにしても大変というのはある
- 人がいっぱいいる
- 自己管理が大変
- 体調崩しても頼れる人がいない…
- 自堕落になると戻れない
- 食費がやばい
- ランチで1000円とか飛んで行くのでぐるぐる目になってきた
- 自炊しないと財布も胃も持たない
- 休日が家事で溶ける
- 今日も買い出しと掃除と洗濯と料理してたら一日溶けた
- ありきたりだけど親のありがたみがすごい
ネタがなくなった
技術的な話をしようと思ったけどここ一ヶ月とても忙しかったせいかまとめられるようなスタックがないのでGW中に何か技術的な記事書きます。
- なんでこんな時期にこんなエントリを書いたんだ
- 一ヶ月経って落ち着いたのもあるけど一番の理由は 「4月直後だとみんな書いてるし便乗感が出るから」
おまけ
ひとりぐらしはたいへんなので助けてください
寿司を回そう
この記事は クソアプリ advent calendar 21日目の記事です.遅刻.
クソを作ろう
去年 @amparsand_xyz さんとクソアプリでadvent calendarしたいぞ!ってやってたら現実になってしまったのがことの始まりだった気がします.今年もなんか存在してるので積極的にクソを投下した買ったのですが研究等々で時間が滅亡したのでMAで作った作品の供養をしようと思います.
できたもの
今年はついに目に見える物に手を出して寿司を回しました.大体のインスピレーションはこちらの記事から得ています
動いてるシーン
ちゃんと寿司を乗せて回したりもしています
回しっぱなしで放置されたいなり寿司 #MA_2016 pic.twitter.com/01jTS0s079
— 湯村 翼🍣木-西み38b (@yumu19) 2016年12月17日
何が起きているのか
「年収が800万円を超えると寿司が止まって見える」「ポリエチレンテレフタレートで覆うことによって『イオン化現象』によって安価に寿司を止める」「寿司と等速で回転することで寿司を止める」「ゾートロープの原理で寿司の可視・不可視を切り替え止まったように見せる」など「動いている寿司を止める」ことを目的とした取り組み(?)はかなり行われていたのですが,「寿司が生み出す不平等」(??)は全く問題視されていないので問題視した結果
「年収っぽいものを診断して高ければ高いほど高速で寿司を回せばいいんじゃね?」
となった結果がこちらです.Androidアプリ上で質問に答えると寿司が回るようになりました.これ以上はありません.ちなみに実装は8時間です.ひどい.
何をやっているのか
Raspberry Pi上にsinatraアプリを立てて,そこにAndroidアプリでなんかこう判定した結果をそれっぽく処理して,モータードライバ搭載のArduino互換機「Studuino」をGPIOから信号を送ることで速度を切替えながら回してます.ちなみにGPIOの本数が生える限り回転速度を制御ことが出来ます.
あとMAなのにAPIを一個も使わないのは寂しかったので,株式会社エーアイさんのAITalk Web APIを使って判定結果に応じて琴葉茜ちゃんが関西弁で煽ってくれるようにしました.琴葉姉妹の音源が使えるのはMA限定だったそうです.
おわりに
もっと真面目に時間かけて作ればよかったとおもいました.
来年のMAもガンバルゾー
ニューでなハウス
この記事は denari01 Advent Calendar 2016 - Adventar 16日目の記事です
前日の記事は @sonet_sou くんでした.まだ記事が存在しないので楽しみに待ってます
前置き
当記事はでなりの家に泊まりに行った際の記憶とそこから学ぶ一人暮らしに関する知見を並べたいと思います.ちなみにkosen10sにおけるでなハウスの位置づけは以下のツイートに集約されます.
家探しの底辺のベンチマークはでなりという #kosen10s のお決まり芸
— クソメガネくん@どくぴー (@e10dokup) December 5, 2016
最初に言っておきたいこと
これからでなりの家に関して色々書きますが,とにかく東京で宿を探すのが大変なので泊めていただけることには大変感謝しております.私も来年は多分上京するので誰かを泊めれるような家の環境を保とうと思います.
ニューでなハウスに泊まりに行かせてもらうことになった
実はでなハウスには2回泊まりに行ったことがある.でなりが福井県にいた頃のオールドでなハウス(以下:オールド)に1回.千葉県に行った後のニューでなハウス(以下:ニュー)に1回だ.ちなみにどちらも「カレーメシを食す会」絡みなので同時に @c_bata_ も泊まったことがあることになる.どうでもいいけど「ニューでなハウス」という文字列からは場末のホテルっぽい響きが感じ取れるので個人的には大好きだ.
オールドにはこれ以降触れないのでこの際説明しておくと,2Kでプロジェクタをおける程度の広さで堂々とカレーメシの調理をすることが可能だった記憶がある.地味に家具もいろいろ揃えていたのでなんだかんだ住みやすそうにはしていたと思う.
なんやかんやあってニューでなハウスに泊めていただくことになった時,でなりは「いやぁうち引っ越したばかりだからやばいよ,なんかにおいするし」とか言っていた記憶があるが当時の僕は「いやまさかそんな限界生活みたいなことしてるはずは無いやろwwwww」とか楽観視していた.
Problem 1 : 布団を買う
当時はまだ立川シネマシティでガルパンの上映が続いていたので,僕はガルパンをみて「いいぞ」ってなりながらニューでなハウスに向かった.最寄り駅につくとでなりが待ってくれていたので
「いやぁ引越し代下げるためにほとんど家具処分したんよ」
「まじ?」
とか話しながら駅からちょっと歩いたところにあるニューでなハウスに到着する.お邪魔してみるとなるほど何もない.どれくらい何もないかというと布団すら無いので一体こいつどこで寝ているんだとかそんな疑問がふつふつと浮かんでいると
「これからクソメガネとカレーメシ先輩(@c_bata_)が寝るための布団買いに行く」
と言い出すので最寄りのニトリに行くことになった.最寄りと言ってもGoogle Maps調べで約2kmの距離があり,それを往復でトボトボ歩いて片道30分らしい.「いや車使えよ」となるのだが免許を持っているのが僕(しかも初心者マーク)だけなので当然のように断念する.行きだけは「はえー,いろいろ店あるねぇ」とか言いながら歩いていたが,ニトリでセミダブルサイズの布団を2つ買って帰ろうとした時に僕たちは気づいてしまった.
「えっ,つらくね…?」
いくら軽めの布団を選んだとは言え,2kmをセミダブルの布団を抱えてトボトボ歩くのは流石に厳しい,しかも梅雨の入りくらいでちょっと蒸し暑くなってきたくらいなので,余計に厳しい.そんなこんなでひぃひぃ言いながら帰りは45分位かけてニューでなハウスにたどり着くことができた.
Problem 2 : 風呂に入る
寝る前にシャワーを浴びようとすると,でなりからファブリーズを渡される.「とりあえずこれで服の臭いを抑えとけ」という意味なのかなと思うと,全然違うって顔をするのでとりあえずはいってみると,これは確かにやばいにおいがする.具体的な言及は避けるが,脱衣所によくある洗濯機置場の排水口からどうやら発生しているみたいなのでそこにファブリーズを散布しろということらしいので言われたとおりにする.なんで洗濯機買ってないんだと思ったが,家の目の前にコインランドリーがあるのでそっちを使うと考えるとまぁ大丈夫だろうという感じがする.40回も使えば減価償却できるんじゃね?という気もしなくもないが,多分突っ込んではいけないのだろう.
シャワーを浴びて出てくると,もう一度臭いが立ち込めているので「うへぇ」という顔をしながらもう一度ファブリーズを散布して急いで服を着て脱出する.他の二人がシャワーを浴びている間に「そういえば排水管って2回曲がっててそこに水が溜まって臭いを防ぐんだったよな」ということに気付いたので,二人に「排水口に水流し込んだら臭い止まるんじゃね?」って言って実際に流し込んでみると無事に臭いは止まった.どうやら溜まってた水がなくなったので臭いがダイレクトに届いていたらしい.あとで聞いてみると「排水管から臭いがするのはずっと人が済んでいない証拠って言われまくった」って言われたので色々と察してしまった.最近もう一度臭いがしたらしく,もう一度水を流し込んだらしい.いやいい加減洗濯機買えよ.
Problem 3 : 単純に住みにくい
梅雨の入りとはいえ,別に空調いれないとやっていけないほど暑いと言うわけでもないはずだったのだが,ニューでなハウスはなぜかとにかく暑かった.空調を仕方ないのでいれさせてもらうと,これがどっこい異様に効かない.後々のでなり曰く「夏は死ぬほど暑くて冬は死ぬほど寒い」ということらしいので単純に断熱的な何かが異様に足りてないのではないかという結論になった.
そしてニューでなハウスでまったりしてると突然「ガタガタガタ…」と揺れたりする.@c_bata_ と二人で「え?地震ですか?」みたいな顔をしているとでなりは「あっ,トラックが通っていますね」みたいなことを言う.家が幹線道路に面しており.トラックとかがバンバン通るため,その影響で家がガタガタと揺れるようだ.本当に地震が来たらあっさり倒壊しそうで冗談抜きで心配している.ちなみにニューでなハウスには謎の隆起があったり,すでに床が斜めになっていたりしているので地震が来なくても倒壊しそうとかは口が裂けても言ってはいけない.
最後に,でなりの住んでいる階にはどうやらネットが配線的に届かないらしく,インターネット環境はなんとWiMAXルータでしのいでいた.しかも別に電波がビンビンに入るわけでもない感じだったのでエンジニアとしては本当に厳しい環境だなと一人で考えていた.
以上より得られた知見
どうやらでなりは家賃に目がくらんでニューでなハウスを選定したようだが,「給料の三分の一を家賃に貢がないと人間的な生活は出来ない」と誰かが言うように本当にケチると限界生活が始まるんだなという感想が得られた.内見していればこんなことにはならないのではないかという気はするが,1-3月期の東京での家探しではそうも言ってられない感じなので不安が止まらない.そして,いくらもったいないとは言え家具は揃えたほうがいい.絶対にQoLに響くので最低限の冷蔵庫,洗濯機,電子レンジくらいは揃えようという気持ちになった.いくら「いやぁ家は寝に帰るだけだし〜」と言っても,そもそもその家が寝るだけで苦しい環境とかだと本当に心が折れかねないので,家探しはまじで大切なんだなとでなりの尊い犠牲からえられたので,これから家を探すにあたって,住みよい家に出会えるように努力をしていきたい.
最後に
ニューでなハウスのことを散々言いましたが,本当に泊めていただいたことには感謝をしております.Thanks a lot for denari's kindness.
叶うならば,でなりがもっといい家に住めますように…
でなlangに見る手を抜いてつくる3文字言語
この記事は denari01 Advent Calendar 2016 - Adventar 5日目の記事です
諸注意
特に何もしてないのでご注意
動機
誰かが「Grassのトークンさえ入れ替えたら任意の3トークンを用いた言語ができるゾ」とか言ってた
まずGrassって何さ
要は「W」「w」「v」だけで書く言語です.型無しラムダ計算がベースの関数型プログラミング言語ですが特にこれ以降このことに絡んだお話はしません.
やったこと
まずこちらのyagi.rbを拝借します.こちらを改変するだけです.基本的には「W」「w」「v」のトークンを使いたい任意の3トークンに入れ替えてあげましょう.今回は「で」「な」「り」なので
W -> で
,w -> な
,v -> り
ですね.
# W -> で # w -> な # v -> り App = Struct.new(:m, :n) L = Struct.new(:src) src = File.read(ARGV[0]) src = src.gsub(/[^でなり]/, "") # vで区切る,vなし区切りのために更に次で区切る src = src[/な.*\z/m].split(/り/).map.with_index do |sub, i| sub = sub.scan(/で+|な+/) arg_num = sub.first[0] == "な" ? sub.shift.size : 0 sub = sub.each_slice(2).map { |n, m| App[n.size - 1, m.size - 1 ] } arg_num.times { sub = [L[sub]] } sub end.flatten(1) # eval eval = ->(src, env) do ctrue = ->(arg) { eval[[L[[App[2, 1]]]], [arg, ->(arg) { eval[[], [arg]] }]] } cfalse = ->(arg) { eval[[L[[]]], [arg]] } src.inject(env) do |env, insn| val = case insn when App clos, arg = env[insn.m], env[insn.n] case clos when Proc then clos[arg] when Fixnum then clos == arg ? ctrue : cfalse when :out then putc(arg); arg when :in then ch = $stdin.getc; ch ? ch.ord : arg when :succ then arg.succ & 0xff end when L ->(arg) { eval[insn.src, [arg] + env]} end [val] + env end.first end puts "-- denalang source code --" puts File.read(ARGV[0]) puts "-- result --" # プリミティブ eval[[], [eval[[App[0,0]], [eval[src, [:out, :succ, "w".ord, :in]]]]]] puts "\n"
できた
本当に何もしてないですが,これだけです,早速見てみましょう.じゃあHello World.
$ cat hello.dn なりななででなでででなりでななななででなでででなででででなでででででなででででででなでででででででなでななななななななななななででででなでででででででなででででででででででででででなでででででででででででななででででででででででななででででででででででででなででででででででででななででででででででででななななななでででででででででででででででなでででででででででででででででででででででなででででででででででででででででででななでででででででででででででででででななでででででででででででででででででなななななででででででででででででででででででででななででででででででででででででででででででででなでででででででででででででででででででででででででななななななななななななななななななななななななななでななななななななななででなななななななでででなななななななででででなでででででななななななななででででででななななななななななななななななでででででででななななななななななななななななななななででででででででななななななななななななななななななななななななななななななななななななでででででででででななななででででででででででなななななななななななでででででででででででなななななななででででででででででででななななななななななななななななななでででででででででででででななななななななななななななななななななななななな
$ ruby denalang_interpreter.rb hello.dn -- denalang source code -- なりななででなでででなりでななななででなでででなででででなでででででなででででででなでででででででなでななななななななななななででででなでででででででなででででででででででででででなでででででででででででななででででででででででななででででででででででででなででででででででででななででででででででででななななななでででででででででででででででなでででででででででででででででででででででなででででででででででででででででででななでででででででででででででででででななでででででででででででででででででなななななででででででででででででででででででででななででででででででででででででででででででででなでででででででででででででででででででででででででななななななななななななななななななななななななななでななななななななななででなななななななでででなななななななででででなでででででななななななななででででででななななななななななななななななでででででででななななななななななななななななななななででででででででななななななななななななななななななななななななななななななななななななでででででででででななななででででででででででなななななななななななでででででででででででなななななななででででででででででででななななななななななななななななななでででででででででででででななななななななななななななななななななななななな -- result -- Hello, world!
できました(?)
おわび
Q: え?トークン入れ替えただけだよね?
A: はい,それだけなのに記事書いて申し訳ありませんでした.時間がないけどとりあえず書いた結果がこれです.クソ記事に付き合っていただきありがとうございました
#kosen10s slackでの自分のemojiのつくりかた
これは #kosen10s Advent Calendar 2016 - Adventar の4日目の記事です.
昨日の記事はこちらでした
言い訳
昨日の記事で上がったハードルが高すぎる
技術系アドベントカレンダーに取り上げられたりしてるらしいですが僕は技術の話はしません.ご了承を
kosen10s slack
初日でもなっちゃん( id:marin72_com )が言及していましたね
こんな感じでやいのやいのしているわけですが,emojiに関してルータ芸人( id:puhitaku ) も言及したので自分もemojiの話をします.
How to make emoji
自分はMacを使ってるのですが,Macに標準でついてくる「プレビュー.app」がやけに高機能なのでこれを使えば簡単に透過画像を作れます.プレビューのくせに…
画像をもってくる
適当なフリー素材でも探してきましょう.
プレビュー.appで編集する
プレビュー.appで開いて,編集機能を出します.
すると出てくるツールバーの中に「インスタントアルファ」があるので,選択しましょう.
「インスタントアルファ」は,ドラッグした部分の色合いに近い箇所を全部選択するツールです.ドラッグの長さで選択する領域の微調整も出来ますが,今回は白背景なのでそのまま選択してdeleteキーで全部背景を抜いてしまいます,
後はこれのサイズを縦・横両方128px以下になるようにして,Customize SlackからEmojiに登録しましょう.これで完成です.白背景なら簡単に背景を抜いて透過できるので楽なのですが,アニメのキャプチャなど,空や木がはいって背景が単色ではない画像などでも,うまいことそれぞれの色に合わせてインスタントアルファを描けたり,矩形選択や投げ縄選択を使ったりして微調整しながら比較的簡単に背景を抜くことが出来ます.
ね?簡単でしょ?これで皆も絵文字量産おじさん!