ushumpei’s blog

生活で気になったことを随時調べて書いていきます。

dockerに関するメモ

golangを少しだけ試して見たくなったけれど、環境を作りたくなかったのでdockerを使って見ました。特に目新しい内容ではないですが、自分用にメモします。

構成というほどでもないですが、

  • コーディング: 自分のマシン
  • 実行環境: コンテナ

とするために、まずはディレクトリを作成し、そのディレクトリをコンテナと共有して起動します。

mkdir ~/my_golang_src
cd ~/my_golang_src
docker run -i -t --rm -v `pwd`:/my_golang_src golang /bin/bash

シェルが開くので、/my_golang_srcに移動してgo runなどでソースを実行できるようになります。

redisのレプリケーション

 redisレプリケーションがとても簡単だったのでメモです。勉強も兼ねてdockerを無駄に使っています。環境は以下です。

  • macOS Sierra
  • Docker for Mac Version 1.12.3-beta29.2

 とりあえずredisが入ったAlpine Linuxのイメージを作成します。my-alpineディレクトリを作りその中にDockerfileを以下の内容で作成します;

FROM alpine:latest
MAINTAINER ushumpei
RUN apk --no-cache add redis
CMD ["/bin/sh"]
$ docker build -t my-alpine .
$ docker run -it --rm  my-alpine

 redisがインストールされた状態でAlpine Linuxが起動しました。

単一マシン上で

 先ほどのmy-alpineを使って、同一ホストで複数のredisを起動し、master、slaveのレプリケーション構成を行ってみます;

/ # redis-server --port 3679 &
/ # redis-server --port 3680 --slaveof localhost 3679 &

 本当はちゃんと設定ファイルを書いたほうがいいんだと思います。レプリケーションを確認すると;

/ # redis-cli -p 3679 INFO
...
# Replication
role:master
connected_slaves:1
slave0:ip=::1,port=3680,state=online,offset=519,lag=1
...

/ # redis-cli -p 3680 INFO
...
# Replication
role:slave
master_host:localhost
master_port:3679
...

/ # redis-cli -p 3679 set hoge fuga
OK
/ # redis-cli -p 3680 get hoge
"fuga"

とちゃんとなっているようです。

docker

 docker-composeでmaster: 1台、slave: 2台の構成を作ってみようと思います。新たにディレクトリ(docker-redisにしました)を作成し、その中にdocker-compose.ymlを以下のように作成しました。

version: '2' 

services:
  master:
    image: redis:latest
    restart: always
    ports:
      - 3679:3679
  slave_1:
    image: redis:latest
    restart: always
    ports:
      - 3680:3679
    command: redis-server --slaveof master 6379
  slave_2:
    image: redis:latest
    restart: always
    ports:
      - 3681:3679
    command: redis-server --slaveof master 6379

 slave_1slave_2からはmasterhostmasterで参照できるようになっているようで、分かるまで悩みました(参考: Compose のネットワーク機能)。docker-compose upで起動します。

 動作確認を先ほどのmy-alpineから行います。--netオプションで先ほどのdocker-compose upで起動したコンテナたちのネットワークに参加します。こうすることで、masterなどのホストを参照することができるようになります。

(ネットワーク名はデフォルトではディレクトリ名_defaultになるらしく、docker network lsでも確認できます。ディレクトリのハイフンが無視されています、どういうルールでしょう?)

$ docker run -it --rm --net dockerredis_default my-alpine

/ # redis-cli -h master INFO
...
# Replication
role:master
connected_slaves:2
slave0:ip=172.19.0.4,port=6379,state=online,offset=2367,lag=1
slave1:ip=172.19.0.2,port=6379,state=online,offset=2367,lag=1
...

/ # redis-cli -h slave_1 INFO
...
# Replication
role:slave
master_host:master
master_port:6379
...

/ # redis-cli -h slave_2 INFO
# Replication
role:slave
master_host:master
master_port:6379
...

/ # redis-cli -h master set hoge fuga
OK
/ # redis-cli -h slave_1 get hoge
"fuga"
/ # redis-cli -h slave_2 get hoge
"fuga"

感想

 redisレプリケーションってすごく簡単にできるように思えました。pub/sub使って何か作ってみたいです。

