Gaucheで指定した数の数字の配列を作るiota
「1から10までの配列から奇数だけを取り出す」というお題を見てGaucheだとこう書くかなって考えた時に、Gaucheで「数字を指定して範囲を取り出す関数って何だっけ」となったのでメモ。
iota
iota
だった。最初range
とかtimes
とかの単語でドキュメント引いて見つからなくてアレってなった。
Function: iota count :optional (start 0) (step 1)
[SRFI-1] startから始まり、stepずつ増加する、 count 個の要素からなる数値のリストを返します。countは 非負の整数でなければなりません。startとstepが ともに正確数であれば、結果は正確数のリストになります。そうでなければ 結果は非正確数のリストです。
ちなみに語源はギリシャ語のイオタらしい。
the index generator → index → i → ι → iota
「素数夜曲」、書店で目立つので存在知っていたけど数学的な本で難しそうと思ってスルーしていた。 これタイトルにLISPとあるけどサンプルコードはSchemeだったのか。
追記: Gauche作者のShiroさんからリプライ頂きました
@kanno_kanno lazyなリストを作るlrangeとかliotaってのもあります。リストが巨大になる時は一度に作るとメモリの無駄なので。あと、iotaというのはAPLの演算子から来ています。C++のstd::iotaも同様。
— Shiro Kawai (@anohana) May 13, 2017
使用例
冒頭のお題を解くのは簡単。
gosh> (filter odd? (iota 10 1)) (1 3 5 7 9)
カッコを入れ子にしたくなければ$を使うと良い。
gosh> ($ filter odd? $ iota 10 1) (1 3 5 7 9)
$ arg …
関数適用をチェインするマクロです。Haskellの$にヒントを得ました (意味は異なりますが)。 マクロ引数arg …中に$が出現すると、それが 関数の最後の引数の区切りとなります。例えば次のコードでは、 関数fの最後の引数が(g c d …)となります。
おまけ: 他の言語での解答
この程度だとほとんどのスクリプト言語で1行でサクッと済ませられそうだなと思ったので、ぼくが少なからず書ける言語でどう書くか試してみた。
(1..10).select &:odd?
[x for x in range(1, 10) if x % 2 != 0]
Perl。半年ぶりぐらいなのでちょっと書き方忘れてた。
grep { $_ % 2 != 0 } (1..10);
PHP。ちょっとつらい。
<?php array_filter(range(1, 10), function($n) { return $n % 2 != 0; });
Vim script。久々すぎて「Vim ScriptだっけVim scriptだっけ」ってなった。
filter(range(1, 10), 'v:val % 2 != 0')
MySQLでgroup byなしでcountとorder byを同時に使うとエラー
MySQLとLaravelの話です。
MySQLのSQL_MODE
にONLY_FULL_GROUP_BY
が指定されていると、group by無しでcountとorder byを同時に使うと以下のエラーが発生します。
ERROR 1140 (42000): Mixing of GROUP columns (MIN(),MAX(),COUNT(),...) with no GROUP columns is illegal if there is no GROUP BY clause
MySQLのバージョン
Server version: 5.6.36 MySQL Community Server (GPL)
5.6.36
以前は確認していません5.7.17
では発生しませんでした
再現
-- サンプルのテーブル mysql> desc foo; +-------+---------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+---------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | +-------+---------+------+-----+---------+-------+ 1 row in set (0.00 sec) -- sql_modeを一旦未指定にする mysql> set session sql_mode=''; Query OK, 0 rows affected (0.00 sec) -- count と order byを同時に使っても問題なし mysql> select count(*) from foo order by id; +----------+ | count(*) | +----------+ | 0 | +----------+ 1 row in set (0.00 sec) -- sql_modeにonly_full_group_byを指定 mysql> set session sql_mode='only_full_group_by'; Query OK, 0 rows affected (0.00 sec) mysql> select @@session.sql_mode; +--------------------+ | @@session.sql_mode | +--------------------+ | ONLY_FULL_GROUP_BY | +--------------------+ 1 row in set (0.00 sec) -- count と order byを同時に使うとエラー mysql> select count(*) from foo order by id; ERROR 1140 (42000): Mixing of GROUP columns (MIN(),MAX(),COUNT(),...) with no GROUP columns is illegal if there is no GROUP BY clause
通常はそんなSQL書かないでしょうが、今回Laravelを使っていてちょっとハマりました。
クエリオブジェクトを使いまわしたかった
以下のような感じで検索条件を構築したクエリオブジェクトに対して、count
と実際の検索を別々に発行したいケースです。
<?php public function something() { // \DB::table('foo')->where()...的な感じでクエリオブジェクトを作成 $query = $this->createQuery(); // 総件数を取得 $count = $query->count(); // 実際のレコードを取得 $records = $query->get(); }
実際のレコードを取得
するために$query
にはすでにorder_by
の指定が入っています。
そのため上記のエラーが発生してしまいました。
ハマったのはログに出力されたクエリを直接MySQLに実行しても通るのに、PHP経由だとエラーになったためです。
(MySQL側ではsql_modeにonly_full_group_byを付けていなかったのでエラーにならない)
config/database.phpのstrict => true
config/database.php
にはstrict
という項目があり、Laravel5.3以降ではtrueがデフォルト値のようです。
<?php 'mysql' => [ // (略) 'strict' => true, // (略) ],
これがtrueだと、MySQL接続時に以下のSQLが走ってsql_modeが設定されます。 https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Connectors/MySqlConnector.php#L178
set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
ということで、MySQL自体のsql_modeは上書きされます。
対応策
同じようなIssueではstrict => false
にすることが提案されています。
5.3 Query Builder count() Sql error - PR proposal
それが嫌な場合はcountを実行してからorder byを指定するとかが回避策になると思います。
Gauche - ファイル読み込みのサンプル
gaucheでファイルを読み込むやり方のメモ。
一行ずつ読み込んで処理する
(call-with-input-file "/Users/kanno/tmp/codes/a.scm" (lambda (in) (port-for-each (lambda (line) ; 1行ずつ行う何かしらの処理 (print line)) (cut read-line in))))
一度に読み込んで処理する
Shiroさんより教えて頂いた。
@kanno_kanno タイミングが難しいので、with-input-from-fileとport->string-lseqの組み合わせになりますね。
— Shiro Kawai (@anohana) 2017年5月4日
@kanno_kanno 私の方も読み直してて気づきましたが、file->string-lseqというのがあるような書き方でしたね。仮想的にそういうものを作りたくてもファイルを閉じるタイミングが難しいので作れないから遅延リストを使うならport->string-lseqを使ってね、という意図でした。
— Shiro Kawai (@anohana) 2017年5月5日
; 1度に全部読み込んでリストにする (use file.util :prefix file:) (dolist [line (file:file->string-list "/Users/kanno/tmp/codes/a.scm")] ; 1行ずつ行う何かしらの処理 (print line)) ; dolistを使わずfor-eachでもよい (for-each (lambda (line) ; 1行ずつ行う何かしらの処理 (print line)) (file:file->string-list "/Users/kanno/tmp/codes/a.scm"))
ちなみにリストではなく文字列そのまま受け取る場合はfile->string
というのもある。
(use file.util :prefix file:) (file:file->string "/Users/kanno/tmp/codes/a.scm")
GaucheのドキュメントをDashに追加する
背景
Gaucheのドキュメントを見るにはEmacsのinfoから引けるようにしたりブラウザで見たりとあるけど、個人的にはDashで引けると助かる気がしたので追加してみた。
手順
- GaucheのドキュメントHTMLを作成
僕はVM上のcentosでHTMLを作ったけど、Macでやるのも変わらないと思う。
-- text2htmlがなければinstallしておく $ sudo yum install -y text2html -- http://practical-scheme.net/gauche/download.html $ curl -O -L "http://prdownloads.sourceforge.net/gauche/Gauche-0.9.5.tgz % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 6307k 100 6307k 0 0 1576k 0 0:00:04 0:00:04 --:--:-- 2091k $ cd Gauche-0.9.5 $ gzip -dc Gauche-0.9.5.tgz | tar xvf - $ ./configure $ make $ cd doc $ make html
これでdoc配下にgauche-ref.html
(英語版)およびgauche-refj.html
(日本語版)が出来る。
以下は日本語版を追加するものとして進める。
- DashのDocset作成
公式の通り。
12 Any HTML Documentation
こちらに沿う。Dashingを使わない場合は以下のサイトの通りでいけるのではないかと思うけど確認はしていない。 自作Dash docsetの作り方 — Alfred + Dashの爆速リファレンス引き環境を拡張する
-- Docset用のsqlite DBとかplistとか作ってくれるやつ -- https://github.com/technosophos/dashing -- 予めGoが必要 $ go get -u github.com/technosophos/dashing $ cd <gauche-refj.htmlが置いてあるディレクトリ> $ dashing create
dashing.jsonの雛形が出来るので編集する。
{ "name": "Gauche", "package": "gauche", "index": "gauche-refj.html", "selectors": { "h1.chapter": "Chapter", "h3.subsection": "Section", "dt b": "Info" }, "ignore": [ "ABOUT" ], "icon32x32": "", "allowJS": false, "ExternalURL": "" }
index
はindex.html的なのを指定するのだけど、gaucheの場合1HTMLファイルしかない- コメントにてセクションごとにファイルを分ける方法を教えて頂いた
make htmls
とすればセクションごとに分割された html を得ることもできますよ。
- コメントにてセクションごとにファイルを分ける方法を教えて頂いた
selectors
は、セレクタにマッチした部分をDashの索引として登録する
編輯したらbuildする。
$ dashing build gauche -- こんな感じでファイルが出来る $ tree gauche.docset/ gauche.docset/ └── Contents ├── Info.plist └── Resources ├── Documents │ └── gauche-refj.html └── docSet.dsidx 3 directories, 3 files
- DashにDocsetを追加する
メニュー
のDocsets
の+
から上記のgauche.docset
を選べば出来上がり。
感想
1ファイルに全て入っているせいかDash上の検索及び表示が若干遅い時があるけど、やりたいことは出来たと思う。
ソースコードの分かりやすさって何だろう
結論はない。
背景
最近またGaucheを勉強し始めているのだが、ドキュメントを読むとdatum
という表現が良く出てくる。
Special Form: case key clause1 clause2 … [R7RS][SRFI-87] keyは任意の式です。clauseは以下の形式でなければなりません。 ((datum ...) expr expr2 …) ((datum ...) => proc)
http://practical-scheme.net/gauche/man/gauche-refj/Tiao-Jian-Shi-.html#g_t_6761_4ef6_5f0f
datum
って何だろうという疑問を持ち、調べると「dataの単数形」だと知る。
Lisp/Scheme界隈では一般的っぽいけど、こっちの世界に詳しくない僕には聞き慣れない言葉だった。
(他にも関数型では一般的?)
一瞬「datum
って分かりにくいな」と思ったのだけど、上記の通り一般的な用語なので単純に僕の知識レベルに要因があるだけだとすぐに思い直し、その勢いで最近感じているコードの可読性についてポエムを書いている。
プログラミングにおいてソースコードは書くより読むことの方が多いはずで、チーム開発をしていれば自分が書いたコードより他人が書いたコードを読むことの方が多い。 将来の自分は他人ということであれば、自分が書いたコードですら時間が経てば他人のコードになる。 必然、コードは(パフォーマンス上の理由がなければ)人が読みやすいように書くべきだ。
では「分かりやすさ」とは何だろう。というのをぼんやり考えることがある。 「面白い」「美味しい」と同じように、当人の経験やスキルや価値観に大きく影響するものであり、普遍的な「分かりやすさ」などないのではないか。
そもそも職業プログラマたるもの、わざと分かりにくいコードを書く人などいるのだろうか。どんなコードであれ本人にとってはそれは分かりやすいのではないか。本人が分かりやすいと思っている以上、周りが分かりにくいと伝えてもピンと来ないのではないか。
ソースコードについて
まずコードレベルの細かいところから言えば、例えば条件分岐1つとっても色々な書き方がある。 (以降の例は擬似コードだがハイライトのためにcode syntaxは一応javaで指定している)
// パターンA function something(type) { if (type == "a") { return "Aだよ"; } else if (type == "b") { return "Bだよ"; } else { return "なんだろうね"; } }
// パターンB function something(type) { var result = "なんだろうね"; if (type == "a") { result = "Aだよ"; } else if (type == "b") { result = "Bだよ"; } return result; }
// パターンC function something(type) { var type_patterns = [ "a" => "Aだよ", "b" => "Bだよ", ]; // 言語によっては存在しない場合のデフォルト値を指定できるが一応愚直に var result = type_patterns[type]; if (result is not null) { return result; } else { return "なんだろうね"; } }
焦点はif文なので関数名、変数名の適当さは無視する。
僕は大概パターンAかCで書く。ロジックを分けたい場合はAで書き、(結果の)データを分けたい場合はCで書くことが多い。が他の判断基準もあるし、最近では後述するように基本的にはチームに合わせる。 ちなみに僕は個人的な好みで言えば最後のelseは書かない方が好きだし、1回だけでシンプルなものであれば条件演算子を使うのが好きだ。
// パターンC function something(type) { var type_patterns = [ "a" => "Aだよ", "b" => "Bだよ", ]; var result = type_patterns[type]; return (result is not null) ? result : "なんだろうね"; }
条件分岐の例を出したが、コードレベルの流派は他にも色々ある。 ループで書くかmap,each,foldみたいなコレクション操作で書くかとか、状態をクラス変数に持って取り回すか引数でもらうようにするかとか。
どこまでメソッドを切るか、というのもコードレベルの話と言えるかもしれない。 以前は1メソッドは短いほどいいと思っていたけど、メソッド呼び出しがあちこち飛ぶと分かりにくいし、OSSとか見ていると長いけど読みやすいコードもあると知った。当然長くて汚い最悪のコードもある。
OSSの場合はパフォーマンスとかの問題でメソッドを分けていない可能性もあるが、とにかくメソッドの行数と可読性は必ずしも一致しないことを学んできた。もちろん適切にメソッドが分かれていれば分かりやすい。が、それに失敗したメソッドは、愚直に長いメソッドより分かりにくいこともあるのではないかという気がしている。 とはいえ個人的には、それでも長いメソッドの中で処理のコメントを書くならそれをメソッドに切ったらいいのではと思うけど、それは僕の「分かりやすさ」であって、万人の「分かりやすさ」ではないのだと思う。
好みといえばフォーマットが典型的だ。言語によってこれだけ規則が違うのは好みの千差万別さを表している。好き嫌いを廃止して(構文が許す限り)全ての言語でフォーマットが統一されていれば各種ツールとかの再発明も必要ないだろうがそうはいかない。 コードを書く上で気持ちよさは大事だし、フォーマットは気持ちよさに影響するのだ。
Goのように公式でツールがサポートされていなくても、最近はほとんどの言語でフォーマットの規約があるしサポートツールもある。問題はプロジェクトがそれに準拠しているかどうかだ。 イメージというか偏見かもしれないが、スタートアップの場合は「大事なのは分かるけど整備している暇があったらプロダクトコード書く」という感じで後回しにされ、大規模とかSIだとそこまで重要視されていないように思う。 ということで、フォーマット規約をきちんとルール化しているプロジェクトにはあまり出会ったことがない。SIerの頃にJava案件でEclipseのフォーマット設定が共有化されていたような記憶はぼんやりある。
他には命名規則にも好みは表れる。例えば何かの配列を表す場合、僕はnameList
よりはnames
(複数形)を好む。でも人によってはList
にした方が(多少文字数は増えても)パッと見て分かりやすいだろう。names
だともしかしたらname
と読み間違えるかもしれない。静的型付けだったらそんなミスは起こらないかもしれないが。
また昔はgetXXX
としていたが最近では単にXXX
でも別に良いと思うようになった。呼び出している側で代入していれば戻り値を返すのは明らかだ。
アーキテクチャや開発手法について
トランザクションスクリプトが好きな人がいればそうじゃない人もいる。OOPやDDDが好きな人もいればそうじゃない人もいる。UMLで設計する人もいれば口頭やplain textだけの設計で終える人もいる。 アジャイルやウォーターフォールといった開発手法もそう。
ここではアーキテクチャ/手法の良し悪しではなくて、あくまでチームメンバーにとって「分かりやすさ」は違うという観点で話している。 どれだけそのアーキテクチャが理論的に優れていても、正しくても、実行・メンテする人が理解していなければ負債でしかない。 難しいアーキテクチャを採用すべきではないということではなく、チームメンバーによって最適解は変わるのではないかという話。
個人的にはちょっと前に読んだ「ユースケース駆動開発実践ガイド」が面白かったのでこれで開発してみたいけど、自分が長期的に開発していくプロジェクトでなければ選択することはまずないだろう。
ドキュメント(コメント)について
プログラミング覚えたての頃はコメントがないと辛かった。知人に「コード読むのに邪魔だからJavadocを全て消すようなプログラム書いた」という人がいてその感覚を理解出来なかった。
ある程度プログラミング出来るようになってくると、今度はコメントはいらないと思うようになった。「結局コードを読むから」というのが理由だった。 当時はほとんど業務コードしか読んでいなかったせいもあって、コメントが正しいことを保証するには結局コードを読む必要があり、コメントに意味を感じなかった。
今では「必要かつ最小限のコメントは書くべき」という立ち位置になっている。リーダブルコードでコメントへの考えが変わったのもあるし、少しずつOSSのコードも読むようになってきて確かにコメントが理解の助けになることを感じてきた。 それまではせいぜい「どうしてもコメント書くならHowじゃなくてWhyのみ書け」という思いだったが、それだけじゃなくて例えば使用例などがあっても良い。 結局「理解の助けになる」なら何でもいいのだ。リーダブルコードにそんなことが書いてあったような気がするし書いてなかったかもしれない。忘れた。
最近、数年前の自分のコードをいじる必要があったが構造とか意図とか全然覚えていなくて、億劫ながらコードを開いたらコメントが随所に書いてあって助かった。たぶんリーダブルコード読んだ後でコメントは大事という認識が強いうちに開発したのだと思う。当時の自分を褒めてあげたい。
そんなコメントもまた「理解の助けになる」の基準は人によって異なるという問題がある。ある人にとっては自明で余計なコメントでも、ある人にとってはそれが助けになるかもしれない。
先人の知恵
リーダブルコードやClean Codeといった書籍もあるし、ネット上に「分かりやすいコード」の情報はたくさんある。 それらに共感できるスキル/マインドセットを持った人だけでチームが組まれているなら、それを基準にすればいいかもしれない。
でもたぶん、そんな簡単なことじゃない。
例えばこれは細かい例だけど=
の位置を揃えるというのがある。
var name = "xx"; var age = 10; var address = "yyy";
僕は最初こういうコードを見た時「うっ」となったけど、今ではこっちの方が見やすいと思っている。けど昔の僕のようにこれが見辛い人もいる。 または他の細かい例だと、僕は「ifの中身が1行なら{}を書かない」というのが嫌いだ。
...前処理 if(pred) return "hoge" ...後処理
一方でrubyなどにあるような後置ifで書くのは好きだ。
...前処理 return "hoge" if pred ...後処理
今の僕はこの通りだが、前述の=
揃えのように好みは変わるかもしれない。
今日自分にとって分かりやすいと思って書いたコードが、将来の自分には分かりにくいコードになっているかもしれない。
つまり先人の知恵に沿って分かりやすさを意識したコードを書いても、それが本当に長期に渡って分かりやすい保証はないのではということだ。
理想論では、そうならないように少しずつ日々のコードをリファクタすればいいのだろう。変わっていく感性/知識にコードを追従させればよい。 でも実際は日々の業務は山盛りでそこまで余裕はないし、それは面白い作業ではないし、何より1つのシステムと共に死ぬことはなくて遠くない未来に別のプロジェクトや会社に移動する。
どうするか
大きくはアーキテクチャから小さくはコードのコメントの書き方まで色々あって、人によって分かりやすさは異なる。 となると何を基準に開発するべきか。
ここ数年の僕のスタンスは「今後そのプロジェクトを長期保守するであろう人に合わせる」ことにしている。 まず既存のコードを軽く読み、コードの雰囲気を知る。それから自分の担当部分のコードを書く時は、8割ぐらいはその雰囲気に合わせる。2割ぐらいは自分の好みを混ぜる。 そこでもしレビューで修正依頼が来たとしても、よほどの理由がない限りは指摘事項に合わせる。そして大概の場合、よほどの理由はない。
同様に他の人のコードをレビューする時も、昔は細かく「僕はこう書く方が好み。なぜなら…」みたいなことも書いたが、今ではほとんどしない。
コードをたくさん読むのはメンテ(保守/機能追加)する人だ。メンテする人が一番大事だ。彼らがストレスなく読み書き出来なければ、どんなに自分にとって分かりやすいコードでも意味がない。 「こう書いたらどうですか」と提案することもあるけど最終判断はその人に任せる。
おわりに
書いてから読み直して気付いたけど、「分かりやすさ」と「読みやすさ」を一緒にして考えている。 これらは別々の話ではないかという気もする。一緒かな。