uuuu & Gauche本読書会 & SICP読書会 & 異業種交流会

投稿者 nanki 2008-04-28 12:25:00 GMT

uuuu

大阪でミーティングがあった後、物件を見に行って、その後帝人ビルの食堂みたいなところでしばらくお茶(というかミックスジュース)して、北浜まで歩いて帰るつもりだったけど、もう午後も遅い時間、uuuu始まるの17時じゃなかったか、と思って、突如参加することに決めた。

uuuuとは、(当面)みんなでMITの講義ビデオを見る会。

会場に着いてから無線につながったので開始時間を見ると19時から…orz 19時まで、JavaScriptのコードをいじったりしながら、早めに来たujihisaと合流.

今回は第一回目なので、コースの説明や試験の説明などもある。 スピーカのセッティングなどをしつつ、そこは聞き流す。

なぜアルゴリズムを学ぶのか?パフォーマンス? パフォーマンスより大事なことはたくさんある。正確さ、単純さ、メンテナンス性、ユーザビリティ…etc パフォーマンスはお金のようなもので、これらを手に入れるにはパフォーマンスをお金のように支払う。 それに、速いと楽しいじゃん!

みたいな話の後、insertion sortとmerge sortについて詳細に分析する。

Gauche本読書会

40分ほど遅刻。会場の入口でもう一人の遅刻者と合流。

昨日突然参加を決めたのでGauche本は持ってきていない。 隣の人に見せてもらうメソッドでyukkyに見せてもらう。 LISP, Schemeの歴史やemacsの話を読む。 よし、今日はemacsしか使わないぞ、と思い立ったのも束の間、.emacsの編集にvimを使ってるのに気がついてあきらめた。

リテラルをいくつか、goshで試してお昼。

SICP読書会

問題 1.5くらいまで。

問題1.3, a b c のうち大きい方二つの二乗和を計算する。

(define (sum-of-squares a b) (+ (* a a) (* b b)))
(define (f1.3 a b c)
  (cond ((and (< a b) (< a c)) (sum-of-squares b c))
        ((and (< b a) (< b c)) (sum-of-squares a c))
        ((and (< c a) (< c b)) (sum-of-squares a b))))

こんな長いのは勘弁してほしいので余った時間でminigolf.

(define (f1.3 a b c)
  (if (> a b)
      (if (> b c)
          (+ (* a a) (* b b))
          (f1.3 a c b))
      (f1.3 b a c)))

おお、再帰脳。 condもなくなったので、BiwaSchemeでも動く。

yharaさんのLT、いいLISPのコードは、見た目がこう!という話を聞いて書き直す。(indentが単調増加してるのがいいらしい)

(define (f1.3 a b c)
  (if (< a b)
      (f1.3 b a c)
      (if (< b c)
          (f1.3 a c b)
          (+ (* a a) (* b b)))))

実は最後のコード以外には致命的なバグがある。

こんな短いコードにバグを仕込めるなんて… このコードにバグはないか?という視点で見ると、すぐに気がつくんだけど。

こんなコードでテストする。このテストにパスしなかったら、問題解けてないのと同じなのだから、テスト重要。

(map (lambda (x) (apply f1.3 x)) 
          '((1 2 14) (1 14 2) (2 1 14) (2 14 1) (14 1 2) (14 2 1) (14 2 2)
            (2 14 2) (2 2 14) (10 10 2) (10 2 10) (2 10 10) (10 10 10)))

これで、大小関係においては網羅しているかなぁ。 テストデータが実装を反映した(比較演算子を使っているという)ものになっているのが、気にかかるがどうすべきなんだろう。

ujihisaのLTで、少し群の話がでてくる。 (演算子)とやると、単位元がでてくるはず。

gosh> (+)
0
gosh> (*)
1
gosh> (and)
#t
gosh> (or)
#f
gosh> (list)
()

naoya_tさんのLT、Scheme on PIC発表資料かっこよすぎる。

異業種交流会

SICP読書会でその存在を知り、突然行く事にしたのだが、知らない人たくさん、知ってる人もたくさんで楽しかった。 知らないはずなのに、どこかでつながっていたり、実はとてもご近所さんだったり、どっかですれ違っていたり。 ごはんもおいしかったし。

帰りにお茶してタクシーで帰った。ぐったり。

百万ベンチャーw

1000000.times {puts "チャー"}

ライフゲーム辞書 - Life Lexicon.dictionary

投稿者 nanki 2008-04-12 08:40:00 GMT