¥eあるいはedit

 mysqlの対話環境でちょっと長い処理を実行したくなった時のメモです。

 mysqlコマンドを実行すると対話環境が起動します。ただ改行を含んだクエリを書こうとすると、どこか間違えた時に書き直すのが非常に面倒です。

 そういった時は¥eまたはeditコマンドを使うと便利です。コマンドを実行するとエディタが起動し直近に実行したクエリが表示されます。編集を行い保存して閉じるとコマンドを抜けて対話環境に戻ります。あとは;を入力してエンターキーを押せば内容が実行されます。毎回エンターキーを押すのも面倒な場合は、¥e;で実行します、この場合はエディタを閉じたら即時実行されます。

 開くエディタは選べるようです。私の環境ではデフォルトはviでした。これは環境変数EDITORで設定できます。(export EDITOR=vimとか設定しときました)

 調べたいクエリがあった時に、

  1. mysql> ¥e;実行
  2. vimが開くので挿入コマンド:a!でコピーしてきたクエリをペーストする
  3. 適宜修正して:wqvimを終了する
  4. 結果を確認し、必要なら1に戻る

 という感じで使っていました。

 ちなみに¥eを実行した時に編集しているファイルは/tmp以下に作成されていることが確認できました、しかしこれはエディタを閉じると削除されるようです。ちゃんと取っておきたいなら保存した方がいいと思います。(viなら:w ~/hogehogeなど)

感想

 rails consoleでもeditコマンドが使えるみたいですirbはダメか、コマンドが違うようです?)。他の対話環境ではどうなんでしょうか?

追記: 2017/01/13 - 今手元にあるrails 5で試してみると、editコマンドは使えないみたいです。動作が確認できていた環境はすでに手を離れてしまったので再確認できません。。。何かわかり次第追記します。

git logの折り返し

 gitのlogdiff表示時の、文字列の折り返しの切り替え方をメモします。

 自分の環境(mac 10.12terminalgit version 2.8.4 (Apple Git-73))ではデフォルトで折り返しがされていたのですが、以下の指定で折り返しが無効になりました。

折り返し無効

$ git config --global core.pager "less -S"

折り返し有効

$ git config --global core.pager "less -r"

 (デフォルトで有効だったので、折り返し有効のコマンドは未確認です。。。)

 git log --graph --decorateなどでログを見たい場合は、折り返さない方が見やすい気がします。

 ちなみにcore.pagergit loggit diffで表示するときの出力コマンドを指定しているそうで、lessSオプションをつけて折り返しの制御が行われているようです。catとかも指定できるようです。moreは私の環境では文字化けしました。

アロー関数(Arrow function)の書き方色々

 アロー関数は引数や戻り値の種類によって色々な書き方ができます。分割代入を使うことで柔軟な関数をシンプルに宣言できてとても気持ちいです。

 基本的な書き方から、やや直感的ではない書き方まで、メモしていこうと思います。

ざっくり

 アロー関数の記述方法は、引数に関しては、引数なし、引数1、引数N、引数オブジェクトリテラルによる分割代入、の4種類があります。関数の処理部分に関しては波括弧、括弧なし、丸括弧の3種類があります。

  • 引数1、括弧なし
  • 引数N、丸括弧
  • 引数オブジェクトリテラルによる分割代入、丸括弧
  • 引数なし、波括弧

 を並べてみます。

引数1、括弧なし

 引数が1のときは、引数の括弧は省略できて、右側が評価され戻り値が返ります。Promiseのcatchとかすっきりします。

const factorial = n => (n > 0) ? n * factorial(n-1) : 1;

引数N、丸括弧

 引数N個は単純にfunctionのときと同じように並べるだけです。戻り値にオブジェクトリテラルを返したいときに丸括弧を使うとすっきりです。

let projection = (a, b) => ({ 
  x: (2 * a) / (1 + a * a + b * b),
  y: (2 * b) / (1 + a * a + b * b),
  z: (-1 + a * a + b * b) / (1 + a * a + b * b),
});

引数オブジェクトリテラルによる分割代入、丸括弧

 上の例と近いです。ただ引数個数が頻繁に変わる可能性がある場合この記法がいいようです。ですが、個人的に好きなので何でもかんでもこれで書きたいです。Reactの純粋な関数スタイルのコンポーネントを書いたりするとき便利です。

const Deco = ({ deco, children }) => (
  <div>
    {deco + children + deco}
  </div>
);

 呼び出すときは引数で代入しているプロパティをもったオブジェクトを渡せば大丈夫です。引数いっぱいなのは嫌だけど、オブジェクトまるまる渡したら何されるかわからない、といった不安を綺麗に取り去ってくれます。

