読者です 読者をやめる 読者になる 読者になる

自分を攻略していく記録

自分がやりたいことを達成するには何をすればいいのか、その攻略していく過程をつらつらと

C言語の学習にちょうど良いものを見つけた(Tutorial - Write a Shell in C)

f:id:ngo275:20170331224641j:plain

Tutorial - Write a Shell in Cを読んだ

Cの勉強にと思ってTutorial - Write a Shell in Cを手を動かしながら読み進めてみた。以下のような簡単なbashのようなシェルを作ってみようというもの。非常にわかりやすく書かれており、C言語の勉強にはもってこいだと感じた。(僕はC言語にはあまり触れたことがない。)

f:id:ngo275:20170331225229g:plain

シェルのライフサイクル

  • Initialize 設定ファイルの読み込み等。

  • Interpret ユーザー入力やファイル(stdin)を読み込んで実行。

  • Terminate メモリを解放して終了。

この流れが基本的なライフサイクルだが、今回作るシェルはbashのようなものなので、実行のたびに設定やシェルの終了がないことに注意する。プログラムの概形は以下のようになる。

int main(int argc, char *argv[]) {
    // もしあれば設定を読み込む.

    // loopするコマンドの実行. あとで実装
    lsh_loop();

    // シェルの終了
    return EXIT_SUCCESS;
}

シェルの簡単なループ

まずコマンドを扱う基本的な流れは以下のとおり。

  • Read 入力からコマンドを読み取る。

  • Parse コマンドの文字列をプログラムや引数として解釈する。

  • Execute 解釈したコマンドを実行する。

loopのコマンドの概形は以下のとおり。

void lsh_loop(void) {
    char *line;
    char **args;
    int status;

    do {
        printf("> ");
        line = lsh_read_line();
        args = lsh_split_line(line);
        status = lsh_execute(args);

        free(line);
        free(args);
    } while (status);
}

