Gaucheで指定文字を指定回数繰り返す書き方

背景

Gaucheで指定した文字を指定した回数だけ繰り返したい。
Rubyでいう"a" * 5PHPでいうstr_repeat('a', 5)のようなこと。

irb(main):002:0> "a" * 5
=> "aaaaa"

目的は受け入れテストにて「x文字以上ならエラー」というケースでx文字を簡単に用意したかったから。
(Vimですぐ出来るけど、練習のためにGaucheのやり方を調べた)

結論

1文字の繰り返しでいいならmake-string

gosh> (make-string 5 #\a)
"aaaaa"

文字列で繰り返したいならリストを作ってからapplyする方法っぽい。

gosh> (apply string-append (make-list 5 "Vim"))
"VimVimVimVimVim"

ちなみにClojureでも似たような感じっぽい。

user=> (apply str (repeat 3 "str"))

追記: 他の書き方も教えて頂いた。

調べ方

まずはGaucheのドキュメントをrepeatとかcycleとかのキーワードで探したが辿り着けず。
GaucheというよりSchemeでの書き方が分かればいいはず、ということでググっていたら以下に辿り着いた。

色々な言語での実装例があって面白かった。 Repeat a string

jQueryのonでハンドラを複数登録するのとaddEventListenerで複数登録するのは微妙に挙動が違う

背景

jQueryのonにハンドラを複数登録するのと、addEventListenerで複数登録するのは微妙に挙動が違う。
それについて調べる必要があったのでメモ。

環境

Chrome バージョン 58.0.3029.110 (64-bit)

再現

formのsubmitを押した時のハンドラを複数登録する。

  • 1つ目: alert出すだけ
  • 2つ目: 例外を発生
  • 3つ目: submit本来の挙動を停止(結果として遷移しなくなる)

サンプルコード - addEventListener版

<!DOCTYPE html>
<html lang="ja">
  <head></head>
  <body>
    <form action="" id="form">
      <button type="submit">submit</button>
    </form>
    <script type="text/javascript">
      var form = document.getElementById('form');
      form.addEventListener('submit', function() {
        alert(1);
      });
      form.addEventListener('submit', function() {
        alert(2);
        throw new Error("doooon");
      });
      form.addEventListener('submit', function(e) {
        alert(3);
        e.preventDefault();
        e.stopPropagation();
      });
    </script>
  </body>
</html>

実行結果は以下のようになる。

  1. alert(1)が表示
  2. alert(2)が表示
  3. 例外が発生し、Webコンソールに出力される
  4. alert(3)が表示
  5. 何も遷移しない

つまり各ハンドラは独立して実行される。

サンプルコード - jQuery

バージョンは3.2.1と2.2.4で確認した。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
  </head>
  <body>
    <form action="" id="form">
      <button type="submit">submit</button>
    </form>
    <script type="text/javascript">
      $("#form")
        .on("submit", function () {
          alert(1);
        })
        .on("submit", function () {
          alert(2);
          throw new Error("doooon");
        })
        // このハンドラは呼ばれない
        .on("submit", function (e) {
          alert(3);
          // 本来はreturn falseだけでいいけどテストとして一応書いておく
          e.preventDefault();
          e.stopPropagation();
          return false;
        });
    </script>
  </body>
</html>

実行結果は以下のようになる。

  1. alert(1)が表示
  2. alert(2)が表示
  3. 例外が発生しているが、Webコンソールには何も表示されていない(というか見えない)
  4. 遷移する

つまり3つ目のハンドラは呼ばれていない。

原因

jQueryonで登録するハンドラはそのままaddEventListenerに渡しているわけではなく、内部的にtype毎に1つの関数でラップしている。
このラップした関数が実際のイベント発火時に処理されるわけだが、ここでは登録されたハンドラをループして処理している。
そのため、登録したハンドラが途中で落ちると後続のハンドラは動かない。実際のハンドラ呼び出しでは例外をキャッチしていない。

またハンドラの呼び出しは以下のようになっていて、falseを返すとpreventDefault()stopPropagation()を呼ぶ。

  ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
    handleObj.handler ).apply( matched.elem, args );

  if ( ret !== undefined ) {
    if ( ( event.result = ret ) === false ) {
      event.preventDefault();
      event.stopPropagation();
    }
  }

例外が発生すると当然preventDefaultなどには到達しないので、デフォルトの挙動で動く。よって、上記のサンプルではsubmitがデフォルトの通り遷移した。

ちゃんと動作確認をしないと、期待通りに動いているのか、途中で例外が起きたけどデフォルトの挙動で上手くいっているように見えたか分からない。

おわりに

ChromeのWebコンソールでブレークポイント貼ってCall Stack辿れるの便利。

LaravelでクエリビルダーやEloquentを使ったupdateの戻り値の違い

背景

Laravelアプリケーションでレコードをupdateするやり方が色々あり、戻り値の違いについて調べた。

結論

表にまとめた。