superstring

前回作成したライフゲーム辞書をちょこっと調整して、Quartz Compositionもより普通の書き方をするように直してみたので公開。 (前のは、一フレーム前を取るためにJavaScriptのMathオブジェクトにプロパティを作って…)

  1. LifeLexicon.zip を展開してできたLife Lexicon.dictionary を ~/Library/Dictionaries 以下に移動。
  2. Dictionary.appを再起動。

主な使い方は、宇宙(動いてる黒いの)を左クリックでリセット、AppleRemoteで停止、ステップ再生など。

download: LifeLexicon.zip(OS X 10.5用)

参考:

netswitch! | OSXのユーザ辞書の作り方

ライフゲイムの宇宙


プログラミング言語 purl を ruby で実装

投稿者 nanki 2008-02-21 01:40:00 GMT

言語処理系を実装するのは、初めて(だと思う)

PostScript などと同じ逆ポーランド風の言語で、画像処理用途。

以下purlのソース。

2:load:dup:2:shadow:swap:composite:geom:1:load:100:100:4:pull:4:pull:crop:swap:composite:to.png

:がリテラルの区切り、その他は数字と演算子。 上のコードでは、2番の画像をロードして、ドロップシャドウしたものに1番の画像を背景として合成している。

ソースコード中に改行は入れられない。何故ならコードはURLの一部だから…

sample


Re: netswitch! | vim/rubyのための部分最適化

投稿者 nanki 2008-01-26 21:39:00 GMT

Re: netswitch! | vim/rubyのための部分最適化

今はこんな風になっている。

function SymbolUnderCursor(tran)
  return synIDattr(synID(line("."),col("."), a:tran),"name")
endfunction

function SmartSemicolon()
  execute "normal a "
  try
    throw SymbolUnderCursor(1)
  catch /rubyString\|rubyRegexp/
    execute "normal r;"
  catch /.*/
    execute "normal xo \<BS>"
  endtry
endfunction

function SmartEnd()
  execute "normal a1"
  try
    throw SymbolUnderCursor(0)
  catch /rubyString\|rubyRegexp\|rubyLocalVariableOrMethod/
    execute "normal xae\<Esc>and"
  catch
    execute "normal xae\<Esc>and\<Enter> \<BS>"
  endtry
endfunction
  
    
function SmartDefine()
  try
    throw SymbolUnderCursor(1)
  catch /rubyDefine/
    execute "normal oe\<Esc>and\<Esc>k"
  catch
  endtry 
endfunction
    
au FileType ruby imap <buffer> begin begin<Enter>
au FileType ruby imap <buffer> end  <Esc>:call SmartEnd()<CR>a
au FileType ruby imap <buffer> then then<Enter>
au FileType ruby imap <buffer> ensure ensure<Enter>
au FileType ruby imap <buffer> else else<Enter>
au FileType ruby imap <buffer> elsif elsif<Enter>
au FileType ruby imap <buffer> ; <Esc>:call SmartSemicolon()<CR>a
au FileType ruby imap <buffer> def def<Esc>:call SmartDefine()<CR>a
追記:
  • rubyRepeat追加

MacOSXのキーバインディングを変える

投稿者 nanki 2008-01-26 17:43:00 GMT

毎回、方法を忘れるのでメモ。

Mac OS X Key Bindings

☂ cat ~/Library/KeyBindings/DefaultKeyBinding.dict
{
  "^j" = "moveDown:";
  "^k" = "moveUp:";
}

Kanasan.JS#2

投稿者 nanki 2007-12-11 00:34:00 GMT

朝九時から夜九時まで、prototype.jsを読む、という勉強会が開催されていたので、夕方の数時間だけオンラインで参加。 読んでいるソースのバージョンが違っていて、行数指定の話の内容を勘違いしていた。

僕が読んだ範囲は、prototype.js v1.6.0 l.587 - l.1193, Enumerable - Ajax.Baseまで。

Kanasan.JS本体の人はもっと広いはず。

Enumerable

l.587

Rubyと似たのが多いので、ほとんど違和感無く読める。 each の実装は_eachのみに依存して、他のメソッドはeachのみに依存している。

l.775

zipの実装がなかなか素敵。

こんな使い方。

>> [1, 2, 3].zip([1, 2, 3], [1, 2, 3]);
[[1, 1, 1], [2, 2, 2], [3, 3, 3]]