int main(int argc, char **argv) {
////

プロンプトの入力を読み取って、関数や引数に分割して、実行、そしてメモリ等を解放している。処理を終えるべきタイミングを lsh_execute で管理している。

入力の読み込み(Read the line)

ユーザがどれだけの文字の量を入力してくるのかわからないので、まずある程度の量を見積もっておき、それを超えたら再割り当てするようにする(Cではこれが定石らしい)。lsh_read_line は以下のようになる。

#define LSH_RL_BUFSIZE 1024
char *lsh_read_line(void) {
    int bufsize = LSH_RL_BUFSIZE;
    int position = 0;
    char *buffer = malloc(sizeof(char) * bufsize);
    int c;

    if (!buffer) {
        fprintf(stderr, "lsh: allocation error\n");
        exit(EXIT_FAILURE);
    }

    while (1) {
        c = getchar();

        // 終了まできたらnullにして終わり
        if (c == EOF || c == '\n') {
            buffer[position] = '\0';
            return buffer;
        } else {
            buffer[position] = c;
        }
        position++;

        // もしbufferを超えた時
        if (position >= bufsize) {
            bufsize += LSH_RL_BUFSIZE;
            buffer = realloc(buffer, bufsize);
            if (!buffer) {
                fprintf(stderr, "lsh: allocation error\n");
                exit(EXIT_FAILURE);
            }
        }
    }
}

void lsh_loop(void) {
////

最初の部分は宣言ばかりで、while以下がキモになる。while(1) はひたすら回り続けて、getchar をしているが、これはintであることに気をつける。EOFはcharではなくintだからだ。改行もしくは文字の終端がきたら終了する。それ以外はcharを連結してstringを作る。次にはじめに準備したbufferのサイズを超えていないか、になる。もし超えていたらreallocateする。

結構長めのlsh_read_lineを実装したのだが、実は少し新しいCのライブラリにはこの機能に相当する getline という関数があった。それを使うと以下のように簡単に短縮される。

#define LSH_RL_BUFSIZE 1024
char *lsh_read_line(void) {
    char *line = NULL;
    ssize_t bufsize = 0;
    getline(&line, &bufsize, stdin);
    
    return line;
}

読み込んだものを解釈(Parse the line)

今さっき読み込んだものの引数を解釈していく。ここでは簡単のために、引数の解釈にはスペースを利用する。つまり echo "this message" というコマンドがあったら、 "thismessage" のように分割された引数を持つこととなる。このような条件下ではstrtokトークナイズすれば良い。 lsh_split_line は以下のようになる。

#define LSH_TOK_BUFSIZE 64
#define LSH_TOK_DELIM " \t\r\n\a"
char **lsh_split_line(char *line) {
    int bufsize = LSH_TOK_BUFSIZE, position = 0;
    char **tokens = malloc(bufsize * sizeof(char*));
    char *token;

    if (!tokens) {
        fprintf(stderr, "lsh:allocation error\n");
        exit(EXIT_FAILURE);
    }

    token = strtok(line, LSH_TOK_DELIM);
    while (token != NULL) {
        tokens[position] = token;
        position++;
        
        if (position >= bufsize) {
            bufsize += LSH_TOK_BUFSIZE;
            tokens = realloc(tokens, bufsize * sizeof(char*));
            if (!tokens) {
                fprintf(stderr, "lsh: allocation error\n");
                exit(EXIT_FAILURE);
            }
        }
        token = strtok(NULL, LSH_TOK_DELIM);
    }
    tokens[position] = NULL;
    return tokens;
}

#define LSH_RL_BUFSIZE 1024
char *lsh_read_line(void) {
////

lsh_read_line と同じロジックであるが、先ほどはnullで終わるcharの配列を扱っていたのに対して、今回はnullで終わるポインターの配列を扱っている。はじめのstrtokポインターをはじめのトークンに与えている。またそれ以降の strtok は入力で与えられた文字列に対応するポインターたちを返し、それぞれのトークンの末尾に\0 bytesをつけている。そして文字のポインターの配列のポインターを格納している。言っててわからなくなってきた。最後に、全部の strtok が終わるとnullでトークンの配列作成が終わる。

シェルの開始

シェルの開始がもっともシェルの大切な部分になる。プロセスをUNIX上で動かすには2つしか方法がない。まず1つ目は、初期化(Init)だ。UNIXのコンピューターが起動する時、カーネルがロードされる。一度カーネルがロードされ初期化されると、カーネルはそのたった1つのプロセスのみが動き(それを初期化と言うのだが)、コンピューターが動いている間はそのプロセスはずっと動き続ける。

しかしながら、多くのプログラムはこのように初期化されないためもう一つの方法が出てくる。フォークである。OSがプロセスを複製するのだ(オリジナルが親、コピーが子になる)。fork() は子プロセスに0を返し、子プロセスは親プロセスに自身のプロセスID(PID)を返す。新しいプロセスを開始するには既存のプロセスから複製することが大切なのだ。

ところが、ここで問題がある。欲しいのは既存のプロセスの複製ではなく、違うプロセスなのだ。そこで出てくるのが exec() だ。現在のプロセスを全く異なるプロセスに書き換えることができる。まずフォークすることで2つのプロセスができる。そして、子プロセスは exec を利用していて新しいプロセスに書き換わる。親プロセスはこれをしながらも他のことを行い続けることが可能で、子プロセスとの関連は持ち続ける。

起動のコードはこんな感じになる。

int lsh_launch(char **args) {
    pid_t pid, wpid;
    int status;

    pid = fork();
    if (pid == 0) {
        // 子プロセス
        if (execvp(args[0], args) == -1) {
            perror("lsh");
        }
        exit(EXIT_FAILURE);
    } else if (pid < 0) {
        // フォークでエラー
        perror("lsh");
    } else {
        // 親プロセス
        do {
            wpid = waitpid(pid, &status, WUNTRACED);
        } while (!WIFEXITED(status) && !WIFSIGNALED(status));
    }
    
    return 1;
}

#define LSH_TOK_BUFSIZE 64
#define LSH_TOK_DELIM " \t\r\n\a"
char **lsh_split_line(char *line) {
////

プロセスをまずフォークするが、そのpidが0の時は子プロセスに相当する。この時に、ユーザーからの入力を受けてコマンドを実行したいので、 execvp を利用している(execの仲間)。execvpv はstringの配列(ベクトル: vector)を引数にしているためで、 p はプログラムが実行する時にファイルのフルパスを与えるのではなく、その名前を与えてOSに探してもらうことを表す。

もし exec が-1を返す時はエラーがあったことになる。perror はシステムエラーをプログラムの名前とともにプリントする。エラーがどこからきているか検証できる。

pid < 0 はフォークでエラーがあった時である。

if文の最後のブロックはフォークが成功し、親プロセスの時の処理が対応する。子プロセスの実行完了を親プロセスは待ってあげねばならない。waitpid はプロセスの状態が変わるまで待機する。ただ、この関数には多くのオプションがある(exec と同様に)。プロセスは正常に終了することもあれば、エラーや強制終了で終わることがあって多くのことを想定しないといけない。そこでここでは、waitpid はプロセスが終了するかキルされるまで待機することにする。

シェルのビルトイン

lsh_loop の中で lsh_read_linelsh_split_line を呼び出しているが、lsh_launch は呼び出していない。それは、もしユーザーがディレクトリを移動する時、chdir() を使うことになるが、カレントディレクトリはプロセスのプロパティだからである。親プロセスの持つカレントディレクトリは移動前のままになる。子プロセスも親プロセスの持つカレントディレクトリを継承することになる。

同様に、もしexit というプログラムがあるとして、exit を読んでももともと呼ばれていたシェル自体を終了することができない。終わらせたいシェルの中に組み込む必要がある。多くのシェルは~/.bashrc にそうした設定が書かれている。

いくつかのコマンドをシェルそのものに加えていかねばならない。ここで実装するのはcdhelpexit である。それらは以下のようになる。

/* 
ビルトインシェルのコマンドに対応する関数の宣言
*/
int lsh_cd(char **args);
int lsh_help(char **args);
int lsh_exit(char **args);

/* 
ビルトインシェルの関数に対応するコマンドの宣言
*/
char *builtin_str[] = {
    "cd",
    "help",
    "exit"
};

int (*builtin_func[])(char **) = {
    &lsh_cd,
    &lsh_help,
    &lsh_exit
};

int lsh_num_builtins() {
    return sizeof(builtin_str) / sizeof(char *);
}

/* 
ビルトイン関数の実装
*/
int lsh_cd(char **args) {
    if (args[1] == NULL) {
        fprintf(stderr, "lsh: expected argument to \"cd\"\n");
    } else {
        if (chdir(args[1]) != 0) {
            perror("lsh");
        }
    }

    return 1;
}

int lsh_help(char **args) {
    int i;
    printf("Shuichi Nagao's LSH\n");
    printf("Type program names and arguments, and hit enter.\n");
    printf("The following are built in: \n");

    for (i = 0; i < lsh_num_builtins(); i++) {
        printf(" %s\n", builtin_str[i]);
    }

    printf("Use the man command for information on other programs.\n");

    return 1;
}

int lsh_exit(char **args) {
    return 0;
}

int lsh_launch(char **args) {
////

lsh_cd はまず二つ目の引数が存在するか確認し、なければエラーを出す。あればchdir() を呼んでいる。lsh_help はいい感じのメッセージを出していて、exit は0を返してシェルのループを終了する。

ビルトインとプロセスを組み立てる

あとは残るは lsh_execute() の実装だ。これはビルトインとプロセス両方の起動を行う。

int lsh_exit(char **args) {
    return 0;
}

int lsh_execute(char **args) {
    int i;

    if (args[0] == NULL) {
        return 1;
    }

    for (i = 0; i < lsh_num_builtins(); i++) {
        if (strcmp(args[0], builtin_str[i]) == 0) {
            return (*builtin_func[i])(args);
        }
    }

    return lsh_launch(args);
}


int lsh_launch(char **args) {
////

最後の仕上げ

必要なヘッダーファイルをインクルードする。

#include <stdio.h>
// fprintf()
// printf()
// stderr
// getchar()
// perror()
#include <sys/wait.h>
// waitpid() and associated macros
#include <unistd.h>
// chdir()
// fork()
// exec()
// pid_t
#include <stdlib.h>
// malloc()
// realloc()
// free()
// exit()
// execvp()
// EXIT_SUCCESS, EXIT_FAILURE
#include <string.h>
// strcmp()
// strtok()

あとは gcc -o main main.c して ./main で実行ができる。ここで実装したのは非常に簡単なbashのようなシェルで、以下のようないくつかの問題はある。コードはgithubにあげておいた。

  • スペースで引数を判断するので"“で囲まれた複数の単語までそれぞれが引数として認識されてしまう。

  • パイプライン処理は対応していない

  • ビルトインのコマンドが少なすぎる

  • ワイルドカードで検索(glob)できない

※CのライブラリやUNIXについてわからないことがあればここが非常に参考になるとのこと。

2017年の4分の1がもう過ぎるので読んだ本を振り返ってみる

2017年の4分の1が終わろうとしているので、振り返りを兼ねて読んだ本をメモしておく。

f:id:ngo275:20170327101448j:plain

1月

ヘビーな技術書をガッツリ読んでいた。

Lisperのポール・グレアムが書いた本。前半はアメリカでの初等教育において、オタクがいじめられることについて批判していたのがなぜか印象に残っている。エンジニアでこの本を読んだことがあるという人は多いみたい(少なくとも身の回りには)。

  • 詳解Swift第3版

2016年の年末に出版されたSwift3対応のもの。Swiftを勉強し始めて間もない頃に詳解Swift(一つ古い版)を挫折したが、ある程度わかってきてから読むとものすごく勉強になる。Xcodeの使い方は一切触れずにSwiftという言語にフォーカスしているため、これをマスターしてもアプリは作れない。

  • コンピュータの構成と設計 第5版 上

コンピュータサイエンスの勉強ならこれ、と言われた。序盤はスラスラいけるが、パイプライン処理の詳細な話あたりから急にスピードダウンし、かなり時間をかけて読み進めた。時間がない時に、軽い気持ちで読むものではない本。時間があって勉強したい時にとても良いかも。

  • そのエンジニア採用が不幸を生む

人材系で働いている人に勧められた本。エンジニアとしてやっていきたいと思っていたら読むべき。理系だしなんとなくパソコン好きだからという理由でエンジニアを選んでしまうことがいかに危険か。

2月

1月にヘビーな技術書をずっと読んでいたので、技術から少し離れてビジネス系を読んだ。

  • HARD THINGS

起業家ベン・ホロウィッツが書いた本。twitterFacebookに出資したVCの創業者。CEOにしかわからない辛さがありありと描かれていた。特に起業する・したい人にはすごく参考になる本。

  • LIFE SHIFT

今後平均寿命が100歳を超えるということは科学的に必然だそうで、そうなった時に今のライフスタイルだと、生きていくことが困難になるので、どういう生き方が生まれてくるのか、という話。60代で定年になってから残り30~40年どうやって生きていくのか、いろいろ考えさせられる。

  • 暗号解読 上

暗号技術の歴史に関して。暗号が必要になった経緯や、その時に利用されていた暗号など。暗号の成長について非常にわかりやすく説明されていた。大戦中の暗号解読者の戦いは面白かった。

  • 88.68%勝てる営業

家にあった。カタカナが多すぎて読みにくかった。

3月

TEDを見ている中で、なんでみんなこんなにプレゼンがうまいのか、と気になってプレゼン関連の本を読んだ。

  • ゼロ・トゥ・ワン 君はゼロから何を生み出せるか

PayPal創業者ピーターティールのマインドセットがわかりやすく書かれている。「競争よりも独占をしろ」とか。非常に勉強になる。

  • 個を動かす

同じ高校の大先輩が活躍しているのでどういう人なのかと気になって読んだ。

  • アメリカの大学生が学んでいる「伝え方」の教科書

プレゼンのアウトライン作成から細かい作り込みや、プレゼンのカンペの作り方までわかりやすく説明してある。本というよりも教科書に近い。

  • ビジネスと人を動かす 驚異のストーリープレゼン

上とは違って教科書というより本。読んでいてこっちの方が引き込まれる。引き込まれる話し方について説明しているからだと思うけど。

人工知能について非常に丁寧に説明してある。勉強になった。

4月はお金の回り方に関するものと技術書を読み漁ろうと思う。

ペアプロして気づいた、デキるエンジニアと未熟なエンジニアとの差

デキるエンジニアとペアプロをした

デキるフルスタックエンジニアとペアプロすることになった。かなり繊細な箇所をリファクタリングするというタスクだった。彼は、高校時代に孫さんに影響を受け、高校卒業後に渡米し、カリフォルニアの大学に通う。そして卒業して帰国後、未踏クリエイターに選出されてひたすらLispを触っていた。ちなみに、エディタはEmacsで、サーバーサイドはもちろんiOSAndroidEmacsで実装している。一方で、僕は彼に比べれば経験値も技術力もかなり劣る。

二人の実力差は大きいのだが、実装するのは彼の方だった。力量差があるもの同士のペアプロというと、未熟な方が教わりながら実装する、ということをイメージしてしまう。もちろん実力差が小さくなればそんなこともないのかもしれないが…。

作業速度の速さ

圧巻だったのは速さだ。Emacsを使っており、ホームポジションから本当にほとんど離れない。プログラミング歴が浅いとどうしてもトラックパッドや矢印キーに手を伸ばしてしまうが、その行為がいかに時間を奪っているのかを痛感した。複数の画面をめまぐるしく行き来し、手とパソコンと一体になっている感が強い。学生ベンチャーにいるとなかなかこのレベルの速さでタイプする人に出会えない。タイピングが早い人はいくらでもいるが、実装そのものの速さはペアプロをしないと体感できない。

ドキュメントをまず当たる

Emacsで実装しているとXcodeのように強力な補完がない。それに加えて、彼はiOSエンジニアではなく、むしろサーバーサイドが得意と言っていた。にもかかわらず的確に正しい関数を書いていくのだ。よくこんなに覚えているなと思っていたところ、彼は頻繁に公式ドキュメントを参照していることに気づいた。少しでも曖昧なところがあると Dash というアプリで公式ドキュメントを確認するのだ。これを繰り返しているうちにしっかりと身につくようである。言われてみると、自分が実装するときは疑問解決の際、ブログやQiita、スタックオーバーフローなどばかり参考にし、公式ドキュメントを参照する機会が少なかった。まず公式ドキュメントを参照するという癖がなかったのだ。見習わなければならない。

Dash
f:id:ngo275:20170321233504p:plain:w120

お互いめちゃくちゃ疲れる

こっちは普段慣れない速度でコードを考えていたので非常に集中力を使ったが、実はドライバー役の彼も普段より全然疲れたそうだ。一人で実装しているとぼーっとする時間がどうしても生まれてしまうのが、ペアプロをするとそれがなくなる。いいことだが長時間行うには適度に休憩を挟まないといけないみたいだ。

実装や思考速度は普段より早いのだが、意外とついていけることにも気づいた。逆にいうと、彼の速度でエディタを扱えるようになると実装速度が2倍くらいになると痛感した。

レベルの高い人に囲まれる良さ

こういう高いレベルの人に囲まれる良さも感じた。学生ベンチャーにいると優秀な学生に出会えるがなかなか出会えない人もいるのが現実だ。どうしても幅が狭まってしまう。特に技術力が高いエンジニアはレベルが高い環境と求めると思うので、そういうところに飛び込んでいく度胸が求められるのかもしれない。四月からの新しい環境が楽しみだ。

メルカリ(mercari)のインターンでアメリカに来ました(終わり)

一週間もそろそろ終わり

1週間メルカリのインターンでアメリカのメリーランド州に行ってきた。

f:id:ngo275:20170313200918j:plain

渡航前~1日目

2日目

3日目

4日目

5、6日目はそこまで収穫がないので省略。 アメリカで感じたことを感じたことベースにメモっておく。

メルカリの日米における認知度の差

日本でメルカリというと、ITベンチャーの中で最も勢いがあってアメリカでもいい感じ、という印象があったが、いざアメリカに来てみると実際は全然違っていた。まるで認知されていないのだ。全米2000万ダウンロードと言われているが、数十人に聞いて、メルカリを知っていると答えたのが数人、メルカリで売買したことがある、と答えた人は皆無だった。メリーランドには30万点の商品があるそうだが、結局本格的なユーザーを見つけることができなかったのである。

現地の人にヒアリングをしていて、オンラインショップで有名なサービスには、AmazoneBay、Craigslist、あとはEtsyがあった。他にメルカリの競合だと思われるのがOfferUp、Wish、letgoあたりだろうか(AppStoreでeBayと検索するとこのあたりのアプリがヒットした)。はじめにあげた4つのサービスは、ヒアリングしたほとんどの人が利用経験があると答えた。AmazoneBay、Craigslistは1994、1995年に生まれて現在も生き残っているサービスなので利用者が非常に多いが、Etsyはその10年遅れで登場し、ハンドメイドのブランドを見事作り上げ、同様に高い認知度だった。メルカリ、OfferUp、Wish、letgoは2011年からの数年で誕生したものばかりで、どれも新しく、アメリカ人からの認知度はいずれも低かった。メルカリはここからが正念場だと痛感させられた。

Amazon eBay Craigslist Etsy OfferUp Wish mercari letgo
1994~ 1995~ 1995~ 2005~ 2011~ 2011~ 2013~ 2015~
f:id:ngo275:20170313122128p:plain:w70 f:id:ngo275:20170313122142p:plain:w70 f:id:ngo275:20170313122156p:plain:w70 f:id:ngo275:20170313122212j:plain:w70 f:id:ngo275:20170313122244p:plain:w70 f:id:ngo275:20170313122253p:plain:w70 f:id:ngo275:20170313122228p:plain:w70 f:id:ngo275:20170313122305p:plain:w70

思っている以上にキャッシュレス

ペイモという割り勘アプリを作っているので、アメリカのお金の扱いについて興味があり、あえてドルを持って行かずにクレジットのみで生活してみた。電車のパスも、自販機の飲み物クレジットで購入できる。ただ、バスに乗る時はクレジットだけでは厳しいかもしれない。アメリカでは友達間で割り勘する時、Venmoというアプリを利用することが多い。今回会った人はほぼみんなインストールしてあった。Venmoができる以前はPaypalで送金していたそうだがVenmoの方がお得で今はVenmoが主流だそう。(メルカリよりもペイモに食いついてくれる人が何人かいた。)

ペイモ Venmo
f:id:ngo275:20170313123724p:plain:w120 f:id:ngo275:20170313123741p:plain:w120

シェアリングエコノミーの精神

移動にはUber、宿にはAirbnbを利用した。両方とも、知らない人の車、家にお世話になる、というサービスである。Uberはその場で呼び、Airbnbは前日に宿を抑える、というイメージで利用した。Uberの他にもLyftという似たサービスがあり、Uberより割高だが安心感は強いという。朝早くても夜遅くてもUberで車を見つけられるので、良くも悪くも時間を気にせず行動できる。Airbnbは今回初めて利用したが、そこらへんのホテルよりホスピタリティーがあって、非常に満足度が高い。一人で5000円程度の都市近郊の宿だと少し古くて汚いという印象である。

日本では両方ともあまり流行っていない(そもそも法律的な問題がある)がアメリカでは当たり前になっていて、特にお金のない若者にとって、もはやなくてはならないサービスになっていることを身をもって感じた。

Uber Lyft Airbnb
f:id:ngo275:20170313124144p:plain:w120 f:id:ngo275:20170313124157p:plain:w120 f:id:ngo275:20170313124211j:plain:w120

世代間の差

日本よりシェアリングエコノミーやいろいろなテクノロジーが進んでいる、とは言え、アメリカでもやはり世代間のリテラシー格差が大きいのは同様だった。むしろ格差という意味では日本より大きいのかもしれない。現地に暮らす50代の方3人にヒアリングをしたのだが、スマホを持っていなかったり、スマホは使っているけどカード情報や個人情報を入力したくない、と言っていたりして、その世代になると日本と意外と変わらない印象だった。もちろん、彼らの意見が全てではないのは当然ではあるが。こうした上の世代の方が生活にも余裕があるので、シェアリングエコノミーに参画すると質が一気に上がるポテンシャルがある。

日米で気になった文化の差

今回一番気になった文化の違いは、アメリカだとシェアハウスや寮生活をする若者の割合が多く、かつ、社会人になっても続ける人がそれなりにいるということだ。家賃が非常に高く、社会人になってもよっぽど高給取りじゃないと一人暮らしはできないらしい。それに伴って頻繁に引っ越すので、いらないものがよく出てきて、Facebookグループで売り払ったりすることもちらほらあるそう。もしくは寄付してしまうそうだ。日本だと寄付の文化はあまりないと感じる。

ハッカソンがなんかすごそう

薬局で働いていると言っていた友達が、実はアプリ作ったりしていてハッカソンにも出たこともあるらしい。FBIがハッカソンのスポンサーになっていたり、大掛かりなものが多かった。今回は時間もなかったし、そもそもハッカソンという発想がなかったが、今度アメリカにハッカソン目的で旅行しようと思う。こんな感じのハッカソンがある

まとめ

とりとめもなく感じたことを書いたが、ただ旅行に行ってる時とは違って学びが大きい1週間だった。旅行ついでに海外の大学に忍び込むのはけっこう楽しいかもしれない。あとハッカソン出よう。

f:id:ngo275:20170309221927j:plain