書き方 戻り値の型 更新対象がない時の戻り値
\DB::table(‘samples’)->where($where)->update($values) int(件数) 0
\App\Sample::where($where)->update($values) int(件数) 0
\App\Sample::where($where)->first()->update($values) bool(成否) 実行不可
\App\Sample::find($id)->update($values) bool(成否) 実行不可

詳細

それぞれのupdate呼び出し結果をvar_dumpしてみる。

<?php
// 初期データ
\DB::table('samples')->insert([ 'id' => 1, 'name' => 'alice' ]);

echo PHP_EOL;

echo "レコードと差分がない値で更新する場合" . PHP_EOL;
$exists_where = [ 'id' => 1 ];
$same_values  = [ 'name' => 'alice' ];

// クエリビルダーで更新(変更なし)
var_dump(\DB::table('samples')->where($exists_where)->update($same_values));

// Modelのwhere->updateで更新(変更なし)
var_dump(\App\Sample::where($exists_where)->update($same_values));

// Modelのwhere->first->updateで更新(変更なし) -> bool(true)
var_dump(\App\Sample::where($exists_where)->first()->update($same_values));

// Modelのfind->updateで更新(変更なし) -> bool(true)
var_dump(\App\Sample::find($exists_where['id'])->update($same_values));

echo "レコードが存在しない場合" . PHP_EOL;
$not_exists_where = [ 'id' => 999 ];

// クエリビルダーで更新(変更なし)
var_dump(\DB::table('samples')->where($not_exists_where)->update($same_values));

// Modelのwhere->updateで更新(変更なし)
var_dump(\App\Sample::where($not_exists_where)->update($same_values));

// Modelのfirstおよびfindはnullが返るので実行不可

実行すると冒頭の表の通り、int(件数)を返すものとbool(成否)を返すものがある。
何故違うかは、それぞれの実行クラスを見ると分かる。

<?php
var_dump(get_class(\DB::table('samples')->where($exists_where)));
// => string(33) "Illuminate\Database\Query\Builder"

var_dump(get_class(\App\Sample::where($exists_where)));
// => string(36) "Illuminate\Database\Eloquent\Builder"

var_dump(get_class(\App\Sample::where($exists_where)->first()));
// => string(10) "App\Sample"

var_dump(get_class(\App\Sample::find($exists_where['id'])));
// => string(10) "App\Sample"

つまりDB::table経由だろうとモデル経由だろうと、firstなりfindなりで一旦モデルを取得したかどうかで処理が異なる。
なおIlluminate\Database\Eloquent\Builderは内部でIlluminate\Database\Query\Builderに委譲している。

  • 余談1: 試していないが\App\Sample::where($where)->update($values)の場合は各種フックが走らないはず
  • 余談2: \DB::table('samples')->where($where)->first()はモデルではなくstdClassが返る

Illuminate\Database\Query\Builder

このクラスのupdateは最終的にPDOStatement::rowCountを返す。
なので常に更新件数が返る(はず)。

<?php
    /**
     * Run an SQL statement and get the number of rows affected.
     *
     * @param  string  $query
     * @param  array   $bindings
     * @return int
     */
    public function affectingStatement($query, $bindings = [])
    {
        return $this->run($query, $bindings, function ($query, $bindings) {
            if ($this->pretending()) {
                return 0;
            }

            // For update or delete statements, we want to get the number of rows affected
            // by the statement and return that back to the developer. We'll first need
            // to execute the statement and then we'll use PDO to fetch the affected.
            $statement = $this->getPdo()->prepare($query);

            $this->bindValues($statement, $this->prepareBindings($bindings));

            $statement->execute();

            return $statement->rowCount();
        });
    }

Illuminate\Database\Eloquent\Model

App\Sampleが継承しているModelのupdateは以下のようになっている。

<?php
    /**
     * Update the model in the database.
     *
     * @param  array  $attributes
     * @param  array  $options
     * @return bool
     */
    public function update(array $attributes = [], array $options = [])
    {
        if (! $this->exists) {
            return false;
        }

        return $this->fill($attributes)->save($options);
    }

saveはEloquentのフックの処理を色々したりするが、最終的には必ずboolを返す。
件数を返すことはないように見える。

なお$this->existsで見ているexistsはレコードをチェックしているわけではなく、論理上のフラグである。
モデルのコンストラクタで指定するか、insertに成功したりするとフラグが立つ。

Ruby - csvをmarkdown形式のテーブル表記にするサンプルコード

探せば色々な言語のサンプルコードがある。
気分転換に自分でも書いてみた。

整形しないで出力

require 'csv'

def print_to_table(rows)
  header   = rows[0]
  contents = rows[1..-1]

  puts "|" + header.join("|") + "|"
  puts "|" + Array.new(header.length, "---").join("|") + "|"
  contents.each do |items|
    puts "|" + items.join("|") + "|"
  end
end

# 実行
csv = <<EOS
Title,T,Tit
Text,Long Long Text,Awesome
TextText,Text,Some
EOS
rows = CSV.parse(csv)

print_to_table(rows)
|Title|T|Tit|
|---|---|---|
|Text|Long Long Text|Awesome|
|TextText|Text|Some|

