Go と Perl で sha1sum(md5sum) コマンドっぽい機能を実装した場合のソースコード比較

同じ機能を両方で書いてみたらどんな感じになるかなと。

  • 起動されたコマンド名から SHA1 or MD5 を求めるように切り替わる
  • コマンドライン引数があれば引数のファイルについて、なければ標準入力からの入力について SHA1(MD5) hash を求めて表示

こんな仕様で書いてみる。

モジュールの導入部分など (上が Perl、下が Go)

use strict;
use warnings;
use Digest::MD5;
use Digest::SHA1;
use constant BUF_LENGTH => 4096;
package main
import (
    "fmt";
    "hash";
    "crypto/sha1";
    "crypto/md5";
    "os";
    "regexp";
)
const (
    buf_length = 4096;
)

エラーを報告するサブルーチン

sub error {
    my $msg = shift;
    warn sprintf "%s: %s\n", $0, $msg;
}
type Error interface {}
func error (error Error) {
    os.Stderr.WriteString(
        fmt.Sprintf("%s: %s\n", os.Args[0], error)
    );
}

入力ハンドルについて、ダイジェストを求めるサブルーチン

sub digest {
    my ($is_md5, $handle) = @_;
    my $crypt = ($is_md5 ? "Digest::MD5" : "Digest::SHA1")->new();
    my $buf;
    while ( read $handle, $buf, BUF_LENGTH ) {
        $crypt->add($buf);
    }
    return $crypt->hexdigest();
}
type Handle interface {
    Read([]byte) (int, os.Error);
}
func digest (is_md5 bool, handle Handle) []byte {
    var crypt hash.Hash;
    switch is_md5 {
    case true:  crypt = md5.New();
    case false: crypt = sha1.New();
    }
    buf := make([]byte, buf_length);
    for {
        n, _ := handle.Read(buf);
        if n == 0 { break }
        crypt.Write(buf[0:n]);
    }
    return crypt.Sum();
}

メインルーチン

sub main {
    my $is_md5 = ($0 =~ /md5/);
    if (@ARGV == 0) {
        printf "%s  -\n", digest($is_md5, *STDIN);
        exit;
    }
 FILES:
    for my $filename (@ARGV) {
        if (-d $filename) {
            error(sprintf("%s is a directory", $filename));
            next FILES;
        }
        open my $fh, "<", $filename
            or do {
                error($!);
                next FILES;
            };
        printf "%s  %s\n", digest($is_md5, $fh), $filename;
    }
}
func main() {
    is_md5, _ := regexp.MatchString("md5", os.Args[0]);
    n := len(os.Args);
    if ( n == 1 ) {
        fmt.Printf("%x  -\n", digest(is_md5, os.Stdin));
        return;
    }
    FILES:
    for _, filename := range os.Args[1:n] {
        file, err := os.Open( filename, os.O_RDONLY, 0 );
        if err != nil {
            error(err);
            continue FILES;
        }
        d, _ := file.Stat();
        if d.IsDirectory() {
            error(fmt.Sprintf("%s is a directory", filename));
            continue FILES;
        }
        fmt.Printf("%x  %s\n", digest(is_md5, file), filename);
        file.Close();
    }
}

比較のために意図的に書き方を似せているところとか、

  • Perl で while(read){ $crypt->add($buf) } してるところは実は $crypt->addfile($handle) でいい

エラーチェックを省略してるところとかがあるので、そこは注意。

  • Go で2番目の戻り値を _ で受けてる所がそう

Go は強い型付けされた言語にも関わらず、関数定義以外のところで明示的な型宣言を強いられる場面が結構少ない。これは非常にいい感じですね。
あと、Go には3項演算子がないみたいなんだけど、あっても良さそうなのに何故?