2017/04/06


今日こんなツイートをした。

qt_luigi さんからどうしてかを聞かれたので説明したいと思います。

golang では宣言した位置で初めて自動変数としてメモリが確保され、ゼロクリアされます。

for i := 0; i < b.N; i++ {
    var foo Foo
    bar, err := doSomething()
    if err != nil {
        continue
    }
    foo.v = bar
    fmt.Fprintln(ioutil.Discard, foo)
}

なので例えばこの様なコードで doSomething() が err を返した場合、foo が無駄に初期化されてしまうのです。

本当にそうなのか、以下のベンチマークを見て貰えると分かります。

package var_test

import (
    "errors"
    "fmt"
    "io/ioutil"
    "testing"
)

type Foo struct {
    v *Bar
    b [1000]int64
}

type Bar struct {
}

func doSomething() (*Bar, error) {
    return nil, errors.New("bad some")
}

func BenchmarkVar1(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var foo Foo
        bar, err := doSomething()
        if err != nil {
            continue
        }
        foo.v = bar
        fmt.Fprintln(ioutil.Discard, foo)
    }
}

func BenchmarkVar2(b *testing.B) {
    for i := 0; i < b.N; i++ {
        bar, err := doSomething()
        if err != nil {
            continue
        }
        var foo Foo
        foo.v = bar
        fmt.Fprintln(ioutil.Discard, foo)
    }
}

通るはずのない所に Println を書いたのはコンパイラが最適化して消し去ってしまわない様にです。(本当に消し去るかは未確認)

goos: windows
goarch: amd64
pkg: github.com/mattn/go-sandbox/var
BenchmarkVar1-4     10000000           226 ns/op           0 B/op          0 allocs/op
BenchmarkVar2-4     2000000000           1.26 ns/op        0 B/op          0 allocs/op
PASS
ok      github.com/mattn/go-sandbox/var 5.222s

ちょっと大げさに int64 変数が1000個保持されるような struct で確認しているので180倍近い差が出ていますが、少し大きめの構造体でも幾らかは差が出てしまいます。early return は golang の良い文化ではありますが、さらに変数の宣言位置も気を付けておくとよりパフォーマンスの良いアプリケーションになっていくでしょう。

みんなのGo言語【現場で使える実践テクニック】 みんなのGo言語【現場で使える実践テクニック】
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧 大輔, 鈴木健太, 稲葉貴洋
技術評論社 大型本 / ¥30 (2016年09月09日)
 
発送可能時間:

Posted at by



2017/03/09


以下の記事は Java について触れていますが、Java を dis っている訳でもありませんし、冗長に見える例を意図的に使っています。

最近 Twitter で golang に Generics が無い事についてずいぶんと盛り上がったのですが、僕の意見をこのブログにも書いておこうと思います。

僕も煽り言葉で「golang に generics は要らない」と書いた事もありましたが、本心は「Generics 欲しいと思った事はあるけど無くても生きてこれた」というところです。

「golang の型システムは貧弱だ、Generics が無いから駄目だ」、そんな意見をたまに見ます。実際どんな場合に Generics が欲しいかというと、ある特定の意味を持った型を手続き処理に渡したい場合です。例えばその一つがコンテナです。Java の List<SomethingType> などがそれにあたります。この List の中身をぐるっと回して処理したい、または処理する関数を以降ほかの型でも使用したい、といった物です。golang に Generics が欲しいと言っておられる方の多くはこれが欲しいと言っておられるのだと思います(違っていたらごめんなさい)。例えば整数か浮動小数点か分からない数値をコンテナに持たせてその合計を出す処理を考えてみます。

import java.util.List;
import java.util.Arrays;

public class Foo {
    public static Integer sum(List<Integer> list) {
        int ret = 0;
        for (Integer i : list) {
            ret += i;
        }
        return ret;
    }

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(5,2,3,1,4);
        System.out.println(sum(list));
    }
}

Java だとこんなところでしょうか。golang だと container パッケージもありますが、slice で処理できる物は slice のまま扱いますね。さて、この Java のコードを再利用可能にしてみます。

import java.util.List;
import java.util.Arrays;

public class Foo {
    public static <T> T sum(List<T> list) {
        int ret = 0;
        for (T i : list) {
            ret += i; // コンパイルエラー
        }
        return ret;
    }

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(5,2,3,1,4);
        System.out.println(sum<Integer>(list));
    }
}