整形して出力

require 'csv'

def print_to_table_pretty(rows)
  # 幅に応じて空白を後ろに追加する
  def align_cell(str, width)
    # str + (" " * (width - str.length))でもいい
    str.ljust(width, " ")
  end

  column_widths = Array.new(rows[0].length)
  # 各列の最大幅を記録する
  rows.each do |row|
    row.each_with_index do |text, i|
      current_width    = column_widths[i] || 0
      column_widths[i] = [current_width, text.length].max
    end
  end

  # 各行を列ごとの最大幅に合わせながら出力する
  rows.each_with_index do |row, row_i|
    print "|"
    row.each_with_index do |text, y|
      print " " + align_cell(text, column_widths[y]) + " |"
    end
    puts

    # ヘッダー行の後に入れる --- の行
    if row_i == 0
      print "|"
      column_widths.each do |width|
        print " " + align_cell(("-" * width), width) + " |"
      end
      puts
    end
  end
end


# 実行
csv = <<EOS
Title,T,Tit
Text,Long Long Text,Awesome
TextText,Text,Some
EOS
rows = CSV.parse(csv)

print_to_table_pretty(rows)
| Title    | T              | Tit     |
| -------- | -------------- | ------- |
| Text     | Long Long Text | Awesome |
| TextText | Text           | Some    |

phpのini_set("max_execution_time",n)とset_time_limitの違いについて調べた

背景

phpスクリプトタイムアウト上限を伸ばすには2つの方法がある。

このうちset_time_limitのドキュメントにこのような記述がある。

デフォルトの制限値は 30 秒です。 なお、php.iniでmax_execution_timeの 値が定義されている場合にはそれを用います。

これを僕は「常にmax_execution_timeが優先される」ように解釈した。調べてみた。

結論

「常にmax_execution_timeが優先される」ようなことはない。
set_time_limitini_set('max_execution_time', n)のラッパーというだけな気がする。

調査

OSはMacphpのバージョンは7.0.14。

$ php -v
PHP 7.0.14 (cli) (built: Apr  1 2017 23:41:23) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies

まずは実際に挙動を確認

<?php
// 初期値を確認
var_dump(ini_get("max_execution_time"));

// ini_setで上限値を更新
ini_set("max_execution_time", 60);
var_dump(ini_get("max_execution_time"));

// set_time_limitで上限値を更新
set_time_limit(120);
var_dump(ini_get("max_execution_time"));

コマンドライン経由だとmax_execution_timeは0になるので、ビルトインサーバーを起動してリクエストを投げることで確認した。

-- ビルトインサーバー起動
$ php -S localhost:9000


-- 別コンソールからリクエスト実行
$ curl "http://localhost:9000/sample.php"
string(2) "30"
string(2) "60"
string(3) "120"

もし「max_execution_timeが優先される」なら最後の出力は30もしくは60になるはずだが、実際はちゃんと値が更新されている。
だとすると、ドキュメントの一文は何を意味するのか。

英語の原文を見る

英語のドキュメントだとこう書かれている。

The default limit is 30 seconds or, if it exists, the max_execution_time value defined in the php.ini.

「デフォルト値は30秒だが、もしphp.iniにmax_execution_timeの定義があればそれをデフォルト値とする」という意味に読める。
言葉の意味は分かるが、そもそもset_time_limitは引数が必須だ。このデフォルト値とは何のことなのか。一応php本体のコードも読むことにした。

PHP本体のソースを読む

ざっくりとしか読んでいないので間違いはあるかもしれない。

処理的には両者に違いはほとんどない。
デフォルト値がどう関係するかといえば次に書く通り。

どちらも設定値を上書きするたびにカウンタはリセットされる

set_time_limitのドキュメントより。

この関数がコールされた場合、 タイムアウトカウンタをゼロから再スタートします。 言いかえると、タイムアウトがデフォルトの 30 秒で スクリプト実行までに 25 秒かかる場合に、 set_time_limit(20) を実行すると、スクリプトは、 タイムアウトまでに全体で 45秒 の間実行されます。

つまり、以下のコードはタイムアウトにならずに無限ループになる。

<?php
while (true) {
    set_time_limit(1);
    // ini_setの場合も同じ
    // ini_set("max_execution_time", 1);
}

なお、これを実際に試すとプロセスを直接killしないとCPU100%でずっと走り続けるので注意。

本体のソースコードを読んで「これini_setも同様にゼロから再スタートするのでは」と思って試した。
この挙動はset_time_limitだけだと勝手に思っていたが、ini_set('max_execution_time', n)でも同様だった。
ドキュメント的にもmax_execution_timeの項にはset_time_limitを参照するように書かれているわけなので正しい。

ちなみに手元のphp.iniは最初からmax_execution_timeが30秒で設定されていたが、仮に設定がなくてもphp本体が30秒をデフォルト値にしている。

要は「タイムアウト値のデフォルトはmax_execution_timeに依存する」というだけの話だと思う。