>> var sum = function(r,m) { return r + m };
>> [1, 2, 3].zip([1, 2, 3], [1, 2, 3], function(e){return e.inject(0, sum)})
[3, 6, 9]
// l.775
  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (Object.isFunction(args.last()))
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      return iterator(collections.pluck(index));
    });
  }

[this].concat(args).map($A)と、collections.pluck(index) がキモ。

Array

l.813

WebKitでは、NodeList#toArrayにネイティブ実装を使う。 できれば、パフォーマンスのためのコードは、一目でわかるようにしておいて欲しいorおきたい。 パフォーマンスのためなのか、互換性のためなのかがわからない。(できればかかわりたくない)

l.837

this.length = 0;で配列の初期化ができるとは、知らなかった。 ちなみに、元の長さより長くすると、undefinedでpadされる。

l.877

array.uniq遅そう。 arrayがソート済みの場合、array.uniq(true)でずいぶん速くなりそう。 次のintersectもdetectで遅そうだけど、arrayはEnumerableであれば何でもよさそうなので、これでいいか。 気をつけて使う。 Enumerableって、極端な話、each実行するたびに結果が違ってもいいんだもんな。

Number

l.956

toColorPart:0-255の値を2桁16進にするためのコード。

l.979

Math.ceil とかをNumber#ceilにしてる。methodize().

Hash

l.987

いきなりのif文がわけわからないが、なにかの挙動の違いで、パフォーマンスをあげようとしているのだろうか。 v1.6.0.1ではなくなっているので、あまり気にしないで読み進む。

eachの中で、pair.key, pair[0] の両方のアクセス方法を許すようなコードが書かれている。

l.1016

この書き方(無名関数の中で関数を作って、return でオブジェクトを返す)は、JavaScriptでプライベート関数を実現するイディオムのようなもので、 この場合は、toQueryPairは、外からはアクセスできない。

1.5時代のソースも見てみると、Hashががらりと変わっていることに気がつく。 1.5では、値をthis自身に、1.6では、値をthis._objectに格納している。

// 1.5
$H({a: 3})["a"] // => 3

// 1.6
$H({a: 3})["a"] // => undefined
$H({a: 3}).get("a") // => 3
$H({a: 3}).toObject()["a"] // => 3

うーん、これは結構使ってしまっている気がするんだなぁ。

l.1072

toQueryString 突然のcurryに緊張が走る。 配列をQueryStringに変換する際、toQueryPairの第一引数を固定しているだけのようだ。

>> $H({a: [1,2,3]}).toQueryString()
"a=1&a=2&a=3"

そういえば、最近、近所の交差点がcurry激戦区になりつつある。

l.1100

toTemplateReplacements. どうも、1.5, 1.6間でHashの実装が大きく変わったのは、これが原因のような気もする、この関数。 evaluateの中で勝手に呼ばれるので、気にする事はない。

//v1.5
>> (new Template("hello #{name} #{each}.")).evaluate($H({name: 'ujihisa'}))
"hello ujihisa function (iterator) {
....
...
}."

//v1.6
>> (new Template("hello #{name} #{each}.")).evaluate($H({name: 'ujihisa'}))
"hello ujihisa ."

ObjectRange

RubyのRangeみたいなの。 Range だと、DOMのRangeとかぶるので、ObjectRangeらしい。 exclusiveは、最後の要素を含まないか含むか。

start, endは、比較可能で、succが実装されていれば何でもよい。 比較も、オーバライド可能なメソッドにしてほしいな。

Arrayは比較可能なので、Array.prototype.succを実装すると、ObjectRangeで使えるようになると思う。 あんまり意味なさそうだけど。

$R("a", "c").map()
// => ["a", "b", "c"]

Array.prototype.succ = function() {
  var clone = this.clone();
  clone.push(this.last());
  return clone;
}

$R([1], [1,1,1]).map()
// => [[1], [1, 1], [1, 1, 1]]

Ajax.Responders

Ajaxのリクエストに関するイベントリスナを管理している。

l.1162

なぜapplyの第一引数がresponderなのか、という話題になる。 たぶん、こういう使い方をしたいから、という気がする。

Ajax.Responders.register({onComplete: function () { Ajax.Responders.unregister(this)}});

Ajax.Base

あんまり読みどころがない。 目がAjax.Requestの中の$superを見つけてしまう。なんだこれ。

