シェルスクリプトで匿名関数(?)を用い標準入力を取らないコマンドをパイプに繋ぐ
恥ずかしいことに全てのコマンドをパイプに繋ぎ込めると思っていたので、関数型言語でよくやるように、x |> f |> g
(xに対し関数fを適用し、その結果に対してさらに関数gを適用する)みたいに繋込もうとしていた。
けれど、コマンドに渡すコマンドライン引数の順序が違ったり、そもそも標準入力を受け付けないコマンドだと安直にパイプで繋げない、ということがわかった。
実際に直面した問題
webpackみたくparcelを使ってjsをバンドルする、ということをやっていたときに、ビルド成果物であるところのjsの名前がハッシュ化(dist/js.e20f4712.js
)されていたためリネームする必要があった。(普段はそのままでいいのだけど。)
よって、ビルド成果物のうち、正規表現\.js$
に合致するものをcontent_scripts.jsにリネーム(コピー)しようとして書いたスクリプトが下記。
$ npx parcel build index.html / # ビルドして成果物一覧を標準出力 | xargs -n1 echo / # 標準出力に1行ずつ出力 | grep \.js$ / # 正規表現\.js$に一致する行を抽出 | cp {ここにパイプから渡ってきた値を配置したい} dist/content_scripts.js
渡ってくるコマンドは第一引数に渡したいのに、第二引数の位置に渡ってくる。こういう場合、関数型言語においてはflipedな(引数の順番をひっくり返した)関数を定義するか、匿名関数を用いることが多い。
そこで調べたところ、シェルスクリプトにも匿名関数っぽく書ける書き方があったので試してみた。
$ npx parcel build index.html / | xargs -n1 echo / | grep /.js$ / | (){ cp $1 dist/content_scripts.js } # 渡ってきた値を変数$1,2...に格納して{}の内部で使えるように。
すると、
usage: cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file target_file cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file ... target_directory
なんかsyntaxで怒られてるっぽい。
引数の順番としては間違ってないはずなのに、、、と思って調べていたところ、シェルスクリプトのコマンドは何でもかんでもパイプにつなげるわけではないらしいことがわかった。例えば、echoコマンドとか。
$ echo hoge | echo # -> 何も出力されない
じゃあどうするのか?
1.パイプを使わないパターン
こいつは要するに、x |> f |> g
のように書くのではなく、g(f(x))
のように包含する形の書き方だ。
$ cp $(npx parcel build index.html | xargs -n1 echo | grep \.js$) dist/content_scripts.js
なんだけど、パイプと愚直に引数にとる書き方が混ざっていてデータの流れ、処理の流れが分かりにくい。よって、基本的には次のパイプを使うパターンの方がいいように感じる。
2.別の匿名関数っぽい書き方でパイプを使うパターン
$ npx parcel build index.html \ | xargs -n1 echo \ | grep \.js$ \ | ( v=$(cat); cp $v dist/content_scripts.js ) # 匿名関数っぽい!
スクリプト言語における匿名(ラムダ)関数(\x y -> x + y)
っぽいノリで書けるのがいい。
()じゃなくて{}で書くやり方もあるみたいだけれど、sh/bash/zsh全てで動作確認が取れた()を用いる方がよいのではないかと感じた。
ちなみに、()内で定義した変数はローカル変数になるようだ。({}だとグローバルになる?)詳しくわかってるわけではないのでまた調べてみたい。
※ちなみに、xargsもechoやcpみたく標準入力を取らない(そのままではパイプに繋げない)コマンドに対してパイプで標準入力を引き渡せるように引数を変換してくれているらしい。
以上。
参考
ElmのDebug.logをScalaの拡張メソッドで再現する
ElmからScalaに入門したときにprintlnでのデバッグが難儀だと感じたため、ElmのDebug.logを再現する方法を探していたところimplicitsのうちのpimp my libraryパターン(enrich my libraryや拡張メソッドとも呼ばれている)を使うことで再現ができたため解説を試みる。
ElmのDebug.logの再現
ちなみに、implicitsはscalaのもつ3つの機能の総称であり、曖昧な表現であるため使わない方が良いとされているようだ。なので、日本語訳として存在していて呼びやすい拡張メソッドという呼び方をここからは採用する。
「implicits」は異なる3つの機能を一つにまとめた呼び方に過ぎず混乱を招くので、使うのをやめよう(個々の機能名で呼ぼう)。
引用:Scalaでimplicits呼ぶなキャンペーン | kmizuの日記
拡張メソッドとは、要するに既存のクラスにメソッドを追加する(ように見せかける)実装のこと。実際に動かすことのできるコードを以下に記載する。
object MyApp extends App { implicit class Tap[T](self: T) { def tap[U](block: => T => U): T = { block(self) //値 は 捨 て る self } } 1.tap { s => println(s.getClass) // int } .toString .tap { s => println(s.getClass) // string } }
解説
implicit class Tap[T](self: T) {
implicit class
は拡張メソッド専用の書き方で、implicit def
でも書くことができるが、既存の型への変換など可読性を落とすことになるのでimplicit class
で記述する。クラス名のTapの部分は特に使うことはないので任意の名前でいい。
def tap[U](block: => T => U): T = {
block
のアノテーションが=>
から始まっている通り、名前渡し(call-by-name)にすることで引数に渡される式の評価をメソッドの内部に遅延することができる。
{
block(self) //値 は 捨 て る
self
}
ブロック式形式で記述した複式は、最後の値が返り値となるためprintlnなど副作用を表すUnit型の値は捨て、ジェネリクスによって呼び出し元のクラスを返り値に設定する。
1.tap { s => println(s.getClass) // int } .toString .tap { s => println(s.getClass) // string }
これによって、ElmでDebug.logを用いてパイプでの値の変換や関数合成の任意のタイミングで値をコンソールに表示するように、Scalaでもメソッドチェーンの任意のタイミングで標準出力にアウトプットすることができるようになった。
ただ、注意点としてElmのDebug.logと違って、Scalaではimplicit conversionの関係か、tapメソッドを経過することでときおり返り値が変わってしまうことがある。どういったメカニズムかはまだはっきりとわかっていないので、知っている方がいらっしゃれば是非教えていただきたい所存である。
以上。
参考
新しい言語、技術を学ぶときの手順について(Scalaを題材に)
ここ1年ぐらいはElmを用いてフロントエンドの開発をすることが多かったのだけど、最近Scalaに入門する機会があったので、「この機会に新しい言語や技術を学ぶときの自分用最強手順を確立しよう」ということでいろいろ工夫して取り組んでみたので、その結果として学んだことを記事にして残しておこうと思う。
目次
新しい技術の学習手順
1. ハンズオン形式の教材を見つける
本当に初期の初期の入門の段階で文字情報から実際の作業手順へと脳内変換する手間が無駄でしかないので、ある程度までは動画などを見ながら脳死で実際に動かしてみる、というのが一番早いと思っている。
動画形式のものがなかったとしても最低でも公式のGet StartedのDocsなど、行間を補う必要なくその通りに進めさえすればある程度のものは作れる、という信頼のおけるものを探す。これが見つかった時点でかなりのショートカットができる。
言語以外のところで言うと、AWSのStart Up DayのハンズオンなどはAppSync x GraphQL x Elastic Searchとかの組み合わせ方、自分で作業手順に落とし込んでいたらかなり時間を失うような工程を全て手順に落とし込んだ上で作業している様を画面に映して解説してくれ、昨今のコロナの情勢の中でも詰まったところはチャットでサポートしてくれるためこういったものを見つけるのが一番いいのは言うまでもない。
追記:最近だと5分のハンズオンを先輩など詳しい人に頼むのが非常に有益だと感じる。5分だと時間をもらう方も頼みやすいし時間を使う方も労力がかからない割に後進が育つのでそれならばやってやろうという気持ちにもなる。
Scalaの場合だとドットインストールがいいと思う。
sbtのインストールから実際にコンパイルを通して実行して標準出力を得る、といった超初歩的なところから丁寧に説明してくれているし、他の言語での基本的なお作法をScalaではどういうふうに表現しているのかを、設計までは入らないものの網羅的に解説してくれている。
36レッスンがあるが1日で終わる内容なので1日で終わらせて解約をした。実際に手元で動かすことのできるサンプルの実装を30ほど学習の初期の段階で手に入れることができたのは、その後の学習をスムーズに進める上でとても役に立った。
また、よく使われている言語ほどネット上で調べると本当かどうかわからないようなただの推論で書かれている記事を見かけることも多い。そんなときに実際に動かしてみて反証することができる環境を一早く作ってしまうことは有益だと感じた。
2. 具体例が添えられた網羅的な入門サイトを探す
先にも述べたようによく使われる言語ほど検索して出てくる情報は玉石混合であるので、手順1で見つけたハンズオンや公式のDocsなどの作業手順を進めていく中で出てきたものはそのまま検索するのでなく、サイト内検索のクエリを結合して直和をとる。
for式 site:http://bach.istc.kobe-u.ac.jp/ OR site:https://scala-text.github.io/ OR site:https://docs.scala-lang.org/ OR site:http://www.ne.jp/asahi/hishidama/home/tech/scala
今回のScalaに入門するにあたっては、公式のScalaLangのDocsには具体例が書かれていないため、ひしだま's ホームページやDwangoのScala初学者用入門テキスト、そして、社内の信頼できる先輩方のブログなどを用いた。
3. 手順2で当たりをつけた信頼できる情報源に載っていない場合には普通にGoogle検索にかける
手順2で当たりをつけた信頼できる情報源に載っていないような応用的な内容である場合には玉石混合の中から検索をする。全ての検索に言えることだが、まずは検索結果の集合が大きめになるように検索をしつつ徐々に絞り込んでいく。
4. デバッグの方法を初期で確立する
その言語においてエラーが発生した時に問題をどう切り分けたらいいのかに早めに当たりをつける。RailsならController内部でbinding.pryで処理を中断してローカル変数の値など確認できるし、JSなら同じことをdebuggerで行い、ElmならDebug.logをパイプで繋いでいる合間や関数合成の合間など任意の場所に挟んで値をlogに出力できるなど。
問題の切り分け方を知らない状態で開発を進めるのは推論でデバッグを行うことになるため時間のロスが大きい。自分の場合は型クラスを用いて共通のインターフェースを作ることでElmのDebug.logのようなメソッドを作ってデバッグを行っていた。(最初は型クラスを知らなかったので、コピペで引っ張ってきただけだったけれど。)
5. 既知の言語・技術との違いを意識する
既知の言語でのこういうことを実装するには新しい言語ではどう表現するのかを整理し、まとめていくと頭に入りやすい。例えば、ElmでのflatMapはflatMap : a -> List b -> List a
など第一引数にとる関数は元の文脈で包み直すものばかりだったが、ScalaのflatMapではdef flatMap(f: (A) => IterableOnce[B]): List[B]
のようにIterableOnceで包み直せばよく、必ずしも元の文脈(この場合だとList)で包み直さなくていいことは驚いたこともあってよく覚えている。
その他、大切だと思ったことを五月雨に
1. 意外と入門書を読む必要はない
検索がいまいちスムーズにできないというのが大きい。ただ既知の言語とは前提が違う場合があるため、知識が狭い状態で開発を進めることで思わぬところで足下を掬われることもある。そんな時には実践scala入門などパラパラとめくってみると思わぬ気づきがあることもあった。
2. 場合によっては、はてブのコメントやツイートなどを見てみる
議論が紛糾している題材が結構見つかるためpros and cons(賛否両論)が見て取れる。
3. 疑問に思ったこと、詰まったところなどをAnswer抜きのQuestionだけでも書き残す
残しておくと、同じところで何度もスタックしていないか、1日の進捗、成長の度合いなどが測りやすい。
以上。
雲の高さ、空の高さ
地球に天井はないのになぜ雨は降るのか、そんなことをつらつらと考えていた。
まあ、簡単な話で高さ次第で気温差があるからだなと小学生でも分かるような適当なあたりをつけたんだけれど、それなら地域によって雲の高さ、空の高さが異なってくるはずだと思ってちょっと調べることにした。
altitude of clouds(雲の高さ)
This graphic shows cloud heights at different latitudes on the Earth. For example, a middle cloud like an altocumulus cloud would be found at a lower height in the sky at the poles than at the equator (which is in the tropics).
引用:Cloud Heights by Latitude - Windows to the Universe
altitude of tropopause(空の高さ)
引用:
概ね思った通りで緯度が低いほど空は高いと言えるらしい。また季節によっても空の高さは変わる。ただ想像と違っていたことがいくつか合って1つ目は高度が上がるにつれて気温は下がる一方ではないことだ。
引用:
高度が上がるにつれ、下がった気温は二回上昇する。変化の様子が緯度によって違うのも面白い。
もう1つは左の図にあるように、僕が漠然と空と呼んでいたものはいくつかの層に分けて考えられていて僕らが普段体験している気象現象が起こる大気の層を対流圏(troposphere)と呼ぶらしい。
あと、国際的には海抜100km以上を「宇宙」と呼ぶ取り決めらしく、これを知っておくと某民間ロケットに百(もも)という名前がついているのにも納得がいく。
以上。