ここまでは想像できますよね。コメントにも書きましたが、Java もアドホック多相のまま演算子を処理するコードはコンパイル出来ないのです。そうなると「足し算する関数を持ったインタフェース」が必要になる訳です。java.lang.Number にもそんな物は定義されてないので、自分で意味づけする必要がある訳です。アドホック性を保ったまま Java で実装するとどうなるでしょう?

import java.util.List;
import java.util.Arrays;

interface MyNumber {
    MyNumber add(MyNumber rhs);
}

class MyInt implements MyNumber {
    private int x;
    MyInt(int n) {
        this.x = n;
    }
    public MyNumber add(MyNumber rhs) {
        return new MyInt(this.x + ((MyInt)rhs).x);
    }
    public int value() {
        return this.x;
    }
}

public class Foo {
    public static MyNumber sum(List<MyNumber> list) {
        MyNumber ret = list.get(0); 
        for (int i = 1; i < list.size(); i++) {
            ret = ret.add(list.get(i));
        }
        return ret;
    }

    public static void main(String[] args) {
        List<MyNumber> list = Arrays.asList(
            new MyInt(5),
            new MyInt(2),
            new MyInt(3),
            new MyInt(1),
            new MyInt(4)
        );
        System.out.println(((MyInt)sum(list)).value());
    }
}

おや、なんかめんどくさくなりましたね。Double 版も作りたくなったとしたらなんか大変そうだしもし Int と Double を足せる物を実装するとなったらもっと大変そうに見えませんか?golang でも同じ事をやってみましょう。

package main

import (
    "fmt"
)

type Numeric interface {
    Add(Numeric) Numeric
}

type Int int

func (i Int) Add(n Numeric) Numeric {
    switch t := n.(type) {
    case Int:
        return i + t
    }
    panic("unknown type")
}

func sum(list []Numeric) Numeric {
    ret := list[0]
    for i := 1; i < len(list); i++ {
        ret = ret.Add(list[i])
    }
    return ret
}

func main() {
    list := []Numeric {
        Int(5),
        Int(2),
        Int(3),
        Int(1),
        Int(4),
    }
    fmt.Println(sum(list))
}

Java も golang も意図的に冗長に書いているので、なんだそのコードはといったご意見もあるかと思います。

上記で Int と Double を足せる物を作る場合について触れましたが、これ golang でやってみたいと思います。

package main

import (
    "fmt"
)

type Numeric interface {
    Add(Numeric) Numeric
}

func sum(list []Numeric) Numeric {
    ret := list[0]
    for i := 1; i < len(list); i++ {
        ret = ret.Add(list[i])
    }
    return ret
}

type Int int

func (i Int) Add(n Numeric) Numeric {
    switch t := n.(type) {
    case Int:
        return i + t
    case Float:
        return i + Int(t)
    }
    panic("unknown type")
}

type Float float64

func (f Float) Add(n Numeric) Numeric {
    switch t := n.(type) {
    case Float:
        return f + t
    case Int:
        return f + Float(t)
    }
    panic("unknown type")
}

func main() {
    list := []Numeric {
        Float(5),
        Int(2),
        Int(3),
        Float(1),
        Int(4),
    }
    fmt.Println(sum(list))
}

そんなに複雑じゃないと思いませんか?上記でも書いた通り、Java もアドホック性を保ったままコンテナを使うには interface を作らないといけないのです。それは golang も同じ話なのです。

やりたい内容によっては Java も golang もフェアなのです。さらに言うなら Duck Type を使ってシグネチャさえ満たせば interface を引数に持った手続き処理に渡せるのは、Java にないメリットになり得ます。

僕は golang も Java も C++ も好きです。Generics が便利なのも知ってます。この記事も Java を dis っている訳ではないです。ただ言いたいのは「golang は型が貧弱だ、Generics を実装しないのは開発者の怠慢だ」といった意見に同調する人たちに「golang は Generics が無くても事足りてしまう事がある」という1例を見せたいだけなのです。もちろんリフレクションや Object のまま扱えばもっと短くできる事は知っています。また本当に Generics が欲しくなるケースもあるのは事実です。ただ、そんな荒さがししてるくらいなら、まず golang に触れてみたらいいんじゃないか、そう思う訳です。

追記

例えが偏り過ぎた事もあって反応頂いてるみたいです。

golang と Generics と吾 - Qiita
http://qiita.com/yuroyoro/items/6bf33f3cd4bb35469e0b
Java の Generics にもの思い - Qiita
http://qiita.com/t2y/items/139c6a38173d7750ddfc
みんなのGo言語【現場で使える実践テクニック】 みんなのGo言語【現場で使える実践テクニック】
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧 大輔, 鈴木健太, 稲葉貴洋
技術評論社 大型本 / ¥30 (2016年09月09日)
 