引数なし、波括弧

 グローバルなオブジェクトに対して何かしたいときに使う、気がします。波括弧なので複数行書くことができます。

const values = () => {
  const inputs = [...document.querySelectorAll('input')];
  const values = inputs.map(input => input.value)
  return values;
};

感想

 戻り値に関して補足です。1行で済む場合は括弧なしか丸括弧、複数行必要なときは波括弧でいいと思います。

 引数オブジェクトリテラルによる分割代入は、修正による影響を少なくできるし、オプションのようなものを渡すことが簡単にできるのでかなり好きです。

 説明の中で複数行とか言っていますが、宣言とか式とか、ちゃんとした言葉をいい加減覚えなければいけないなと思います。

Reactコンポーネントを純粋な関数で書こう

 Reactコンポーネントの書き方は色々あり、es6を使う場合の選択肢は以下の2つがあると思います;

  • クラス
  • 純粋な関数

クラス

 classの構文を使う場合、React.Componentクラスを継承してコンポーネントを定義します。

class App extends React.Component {
  render() {
    return (
      <div className="app-component">
        {this.props.hello}
      </div>
    );
  }
}

純粋な関数

純粋な関数としてコンポーネントを定義する場合、JSX記法で書いたDOMを返すような関数を記述します。なかなか直感的に書くことができます、親コンポーネントから渡されるpropsも引数として宣言します。

const App = ({ text }) => (
  <div className="app-component">
    {text}
  </div>
);

違いは?

 ざっくり言うと状態(state)を持つか持たないかのようです。クラス構文であればstateを持ったコンポーネントを作ることができますが、純粋な関数のコンポーネントではできません。純粋な関数のコンポーネントは引数(props)のみ使用し、実際それはコード上で関数の引数として表現されます。(2つの例でのtextの扱いの違いのような感じです)

 また純粋な関数のコンポーネントではrefが使えないそうです。その理由は説明によるとbacking instanceを持っていないから、だそうです。 静的コンポーネント、というイメージでしょうか。申し訳ないですが詳しくはこちらを参照くださいStateless functions

例:コンストラクタ内でstateを定義する。

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      ...
    };
  }
  ...
  render() {
    ...
  }
}

感想

 reduxチュートリアルで初めてStateless functionで書かれたReactコンポーネントを見ましたが、状態管理をreduxに任せる立場からするとそれは当然なんだろうな、と今になって思いました。

 ReactのStatelessなコンポーネントを作ることは、新しいhtmlのタグを定義することに近い気がしました。

オブジェクトリテラル内でのスプレッド演算子

 こんにちは。

 スプレッド演算子(spread operator)がオブジェクトリテラル内で使えませんでした。babel-preset-es2015入れとけばなんとかなるだろうと思っていたのですが、babel-preset-stage-2が必要みたいです。ECMAScriptの仕様策定方法を知らないのでstage-2の意味はわからないですが、なんかよさそうなものがあったので読もうと思います。

 ざっくりとした設定方法をメモしておきます。

インストール

npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-stage-2 webpack

webpack.config.jsはこんな感じになりました。

module.exports = {
  entry: './index.js',
  output: {
    path: __dirname,
    filename: 'bundle.js',
  },
  module: {
    loaders: [
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        loader:  'babel',
        query: {
          presets: [
            'es2015',
            'stage-2',
          ],
        },
      },
    ],
  },
}

以下動くかどうか確認のためindex.jsindex.htmlを用意。

index.js

let obj1 = { 'before': '...oh' };
alert(JSON.stringify(obj1,null,2));
let obj2 = { ...obj1 , 'after': 'oh!' };
alert(JSON.stringify(obj2,null,2));
// ひどいサンプル

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Spread</title>
</head>
<body>
  <div id="root"></div>
  <script type="text/javascript" src="./bundle.js"></script>
</body>
</html>

webpackでビルド実行

./node_modules/.bin/webpack --colors --progress

 index.htmlをブラウザで開くとアラートが2回表示されます。オブジェクトリテラルが再代入なしで更新されているのが、なんとなくわかるかと思います。もっとプロパティが多いと、魅力が伝わる気がします。

感想

 ちゃんとした例を書かないとダメですね。あとECMAScriptに関しても知っておきたいです。

Ctags と vim と Git

概要

vimでタグジャンプを楽にする方法です。以下適当な翻訳です。

参考: Effortless Ctags with Git