$super

  initialize: function($super, url, options) {
    $super(options);

という使われ方から、親クラスのメソッドを呼び出す関数だというのはわかるのだが・・・

$superで検索すると、文字列として見つかる。あやしい。

  1. value には関数が入る。
  2. value.argumentNames()で関数宣言のソース文字列を正規表現でパースし、引数のリストを取得して、
  3. 第一引数が$superならば、その関数を置き換えている。
  4. function() { return ancestor[m].apply(this, arguments) }; が$superの中身。
  5. wrapで、method(arg1, arg2) という呼び出しを、method($super, arg1, arg2) に置き換えつつ、
  6. 置き換えた事を悟られないように、toString, valueOfをオーバーライドしている。

嘘を嘘で塗り固めたような・・・。

//l.76
Class.Methods = {
  addMethods: function(source) {
    var ancestor   = this.superclass && this.superclass.prototype;
    var properties = Object.keys(source);

    if (!Object.keys({ toString: true }).length)
      properties.push("toString", "valueOf");

    for (var i = 0, length = properties.length; i < length; i++) {
      var property = properties[i], value = source[property];
      if (ancestor && Object.isFunction(value) &&
          value.argumentNames().first() == "$super") {
        var method = value, value = Object.extend((function(m) {
          return function() { return ancestor[m].apply(this, arguments) };
        })(property).wrap(method), {
          valueOf:  function() { return method },
          toString: function() { return method.toString() }
        });
      }
      this.prototype[property] = value;
    }

    return this;
  }
};

本筋とは関係ないけど、if (!Object.keys({ toString: true }).length) というコードを見ると、一瞬困惑するRuby脳。

おまけ

l.1222

      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
        params += '&_=';

この謎の&_=、Safariのどれかのバージョンで、POSTのリクエストの後ろに変な文字列をつけるバグへの対策らしい。&_=........となるので、切り捨てられる。

参考:

OSXのユーザ辞書の作り方

投稿者 nanki 2007-11-26 04:20:00 GMT

Leopardからは、小学館の辞書データとWikipediaが追加されて、とっても便利なDictionary.app.

たぶん、キーボード使用を前提とした小気味よいインターフェースがいいんだと思う。 慣れてしまうと、もう生のWikipediaを使う気はなかなか起きない。

こうなると、なんでもかんでも、同じインターフェースで済ませたくなるもので、そんな時に、こんな検索しにくいページを見たら、「Dictionary.appで検索できて然るべき」と思ってしまうのが自然な流れ。

というわけで、ライフゲームの辞書、Life LexiconをDictionary.app用に変換します。

まずは、テンプレートをコピーしてくる。 試しにmakeすると、あっさり辞書が作られる。 make installすれば、~/Library/Dictionaries 以下にコピーされ、Dictionary.appから検索できる状態になる。(Dictionary.appの環境設定で有効にしないとだめかも)

$ cp -r /Developer/Examples/Dictionary Development Kit/project_templates dict
$ cd dict
$ make
"""/Developer/Extras/Dictionary Development Kit"/bin"/build_dict.sh"  "My Dictionary" MyDictionary.xml MyDictionary.css MyInfo.plist
- Building My Dictionary.dictionary.
- Cleaning objects directory.
- Preparing dictionary template.
- Preprocessing dictionary sources.
- Extracting index data.
- Preparing dictionary bundle.
- Adding body data.
- Preparing index data.
- Building key_text index.
- Building reference index.
- Fixing dictionary property.
- Copying CSS.
- Copying other resources.
- Finished building ./objects/My Dictionary.dictionary.
echo "Done."
Done.

重要なのは、MyDictionary.xmlファイル。 辞書の元となるデータをパースして、XMLファイルを作ってやる。

<?xml version="1.0" encoding="UTF-8"?>                                                                               
<d:dictionary xmlns="http://www.w3.org/1999/xhtml" xmlns:d="http://www.apple.com/DTDs/DictionaryService-1.0.rng">    
  <d:entry id="blinker puffer" d:title="blinker puffer">                                                             
    <d:index d:value="blinker puffer" d:title="blinker puffer" />                                                    
    <d:index d:value="puffer" d:title="blinker puffer" />                                                            
    <h1><span class="headword">blinker puffer</span></h1>                                                            
    <span class="meaning">Any <a href="x-dictionary:r:puffer">puffer</a> whose output is <a href="x-dictionary:r:blinker">blinker</a>s.  However, the term is particularly used for p8 c/2 puffers.  ....
    </span>
  </d:entry>
  <!-- 以下d:entry の山 -->
</d:dictionary>

意味はほとんど見た目通り。
それぞれの項目は、インデックスと内容のXHTMLで構成される。 他の単語を参照する、href=”x-dictionary:r:ID”とかが特徴的か。 上の例では「blinker puffer」という単語に対して、pufferでも検索に引っかかって欲しいので、indexに加えてある。

パースしてXMLを吐くコードは、汎用性がないのでここには載せないが、今回は以下のようなLifeを表す図を、rcairoでpng画像に変換もしている。

OO..
O..O
..OO

画像などのリソースは、dict/OtherResources 以下の適当な場所に置いておき、普通のHTMLのように、<img>タグを使って埋め込む。

<p><img src="Images/blinker%20puffer_0.png"/></p>

これで、

$ make
$ make install

すれば、ユーザ辞書の完成。

続き

さて、これだけで終わりではもったいないので、先日作ったQuartz Compositionを組み込めないか、頑張ってみる。

HTMLにQuartz Compositionを組み込むのは簡単で、EMBEDタグを使って、

<embed id="composition" type="application/x-quartzcomposer" src="LifeLexicon.qtz" width="300px" height="300px" />

とする。

これで、普通のムービーならスタートするのだけど、ライフゲームなので第0世代の画像を渡してやらないといけない。

Quartz ComposerのWebKitプラグインにはJavaScript APIが用意されていて、publishしたパラメータを設定できるようになっている。 が、文字列しか設定できないようだ。画像はどうやって渡すのだろう。

調べてみると、Compositionの中で、Image Downloaderというパッチを使うらしい。 Image Downloaderは指定されたURLの画像をImageとして出力するパッチ。 なるほど。 ImageDownloaderの入力を、imageLocationという名前でPublishして・・・

var composition = document.getElementById("composition");
composition.setInputValue("imageLocation", "fullpath.png");

こんな感じになるのかな。 しかし、フルパス指定じゃないといけないのが面倒だなぁ・・・

先生、こちらにできあがったものが用意してあります。

結局、location.href と、EMBEDに勝手に付け加えたimage属性から、フルパスを取得する仕組みに。 文中のSpaceはCompositionというか、Universeというか。

//LifeLexicon.js
(function () {
  var waitForSpaces = function (spaces) {
    if (spaces.length == 0) return;

    if (spaces[0].loaded && spaces[0].loaded()) {
      var space = spaces.shift();
      var base = location.href.substr(0, location.href.lastIndexOf("/"))
      space.setInputValue("imageLocation", base + space.getAttribute('image'));
    } 
      
    with ({callee: arguments.callee}) {
      setTimeout(function () {callee(spaces)}, 100);
    }
  };

  var arrayFromNodeList = function (nodelist) {
    var result = [];
    for (var i = 0; i < nodelist.length; i++) {
      result.push(nodelist.item(i));
    }
    return result;
  };

  waitForSpaces(arrayFromNodeList(document.getElementsByTagName('EMBED'))); 
})();

なぜか、<script src="LifeLexicon.js">だと読み込まれないので、

<script>
  var tag = document.createElement("SCRIPT");
  tag.src = "./LifeLexicon.js";
  document.getElementsByTagName("HEAD")[0].appendChild(tag);
</script>

というコードも、<d:entry>内に埋め込むように。

これで「生きた」LifeLexiconが完成。

LifeLexicon

CREDITをどこかに入れないと配布できないらしいので、欲しい人には直接送ります。 いないだろうけど。

誰か、Ruby Reference Manualをこれで。

参考:

Quartz Composer - Conway's Game of Life - 宇宙大決戦

投稿者 nanki 2007-11-18 04:17:00 GMT

9月から半分Macユーザになったので、Macの開発環境も触ってみよう思い、まずは楽しそうなQuartz Composerに手を出してみた。

基本的には、Patchと呼ばれる部品をつなぎ合わせてプログラミングしていくものだが、ProgrammableなPatchも三種類(JavaScript, GLSL, Kernel Language?)用意されていて、後者二つは最近流行?のベクトル指向な言語。

以前から、GPUのシェーダ言語でライフゲームを書いて見たかったので、テーマはライフゲームに決定。 KOF2007の懇親会で、小波先生、池上さんとライフゲームの話題で盛り上がったのも理由。

メインとなるKernel Languageのコードは以下。 画像のある一点の色を決めるコードを書けば、あとは勝手にベクトル化してくれているはず。
(たぶん、ベクトル化のために)普通のif文は使えないので、compare関数を使う。

kernel __color conway(sampler image)
{
    float self = sample(image, samplerCoord(image)).r;

    float lives;
    lives  = sample(image, samplerCoord(image) + vec2( 1, -1)).r;
    lives += sample(image, samplerCoord(image) + vec2( 0, -1)).r;
    lives += sample(image, samplerCoord(image) + vec2(-1, -1)).r;
    lives += sample(image, samplerCoord(image) + vec2( 1,  0)).r;
    lives += sample(image, samplerCoord(image) + vec2(-1,  0)).r;
    lives += sample(image, samplerCoord(image) + vec2( 1,  1)).r;
    lives += sample(image, samplerCoord(image) + vec2( 0,  1)).r;
    lives += sample(image, samplerCoord(image) + vec2(-1,  1)).r;

    const float dead  = 0.0;
    const float alive = 1.0;
    float compare4 = compare(lives - 3.5, alive, dead);

    float r = compare(
        self - 0.5,
        compare(lives - 2.5, dead, compare4),
        compare(lives - 1.5, dead, compare4)
    );
    
    return __color(r, r, r, 1);
}

それから、1step前の状態が必要なので、JavaScriptで以下のようなPatchも作成。 フレームをまたがって値を保持するには、ObjectかMathのプロパティに値を入れておけばよい。

function (__image output) main (__image input, __image initial, __number dummy) {
  var result = {output: Object.prev || initial};
  Object.prev = input;
  return result;
}

後は適当にPatchを組み合わせていけば、完成。
Apple Remoteでコントロールしたり、スクリーンセーバにしたり、1フレームで3step進めたりしなければ、もうちょっと単純になる。
quartz composer screenshot

ライフゲームのルールについてはWikipediaを参照していただくとして、自分の周囲9マスから次の状態を決定する、という非常にローカルで単純なルールから、あまりに複雑で大域的な発展が繰り広げられる様は、宇宙や生命といった壮大なものを連想させる。

max vs glider guns
ライフゲーマー必見!ものすごい勢いで宇宙を浸食するmaxと、MITが開発したグライダーガンの、宇宙を賭けた闘い。

参考:

ライフゲーム - Wikipedia


OSC2007Kansai

投稿者 nanki 2007-07-22 07:30:00 GMT

に行ってきました。

今回は、Railsハンズオンセミナーの講師をやったけど、一日目は、プロジェクタへの接続を確認してなくて、みなさまにご迷惑をおかけしました。ごめんなさい。

ちゃんと設定したのに、二日目もやっぱりつながらなくて、okkezさんに迷惑かけました。ごめんなさい。

二日目の懇親会で見た楽器、ウダーがスゴかった。
販売してたら危うく購入するところだったのに。残念。
今後に期待しています。

あと、お子様連れのお子様たちがとってもかわいかったよぅ。子供ほしー。

帰りは、京都駅から四条まで、数人で散歩。
翻訳の仕事をしている外国の方(といってももうほとんど日本の方)も一緒で、東本願寺に掲げてあった看板のコピーをだしに、面白い話が色々聞けた。

OSCの内容の話が無い!

参考:


おいしいコーヒーの作りかた

投稿者 nanki 2007-07-11 21:34:00 GMT

BRITAのフィルタがそろそろ交換時期だったので実験してみた。

概要

コーヒーをBRITAでろ過する。

手順

  1. もったいないので、ちょっと古そうな、既に挽いてある粉を使って、コーヒーメーカーでコーヒーを淹れる。
  2. 冷ます。(活性炭から余計な物質を呼び覚まさないように)
  3. 試飲。おいしくはないが、想像の範囲内。酸味はほとんど感じられず、香りも感じられない。辛いような苦み。
  4. ろ過。見た目はほとんど変わらない。
  5. 試飲。鼻を近づけると、みたらしの香り、苦味はなく、噛んじゃいけない錠剤を噛んだときのような、嫌な酸味。激マズ。

・・・

BRITAの中身は、フィルタと活性炭と陽イオン交換樹脂だそうで、コーヒーは酸性なので、陽イオン交換樹脂はあまり働いてないと思われる。

とすれば、苦味(とコク)の除去が行われただけであの酸味か。 普段あの酸味はどこへいっているのだろう。 辛いような苦味、の辛い部分はこれかもしれない。

この辺を見ると、ジカフェオイルキナ酸辺りがきな臭い。

そう考えるとおいしいコーヒーはまさに奇跡だなぁ。 非線形な味覚の神秘。