発送可能時間:

Posted at by



2017/03/07


おなじみC/C++から使えるJSONライブラリを紹介するコーナー。まずは過去のまとめ。

ずいぶんと前から GitHub Trend の C++ 言語枠には登場していましたが、このコーナーでレビューしてませんでした。

GitHub - nlohmann/json: JSON for Modern C++

JSON for Modern C++

https://github.com/nlohmann/json

特徴(目指すところ)は以下の通り。

  • Python の様な操作感で、ファーストクラスオブジェクトの様に扱える事
  • シングルヘッダで他のライブラリに依存しない事
  • テストカバレッジ 100% を目指す

触ってみた感じ picojson に近いです。picojson よりもオペレータが沢山定義されているのでバイナリにした際には picojson よりも大きくなるかもしれません。今日はこの json.hpp を使って、wandbox の shebang コマンドを作ってみました。

wandbox は画面で入力したソースコードを多種多様なコンパイラやインタプリタを使って実行してくれるウェブサービスです。API が公開されているので扱いやすいインタフェースを自分で作る事ができます。

wandbox/API.rst at master - melpon/wandbox - GitHub

API API home is melpon.org/wandbox/api GET /list.json List compiler informations. Parameter Nothing....

https://github.com/melpon/wandbox/blob/master/kennel2/API.rst
#include <iostream>
#include <fstream>
#include "json.hpp"
#include <curl/curl.h>

size_t
write_cb(char *ptr, size_t size, size_t nmemb, std::string *strm) {
  size_t len = size * nmemb;
  strm->append(ptr, len);
  return len;
}

int
main(int argc, char* argv[]) {
  if (argc < 3) {
    std::cerr << "usage: " << argv[0] << " [compiler] [file] [arguments...]" << std::endl;
    return 1;
  }
  std::stringstream ss;
  if (std::string(argv[2]) != "-") {
    std::ifstream in(argv[2], std::ifstream::in);
    std::string line;
    std::getline(in, line);
    ss << in.rdbuf();
  } else {
    ss << std::cin.rdbuf();
  }

  nlohmann::json obj;
  obj["code"] = ss.str();

  ss.str("");
  ss.clear(std::stringstream::goodbit);
  obj["compiler"] = argv[1];
  for (int i = 3; i < argc; i++) {
    if (i > 3)  ss << "\n";
    ss << argv[i];
  }
  obj["runtime-option-raw"] = ss.str();

  ss.str("");
  ss.clear(std::stringstream::goodbit);
  ss << obj;

  std::string chunk;

  CURLcode ret;
  CURL *curl = curl_easy_init();
  if (curl == NULL) {
    std::cerr << "curl_easy_init() failed" << std::endl;
    return 1;
  }
  std::string data = ss.str();

  struct curl_slist *headerlist = NULL;
  headerlist = curl_slist_append(headerlist, "Content-Type: application/json");

  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
  curl_easy_setopt(curl, CURLOPT_URL, "http://melpon.org/wandbox/api/compile.json");
  curl_easy_setopt(curl, CURLOPT_POST, 1);
  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunk);
  ret = curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  if (ret != CURLE_OK) {
    std::cerr << "curl_easy_perform() failed" << std::endl;
    curl_easy_strerror(ret);
    return 1;
  }

  obj = nlohmann::json::parse(chunk);
  try {
    std::cout << obj["program_message"].get<std::string>();
  } catch(std::exception& e) {
    std::cerr << obj["compiler_message"].get<std::string>() << std::endl;
  }

  int code;
  ss.str("");
  ss.clear(std::stringstream::goodbit);
  ss << obj["status"];
  ss >> code;
  return code;
}

使い方は shebang として設定するだけで、第一引数にコンパイラもしくはインタプリタ名を設定します。

#!/usr/bin/wandbox-run clang-head

#include <stdio.h>

int
main(int argc, char* argv[]) {
  puts("hello clang");
  return 0;
}

こんな風に書いて実行権限を付けて実行すれば hello clang が表示されます。C言語のソースなのにコンパイラをインストールしなくても実行できて便利ですね(そうでもない)。なお扱えるコンパイラやインタプリタは wandbox から参照して下さい。ソースコードは以下に置いておきます。

mattn/wandbox-run · GitHub
https://github.com/mattn/wandbox-run
Posted at by