Ctags はコードのインデックスを作成し, 関数, 変数, クラス, その他の識別子への Vim でのジャンプを容易にします. Gitのフックはリポジトリ単位です(Git hooks). あるリポジトリに対して, 指定されたフックをインストールするスクリプトを使用するのが推奨されていますが, 手作業で面倒です. テンプレを作成して楽しましょう.

テンプレートのフック作成

git全体で使用するテンプレートディレクトリを設定, 作成します.

git config --global init.templatedir '~/.git_template'
mkdir -p ~/.git_template/hooks

ctagsファイルを作成します.

vim .git_template/hooks/ctags
#!/bin/sh
set -e
PATH="/usr/local/bin:$PATH"
dir="`git rev-parse --git-dir`"
trap 'rm -f "$dir/$$.tags"' EXIT
git ls-files | \
  ctags --tag-relative -L - -f"$dir/$$.tags" --languages=-javascript,sql
mv "$dir/$$.tags" "$dir/tags"

上の内容は,

  • set -e: スクリプト内の各コマンドの実行でエラーが出たら処理を中止する
  • PATH..: 環境変数/usr/local/binを追加
  • dir...: gitリポジトリのパスをdir変数に格納
  • trap..: EXITシグナルを検知したら, 古いタグ .git/tags を削除する?
  • git...: git管理下のファイルに対しctagsを生成, jsとsqlは除く
  • mv ...: 生成されたタグをtagsディレクトリに移動

という感じです.

各フックファイルを作成します。pre-commitは以下です。

#!/bin/sh
.git/hooks/ctags >/dev/null 2>&1 &

post-checkout, post-mergeも内容は一緒です. 最後に rebase, commit --amend の対応として post-rewrite を作成します.

#!/bin/sh
case "$1" in
  rebase) exec .git/hooks/post-merge ;;
esac

これでいけるはずです。

結局5つのファイルを作成しました。

/Users/ushumpei/.git_template
|--hooks
|  |--ctags
|  |--post-checkout
|  |--post-commit
|  |--post-merge
|  |--post-rewrite

感想

Ctagsについて昔書いた文章が見つかったので載せてみました。

trapgit rev-parseがわかっていない...

Promiseのthenが素敵

 javascriptではes2015からPromiseが使えるようになります。Promiseで非同期処理を包むことで、非同期処理が終わったタイミングで次の処理を開始できて、コールバック地獄をなくす、等と言われていたりします。

 詳しい使い方は検索するとたくさん出てくるので割愛させていただいて、ちょっと気になったことを書いていきます。

 以下、サーバへ非同期で通信し、jsonデータをもらって、表示するコードです。主な動きとしては;

  1. 2000ms待つ。
  2. サーバへ非同期なGETリクエストを送信。
  3. レスポンスのJSON文字列をオブジェクトに変換。
  4. オブジェクトの情報を加工して画面に描画。

です。

app/index.js

const wait = ms => {
  return new Promise((resolve, reject) => {
    console.log(`wait ${ms}ms...`);
    setInterval(_ => {
      resolve('OK!');
    }, ms)
  });
}

const fetchData = url => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);

    xhr.onload = () => {
      if (xhr.readyState === 4 && xhr.status === 200) {
        resolve(xhr.response);
        return;
      }

      reject(new Error(xhr.statusText));
    };

    xhr.onerror = () => {
      reject(new Error(xhr.statusText));
    };

    xhr.send(null);
  });
};

const renderHoges = data => {
  const root = document.getElementById('root');
  root.innerHTML = '';

  data.forEach(item => {
    let node = document.createElement('div');
    node.innerText = `${item.id}: ${item.hoge}`;
    root.appendChild(node);
  });

  return data.length;
};

wait(2000)
.then(_ => fetchData('/api/hoges'))
.then(JSON.parse)
.then(renderHoges)
.then(length => console.log(`${length} hoges rendered!`))
.catch(error => console.error(error));

app/static/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Promise</title>
</head>
<body>
  <div id="root">loading...</div>
  <script type="text/javascript" src="bundle.js"></script>
</body>
</html>

サーバ側はここから見れます。

 嬉しいなと思ったのは、thenを使うことで、もともとPromiseを考慮していないJSON.parserenderHogesなどの同期処理をチェインできることです。

 つまり明確に非同期な処理だけPromiseで書けばよく、チェインさせたいがために同期処理のPromise版を書く必要がないということです。

感想

 何当たり前のこと言ってんの?と思われるかもしれないですね。

 困ったのは、Promiseから値を取り出す方法はあるのだろうか?、ということです。それを必要としている時点で処理の設計を間違えているのだろうか?とりあえず、外側で宣言した変数に再代入させる方法は上手くいきませんでした。reduxでサーバとやりとりする際にaction creatorの関数の中にPromiseを使った非同期通信を書いた時に、レスポンスの中身が取り出せない。。。

haskellのApplicative laws - composition

 こんにちは。

 すごいHaskellたのしく学ぼう!の11章に出てくるアプリカティブ則の一つ、「composition則」について何を言っているかを考えてみました。

composition則

 Applicative型クラスを継承するデータ型は、関数を実装すること以外にも、実装した関数が満たさなければいけない規則が存在します。これを確認するのは設計者に委ねられています。Applicative型クラスでは4つの法則が存在します。ここではその4つのうちの一つ「composition則」(正式名称不明)について考えてみたいと思います。ちなみに4つの法則はBasic librariesのリファレンスControl.Applicativeのページに載っています。

 「composition則」は次で定義されています;

pure (.) <*> u <*> v <*> w = u <*> (v <*> w)

 なぜこの法則を満たさなければいけないのでしょうか。ここでは関数合成に対して、「アプリカティブファンクターに包まれた合成演算子(.)を使って関数合成を行うことと、<*>を使って関数合成を行うことは同じか?」ということが成り立って欲しいようです。単なる想像ですが、既存に存在している合成演算子(.)と新しい合成演算子<*>の間にある種の整合性を保っておきたいように見えます。composition則はそのことを確かめましょう、といっています。

 composition則の定義を見ると、左辺がごちゃごちゃしています。適応の順を整理していきましょう。

pure (.) <*> u <*> v <*> w
-- = ( pure (.) ) <*> u <*> v <*> w
-- = ( ( pure (.) ) <*> u ) <*> v <*> w
-- = ( ( ( pure (.) ) <*> u ) <*> v ) <*> w :

実際に適用して行ってみましょう。

-- u, v は以下;
-- u :: Num a => Maybe (a -> a)
-- v :: Num a => Maybe (a -> a)

ghci>:t pure (.)
pure (.) :: Applicative f => f ((b -> c) -> (a -> b) -> a -> c)
-- まず合成演算子(.)がpureによってアプリカティブファンクターに包まれます。

ghci>:t pure (.) <*> u
pure (.) <*> u :: Num c => Maybe ((a -> c) -> a -> c)
-- 次に<*>で、アプリカティブファンクターに包まれた合成演算子の、
-- 第一引数の関数としてuを渡します。

ghci>:t pure (.) <*> u <*> v
pure (.) <*> u <*> v :: Num c => Maybe (c -> c)
-- 再び<*>で、アプリカティブファンクターに包まれた合成演算子の、
-- 第二引数の関数としてvを渡します。

ghci>:t pure (.) <*> u <*> v <*> Just 1
pure (.) <*> u <*> v <*> Just 1 :: Num b => Maybe b
-- 最後に完成したアプリカティブファンクターに包まれた関数に対し、
-- <*>でアプリカティブ値を渡します。

左辺はこれで終わりです。右辺は<*>で関数適用をチェインしているだけなので割愛です。

なぜ必要なのか?

 これに関してははっきりとしたことはわかりませんでした。composition則が何を保証したいかは、結合演算子がどんな関数にも使えることに関係してくるのではないかと思うのですが自信はないです。調べたいと思います。

Vimで折畳

こんにちは。

Vimについて書きます。

概要

Vimでは文章を折りたたむことができるようです(Vi には折畳は無い)。htmlを編集している時など、ファイルが長くてちょっと読みづらいといった時にとても便利です。

Vimユーザマニュアルはとても素晴らしいので、ここにあれこれ解説を書く意味もないかと思います。なのでここでは、コマンドとその動きとかを軽く書こうと思います。Vimを起動した状態で:help fold:help usr_28.txtを実行すると関連するドキュメントを読むことが出来ます。またwebの翻訳も利用可能です。

参考 参考

コマンド

折りたたみ関連のコマンドの多くはzで始まります。基本的なものは以下です。

コマンド 内容
zf 折り畳む (Fold)
zo 折り畳みを開く (Open)
zc 折り畳みを閉じる (Close)
za 折り畳みの開く閉じるを切り替える (Alter?)
zd 折り畳みを削除する(Delete)

zfはオペレータなので、)2jなどのモーションか、itipなどのテキストオブジェクトを後ろにくっつけて使用します。

f:id:ushumpei:20160730161923g:plain

他にもファイル全体の操作もあります。

コマンド 内容
zr すべての折り畳みを開く(減らす) (Reduce)
zm すべての折り畳みを閉じる(増やす) (More)

zr,zmはすべての折り畳みに対する操作ですが、あくまで現在表示されているものが対象になります。つまり入れ子になっている折り畳みを開いたり閉じたりは出来ません。この時に使用するのがzR,zMです。これらは再帰的に折り畳みに作用します。同様に、o,c,a,dも大文字にすることで再帰的に操作を行います。例えばzR再帰的にすべての折り畳みを開くので、結果すべての折り畳みを開きます。zMと交互に使うのが楽かもしれないです。

あと、折り畳み間を移動するのに便利なコマンドです。

コマンド 内容
zj 画面下方向の次の折り畳みへ移動。
zk 画面上方向の次の折り畳みへ移動。
[z 現在開いている折り畳みの先頭へ移動、すでに先頭の場合はその外側の先頭へ移動、すでに一番外側の折り畳みなら何も起きない。
]z 現在開いている折り畳みの末尾へ移動、動作は[zと同様。

疑問

折りたたまれている情報はどこにある?

マニュアルによると、ファイルを破棄すると折り畳みの情報は失われてしまうそうです。折り畳みの情報を保存するコマンドはmkview、呼び出すコマンドはloadviewです。

保存されたファイルはset viewdirコマンドで表示されるディレクトリ内で確認できます。ファイルを開いてみるとローカル変数の定義の後に以下のような記述が見つかりました。数字を変えたら折り畳み位置が変わりましたのでこれが保存された設定値だと思います。

...
3,6fold
8,21fold
...

感想

保存について書きましたが、折り畳みを保存して呼び出して使うのは現実的ではないと思います。めんどくさいです。htmlやコードは構造がはっきりしているため、自動的に折り畳みを定義できるはずで、実際そういう設定がVimにはあるようです。

set foldmethodで現在の設定が確認できます。現在はmanualになっているので、これは手動で折り畳みを定義するデフォルトの設定なようです。

この他指定可能な値は以下になるようです。

内容
manual 折り畳みは手動で設定する。
indent 等しいインデントの行で折り畳みを作る。
expr オプションfoldexprで深さを設定する。
marker マーカーで折り畳みを指定する。
syntax 構文強調表示のキーワードを使って指定する。
diff 変更されていないテキストを折り畳む。

とりあえずset foldmethod=indentvimrcに設定して様子を見ます。ただしこれを設定すると開くファイルがことごとくデフォルトで折り畳まれてしまっています。これは結構面倒臭いのでset foldlevel=100とか書いておくと初期表示時に折り畳まれなくなります(なんか乱暴ですね...)。

以上です!

javascriptでpartition 関数を実装

こんにちは

javascripthaskellでいうところのpartition関数を実装してみたのでメモします。

partition関数

全然一般的ではないので説明です。僕がpartition関数って言っているのは、「配列を、その要素に対して真偽を判定する関数により、真なものと偽なものの2つのグループに分ける関数」です。

たとえば以下のように動くものです。

[0,1,2,3,4,5,6,7,8,9].partition(num => num > 4)
// => [[5,6,7,8,9],[0,1,2,3,4]]

[1,"2",3].partition(item => typeof item === 'string')
// => [["2"],[1,3]]

いかにもありそうな関数ですが、探した限りではありませんでした。(altjsでは実装されている場合があるようです)

やっちゃだめみたいですがArrayのprototypeを汚染して関数を定義します。Arrayの他のコールバックを引数に取る関数を参考にしました。

Array.prototype.partition = function(callback, thisArg) {

  if (this === null) {
    throw new TypeError('Array.prototype.partition called on null or undefined');
  }

  if (typeof callback !== 'function') {
    throw new TypeError('callback must be a function');
  }

  var T;
  if (thisArg) {
    T = thisArg;
  }

  var list = Object(this);
  var length = list.length >>> 0;
  var value;

  var accepted = new Array();
  var rejected = new Array();

  for(var i = 0; i < length; i++) {
    value = list[i];
    if (callback.call(T, value, i, list)) {
      accepted.push(value);
    } else {
      rejected.push(value);
    }
  }

  return [accepted, rejected];
};

[0,1,2,3,4,5,6,7,8,9].partition(num => num > 4)
// => [[5,6,7,8,9],[0,1,2,3,4]]

[1,"2",3].partition(item => typeof item === 'string')
// => [["2"],[1,3]]

第二引数のthisArgArray.prototype.mapの仕様にあったため追記しました。使い道があまりわかりませんが例えば繰り返しの中で副作用を起こさせることができます(ちょっと意味のない例。。。)。

追記(2017/11/29): よく考えるとthisArgは関数呼ぶ時に呼び元のthisを渡したりする時に使うためでした。

[1,2,3,4,5].partition(function(num) {
  this.log(num) ;
  return num > 2;
}, {
  log(num) { console.log(`${num}を振り分けました`) } 
});

最後にreduceを使って、配列とコールバックを引数に取る関数として書いてみます。

const partition = (list, callback) => {
  if (list === null) throw new TypeError('partition called on null or undefined');
  if (typeof callback !== 'function') throw new TypeError('callback must be a function');

  return list.reduce((state, item) => {
    if(callback(item)) {
      state[0].push(item);
    } else {
      state[1].push(item);
    }
    return state;
  }, [[],[]]);
};

partition([0,1,2,3,4,5,6,7,8,9], num => num > 4)
// => [[5,6,7,8,9],[0,1,2,3,4]]

partition([1,"2",3], item => typeof item === 'string')
// => [["2"],[1,3]]

感想

テストとか書いてしっかり確かめたいですね。小さいものは書いていて楽しいです。

browser-syncでブラウザ自動更新

Node.jsでコードを書いていると、ターミナルとブラウザの行き来が頻発して面倒です。自分のノートPCはモニタが小さいので、ウィンドウを切り替えていると埋もれてしまい、それを探すために集中力が切れてしまったりします。

browser-syncを使ってコードを書き換えるとブラウザが自動更新されるようにしてみました。expressと連携することもできるので、そのこともメモしておきます。

単純な使い方

npm install --save-dev browser-sync

監視対象のファイルを指定して起動します。

./node_modules/.bin/browser-sync start --server --files index.html 

コマンドを実行するとlocalhost:3000でサーバが起動します。ブラウザが立ち上がり、Connected to Browser Syncという文字がページ右上に表示されます。この状態で、index.htmlを編集するとブラウザが自動更新されます。


try browser sync

expressと連携

expressでサーバを立てている場合、connect-browser-syncミドルウェアを利用してサーバに自動更新の機能を組み込むことができます。まずはインストールします。

npm install express --save
npm install browser-sync --save-dev
npm install connect-browser-sync --save-dev

app.jsを作成します。

var express = require('express');
var app = express();

if ( app.get('env') === 'development' ) {
  var browserSync = require('browser-sync');
  var connectBrowserSync = require('connect-browser-sync');

  var browserSyncConfigurations = { "files": "static/*" };
  app.use(connectBrowserSync(browserSync(browserSyncConfigurations)));
}

app.use(express.static(__dirname + '/static'));

app.get('/', function (req, res) {
  res.sendFile(__dirname + '/static/index.html');
});

app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

app.listen(3000);

ifの部分がbrowserSyncの設定です。オプションをオブジェクトでbrowserSync関数に渡し、connectBrowserSyncミドルウェアをexpressに登録しています。オプションのfilesは監視対象のファイルを設定します。ワイルドカードで指定したり、配列として複数のパスを渡すこともできます。ここではstatic以下のファイルをすべて監視しています。ここをコンパイル後のjsの吐き出し先に設定したりすれば、webpackと協調して使うこともできますね。 ミドルウェアの登録のタイミング次第では動かなかったりして少しはまりました。静的コンテンツのディレクトリ設定をbrowserSyncの前に設定したら監視が始まりませんでした。

感想

browserSyncで検索すると大体はgulpと一緒に使おうみたいな記事が見つかります。設定ファイルの記述方法を覚えるのが大変なので、gulpはやらなくていいかなと思い、自動更新に絞った使い方を調べました。

上記二つのケースどちらでも、browserSyncを起動すると管理サーバも立ち上がるのですが、その使い方があまりわかっていません。また「snipetを貼り付けてね」みたいなことをbrowserSyncから注意されるのですが、これが何に使われているかわからないです。自動更新できているから、ひとまず放置しています。

追記: ブラウザ同士のイベントを同期することもできるみたいです。同一ネットワーク内だと同期されるのかな?

圏, 関手, 自然変換

圏論の定義メモ

Def.圏(category)

 C は次のデータからなります;

  • 対象(objects)
    •  A,  B,  C...
  • 射(arrows)
    •  f,  g,  h...
  • ドメイン(domain), コドメイン(codomain)
    • 任意の射  f に対し, 対象  dom(f),  cod(f) が定まる. この時  f : A \rightarrow B と表す. (ただし  A = dom(f) B = cod(f)
  • 合成(composite)
    • 任意の射  f : A \rightarrow B g: B \rightarrow Cに対し、 h = f \circ g: A \rightarrow C という射が定まる.
  • 恒等射(identity arrow)
    • 任意の対象  A に対し,  1_A という射が定まる.

これらのデータは次の法則を満たします;

  • 結合律(associativity):
    • 任意の射  f,  g,  h cod(f) = dom(g),  cod(g) = dom(h)を満たすものに対し,  h \circ (g \circ f) = (h \circ g ) \circ f が成り立つ.
  • 恒等律(unit):
    • 任意の射  f: A \rightarrow B に対し,  f \circ 1_A = f = 1_B \circ f

Def.関手(functor)

 C,  D に対し, 関手  F : C \rightarrow D とは, 圏  C の対象を圏  D の対象に, 圏  C の射を圏  D の射に写すもので, 次を満たします;

  •  F(f : A \rightarrow B) = F(f) : F(A) \rightarrow F(B)
  •  F(1_A) = 1_F(A)
  •  F(g \circ f) = F(g) \circ F(f)

Def.自然変換(natural transformation)

 C,  D , 関手  F, G : C \rightarrow D に対し, 自然変換  \eta : F \rightarrow G とは, 圏  C の任意の対象  A に対し, 圏  D の射  \eta_A: F(A) \rightarrow G(A) を与えるもので, 次を満たします;

  •  C の任意の射  f: C \rightarrow \acute{C} に対し,  \eta_{\acute{C}} \circ F(f) = G(f) \circ \eta_C が成り立つ.

感想

だからどうしたという感じですよね。写像だ、とか集合だ、とか限定すると間違ってしまうためぼんやりした定義になってしまいます。はてなの所為なのか可換図式書けませんでした...

PCのカメラを起動する

WebRTCで遊んでみたいので、手始めにPCの内臓カメラを起動するだけのページを作成します。

navigatorオブジェクトのgetUserMediaメソッドを使用することでデバイスにアクセスできるみたいです。ただ、ブラウザ環境ごとにこのメソッドがあったりなかったりするため、代替となる関数を列挙してそのうちあったものを使用する、というコードを書きます。

navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia || navigator.msGetUserMedia;

メソッド自体はこんな形をしています。

navigator.getUserMedia(constraints,successCallback,errorCallback)

次にこのAPIを使用してメディアに接続します。

    navigator.getUserMedia({video: true, audio: true},
    stream => {
      video.src = window.URL.createObjectURL(stream);
    },
    error => {
      console.error(error);
      return;
    });

あとは適当にhtmlをかいた、ら動くと思ったのですが、エラーも吐かずになぜか動かない。

そういえばwebサーバに置かないとダメ、みたいなことを聞いた気がしたので、nodeでexpressを使ってサーバを立てました。

mkdir webrtc
cd webrtc
npm init -y
npm install express

二つのファイルを作成します。

server.js

var express = require('express');
var app = express();

app.get('/', function (req, res) {
  res.sendFile(__dirname + '/index.html');
});

app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

app.listen(3000);

index.html

<html>
  <head>
    <title>sample</title>
  </head>
  <body>
  <video id='video' autoplay></video>
  <script type="text/javascript">
    let video = document.querySelector('video');
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

    navigator.getUserMedia({video: true, audio: true},
    stream => {
      video.src = window.URL.createObjectURL(stream);
    },
    error => {
      console.error(error);
      return;
    });
  </script>
  </body>
</html>

あとは起動してlocalhostの3000ポートにアクセスします。

node server.js

f:id:ushumpei:20160717043812p:plain

なんか暗いですが映りました!

感想

getUserMediaがPromise化対応したそうですが、chromeはダメみたいです。

近々reduxコンポーネントとして、動画の録画、再生、停止、保存機能を持った部品を作成したいです。

あと通信部分で、同一LAN内の携帯とPCとかならビデオチャットできないだろうか?と考えています。