にている

昼前に、NHKでおもしろい番組をしていた。偶然だったので番組の途中から。
京都の上賀茂神社下鴨神社で春におこなわれる葵祭を中心にしたもの。
下鴨神社についてのところから見ることができた。


杜における春の息吹、いのちの芽吹く季節に祭がおこなわれることを
神さん*1の復活を祝うというような意図があるように受け止められる表現がされていた。
四季による年間の季節の巡るサイクルを死と生の循環のように受け止めるありかたを見たような気がします。
復活という言葉を強調しているように感じられました。
その趣旨は、まるでイエス・キリストの死と復活を記念する祭のよう。
クリスチャンのなかにはそのような連想をする人もいるかもしれない。
彼の死と復活もおなじく春の時期。
旧約聖書に規定された過ぎ越しの祭のころ木に架けられ三日目によみがえり
四十日間あらわれた後、弟子たちのまえでオリーブ山で昇天。
そして死から七週後の五旬節の祭のころに聖霊降臨、キリストの教会のはじまり。


神饌をささげることも旧約聖書のレビ族による神殿奉仕のようだ。
さらにキリストの弟子たちとの最後の晩餐、過ぎ越しの食事のよう。
神さんをよろこばす、神さんとともに食事をするというのは、そのとき制定された聖餐式みたいだ。
ともにおられる神というのはイエス・キリストを指す際にも用いられる。*2


ある神事のなかで、神職がことばになるまえの声、うめきのような声を杜のなかでひとり発するというのがありました。
御蔭神社で荒魂(あらみたま)、神の霊をお迎えする神事だったとおもいます。
まるでパウロの語ったことの表現かとおもいました。
神の御霊が人々のために言いようも無い深いうめきによってとりなしの祈りをされる、ということの。*3


モンゴルのホーミーとは響きが異なっていたけど、歌詞になっていない歌というか祈りというのは何か似ているかも。
あの祈りあるいは歌は、なんというのだろう。メモできなかった。
「警」という字が含まれていたけど2文字目を覚えていない。
あのことばにならない歌・祈りにはどのような意味が込められているのだろう。
再放送されないかなあ。


面白い番組をみると、いろいろ検索したくなります。
雰囲気のある凄みを感じさせるナレーションなので声の主を調べたら三輪山のふもと出身とか。
下鴨神社式年遷宮があり21年としているとか。
伊勢神宮の20年に一を足すのには何か意味が込められているのかな。

*1:番組では「神さん」という表現が多かった。京都での標準的な敬意をもつ表現なのかな

*2:新約聖書マタイ 1:23, 旧約聖書イザヤ 7:14

*3:新約聖書ローマ 8:26

ルート?

  • シルクロード 陸路?海路? ⇒ 朝鮮半島? ⇒ 九州? ⇒ 淡路島 ⇒ 四国 ⇒ 九州 ⇒ 四国・瀬戸内海周辺 ⇒ 大和 ⇒ 京 ⇒ 丹後 ⇒ 伊勢

XPathサポート判定コード

!!document.evaluate以外での判定方法。

var hasXPath = (function(){
  try{
    return document.implementation.hasFeature('XPath','3.0')
  }catch(e){
    return !1
  }
})();


あるいは、

var hasXPath = (function(i,f){return f in i&&i[f]('XPath','3.0')})(document.implementation||{},'hasFeature');

ただし、もしもObject.prototype.hasFeatureが操作されていたら誤判定はありうる。


別の方法。WindowsXPのIE6,IE7,IE8β,Firefox3,Opera9.6,Chrome1.0で動作確認しました。
LinuxMacなどの別環境を考慮したとき正規表現を変えるべきか、未確認です。

var hasXPath = !!document.evaluate
  && typeof(document.evaluate)=='function'
  && /^function\s(evaluate)?\(\)\s\{\n?\s*\[native\scode\]\s?\n?\}$/.test(document.evaluate);

もし、下記のように書き換えられたらXPath機能ないのに誤作動してしまう。

document.evaluate = function(){};
document.evaluate.toString = function(){
  return 'function () { [native code] }';
};

たとえ上記に対処しても、今度は、Object.prototype.toString、Function.prototype.toStringといった組み込み関数も書き換えることができるので、それらが変更されているか検知できない限り厳密な判定はできない、ということになりそう。そんな方法がクロスブラウザjavascriptであるのだろうか。ごくまれな例外的な事態にも対応できる厳密な方法がないのなら、簡便なやりかたが現実的で結局、良さそう。!!document.evaluateだけでは、name属性がevaluateなimgタグがあるときにうまく動作しないそうだけれども、XPathを使うライブラリを導入するサイト管理者はその事態を避けることがおそらく可能だから。
もしかして、充分多くの場合、正しく動作する現実的な対策はあるけれども、あらゆる事態に対して厳密に判定できる方法はない、つまりjavascriptのライブラリが厳密に判定できないjavascriptに関する事柄があるといえるのかな。javascriptで記述される世界での真偽判定不能な命題ってあるとしたらどんな性質をもつんだろう。


組み込みオブジェクトにたいして安易に書き換えをしない、という暗黙の前提が利用者や開発者にあって、それが安定した秩序を支えているという構図なのかな。厳密さを論じるのが難しくても実用的な解が存在するケースって多そう。自然にも人の社会にも。そういう構造って理解が困難だったり到達不可能だったり精緻だったり複雑だったりするのかな。

javascriptで、Yコンビネータ

メモ。
式変形など。わからないなりに。

Yコンビネータって?

再帰関数gが関数fによって次のように表現されるときを考えます。

g = f(g)
たとえば、フィボナチ数のときは、
f = function(fib){
    return function(n){
        return (n <= 2) ? 1
                        : fib(n - 1) + fib(n - 2);
    }
}
メモ化させるなら、
f = function(fib){
    var memo = {};
    return function(n){
        return memo[n] || (memo[n] =
            (n <= 2) ? 1
                     : fib(n - 1) + fib(n - 2));
    }
}
たとえば、階乗のときは、
f = function(fact){
    return function(n){
        return (n == 1) ? 1
                        : n * fact(n - 1);
    }
}

このとき、

g = Y(f)

となる関数YがYコンビネータ。関数fを引数にしたとき、再帰関数gを返す関数YをYコンビネータというようだ。
この関数Yは、

f(Y(f)) = Y(f)

をみたす。つまり、Y(f)にfをいくら作用させても変わらない。

f(f(f(f( ・・・ f(Y(f)) ・・・ )))) = Y(f)

このY(f)の不変性から、Yは不動点関数あるいは不動点演算子とも呼ばれるようだ。

もうすこし具体的に、Yって、どう表現できるの?

再帰関数gに引数を与えて計算すると

g(m) = f(g)(m)

だから、

g = function(m){
    return f(g)(m);
}

と書ける。これを、

Y(f) = g

に適用すると、

Y = function(f){
    var g = function(m){
        return f(g)(m);
    }
    return function(m){
        return f(g)(m);
    }
}

高階関数を導入して変数変換をすると、

Y = function(f){
    var h = function(k){
        return function(m){
            return f(k(k))(m);
        }
    }
    var g = h(h);
    return function(m){
        return f(h(h))(m);
    }
}

つまり、

Y = function(f){
    return (function(h){
        return function(m){
            return f(h(h))(m);
        }
    })(function(k){
        return function(m){
            return f(k(k))(m);
        }
    });
}

自由変数を一切使わず、引数の変数のみで表現している。そのため引数名を変えられる。

Y = function(f){
    return (function(g){
        return function(m){
            return f(g(g))(m);
        }
    })(function(g){
        return function(m){
            return f(g(g))(m);
        }
    });
}

上記は再帰関数が1つの引数をとるものと仮定した場合のjavascriptでの表現。
Yコンビネータの返す再帰関数が複数の引数をとりうると想定したjavascriptでの表現は次のようになる。

Y = function(f){
    return (function(g){
        return function(){
            return f(g(g)).apply(null, arguments);
        }
    })(function(g){
        return function(){
            return f(g(g)).apply(null, arguments);
        }
    });
}

途中で変数変換できるところがおもしろい。あの変数変換の仕方がほかにもあるのならYコンビネータの表現は複数あるということになるのかな。一意に決まるのかどうか正確な議論があったりするのかな。あの高階関数を利用した自己言及的な変数変換が再帰関数を返すことのできる根拠につながるのかな。初めの条件に再帰があるから当然といえるのかもしれないけれど。


それにしても不思議なかたちだ。

へもかメソッド入力支援Greasemonkey

ブラウザからテキストエリアへ入力する際に、語尾に小さな「」「」「」を追加しやすくするためのGreasemonkeyスクリプトを書いてみました。

インストール先

hemoka.user.js

内容をご確認のうえ、よろしければ、どうぞご利用ください。

へもかメソッドについて

id:kokokubetaさんのエントリをご参照ください。

「首相、退陣か」「広告業界、崩壊も」「噂の二人、結婚へ」語尾にへ・も・かをついて曖昧にすることをへもかメソッドと呼びます(?)。よくスポーツ紙で見ますが、一般的な新聞でもあやふやな情報を出すときにはこの語尾になってたりするようです。さて、ネットでもこのへもかメソッドを使いたい(自信がないときとか)。今は「x」+「あ」で小さい「ぁ」が出るようになっているけど、同様の操作で小さい「へ」「も」「か」が出るようになってほしい。いや、ほしくない。

へもかキーが欲しい - kokokubeta;

ご利用方法

各種ブログサービスなどで、ブラウザからテキストエリア内に書き込むときに動作します。XまたはLキーのあとに「へ」「も」「か」を入力すると、smallタグを自動補完します。
また、語尾に助詞「へ」「も」「か」がつく単語がリストアップされます。小さな「へもか」にしたいときは該当するチェックボックスをチェックします。解除するとふつうにもどります。
単語が表示される灰色の領域は、マウスを押しながら移動させることができます。

注意事項

HTML形式を受けつけるテキストエリアのご利用を想定しています。

ソース

// ==UserScript==
// @name           hemoka
// @namespace      http://d.hatena.ne.jp/atkatanto/
// @include        http://*
// @include        https://*
// ==/UserScript==

var editor;

function seek(){
    if(editor) return;
    editor = document.getElementsByTagName('textarea')[0];
    if(editor){
        editor.addEventListener('keyup', parse, false);
        document.removeEventListener('DOMNodeInserted', seek, false);
    }
}
document.addEventListener('DOMNodeInserted', seek, false);
seek();

function parse(){
    editor.value = editor.value.replace(/ヵ|[xXxXlLlL]([へもか])/g, function(v){return '<small>' + (v[1] || 'か') + '</small>';});
    var text = editor.value;
    var str = text;
    var pos;
    var start = 0;
    var list = [];
    var regex = /(([一-龠々〆ヵヶ]+|[ぁ-んー]+|[ァ-ヴーヵヶ]+|[a-zA-Z]+|[a-zA-Z]+)?(?:([へもか])(?=[\s !?!?..。・…'"’”))」』,、]|$)|<small>[\s\n ]*([へもか])[\s\n ]*<\/small>))/;
    while(true){
        pos = str.search(regex);
        if(pos == -1) break;
        pos = pos + start;
        list.push({start:pos, word:RegExp.$2, hemoka:(RegExp.$3||RegExp.$4), small:!!RegExp.$4, original:RegExp.$1});
        start = pos + RegExp.$1.length;
        str = text.slice(start);
    }
    set(list);
}

var moving = false;
var baseX, baseY, pageX, pageY;
var panel;

function set(list){
    if(!panel){
        panel = document.createElement('div');
        with(panel.style){
            position  = 'absolute';
            width     = '200px';
            height    = '200px';
            overflow  = 'auto';
            margin    = '0px';
            border    = '0px';
            padding   = '10px';
            right     = '10px';
            top       = '10px';
            cursor    = 'move';
            zIndex    = '2147483647';
            textAlign = 'left';
            backgroundColor = '#ddd';
        }
        panel.addEventListener('mousedown', function(e){
            if(moving) return;
            e.preventDefault();
            with(getComputedStyle(panel, null)){
                baseX = parseInt(left.replace('px','')) || 0;
                baseY = parseInt(top.replace('px',''))  || 0;
            }
            pageX = e.pageX;
            pageY = e.pageY;
            moving = true;
        }, false);
        document.body.appendChild(panel);
    }
    var html = '<ol>';
    var data;
    var i;
    for(i=0; i<list.length; i++){
        data = list[i];
        html += '<li><input type="checkbox" ' + (data.small ? 'checked' : '') + '/> ' + data.word + data.hemoka + '</li>';
    }
    html += '</ol>';
    var words = document.createRange().createContextualFragment( html );
    var input = document.evaluate('descendant::*[local-name()="INPUT" or local-name()="input"]', words.firstChild, null, 7, null);
    for(i=0; i<input.snapshotLength; i++)with({j:i}){
        input.snapshotItem(i).addEventListener('click', function(){
            var data = list[j];
            var flag = this.checked;
            var text = editor.value;
            var delta = flag ? 15 : (data.word.length + 1 - data.original.length);
            var renewal = data.word + (flag ? ('<small>' + data.hemoka + '</small>') : data.hemoka);
            editor.value = text.substr(0, data.start) 
                         + text.slice(data.start).replace(data.original, renewal);
            list[j].original = renewal;
            for(var i=j+1; i<list.length; i++){
                list[i].start += delta;
            }
            if(!flag && (list[j].start + list[j].original.length == list[j+1].start)) parse();
        }, false);
    }
    panel.innerHTML = '<center><b><small>へもか</small>メソッド</b></center><hr/>';
    panel.appendChild( words );
}

document.addEventListener('mousemove', function(e){
    if(!moving) return;
    with(panel.style){
        left = (e.pageX - pageX + baseX) + 'px';
        top  = (e.pageY - pageY + baseY) + 'px';
        right = '';
    }
}, true);

document.addEventListener('mouseup', function(e){
    if(!moving) return;
    moving = false;
}, true);

透明人間

タイトルは釣りです。誰か開発してくれないかなあ。いや特に困ってはいないんですが。

「タイトルは釣りです」と本文に書いてあるエントリを非表示にするGreasemonkey - kokokubeta;


書いてみました。
AutoPagerizeに対応しています。
よろしければ、どうぞご利用ください。*1

追記

うひゃあ、日記に書いたら本当につくってくれる人がいた!うれしい。でもまだインストールの方法を理解できていない

はてなブックマーク - kokokubetaのブックマーク / 2008年12月15日

Greasemonkey を導入した Firefox で、InvisibleFisherman.user.js にアクセスすると、内容確認とインストールをおこなえます。ご参考まで!(追記ここまで)

// ==UserScript==
// @name           InvisibleFisherman
// @namespace      http://d.hatena.ne.jp/atkatanto/
// @include        http://*
// @include        https://*
// ==/UserScript==

var xpath1 = '//text()[contains(.,"タイトルは釣り")]/ancestor::*[local-name()="DIV" or local-name()="div"][1]';
var xpath2 =['//text()[substring(.,string-length(.)-3)="タイトル"]/../..', 
             '//text()[substring(.,string-length(.)-4)="タイトルは"]/../..',
             '//text()[starts-with(.,"釣り")]/../..'].join('|');
var xpath3 = 'ancestor::*[local-name()="DIV" or local-name()="div"][1]';

var patrol = function(i){
    var fisher = document.evaluate(xpath1, document, null, 7, null);
    for(i=0; i<fisher.snapshotLength; i++)
        fisher.snapshotItem(i).style.visibility = 'hidden';
    var suspect = document.evaluate(xpath2, document, null, 7, null);
    for(i=0; i<suspect.snapshotLength; i++)
        if(!!~suspect.snapshotItem(i).textContent.indexOf('タイトルは釣り'))
            document.evaluate(xpath3, suspect.snapshotItem(i), null, 9, null).singleNodeValue.style.visibility = 'hidden';
};
patrol();

var target;

GM_xmlhttpRequest({
    method : 'get',
    url    : 'http://wedata.net/databases/AutoPagerize/items.json',
    onload : function(res){
        if(res.status != 200) return;
        var info = [];
        try{
            info = eval(res.responseText).filter(function(v){
                return (new RegExp((v.data || 0).url || '^$')).test(location.href);
            }).map(function(v){
                return {
                    insertBefore : (v.data || 0).insertBefore,
                    pageElement  : (v.data || 0).pageElement
                };
            });
        }catch(e){}
        if(info.length == 0) return;
        if(info[0].insertBefore)
            target = (getFirstElementByXPath(info[0].insertBefore) || 0).parentNode;
        if(!target){
            var lastPageElement = getElementsByXPath(info[0].pageElement).pop();
            if (lastPageElement) target = lastPageElement.parentNode;
        }
        if(target)
            target.addEventListener('DOMNodeInserted', patrol, false);
    }
});

/*
 *  code and ideas from AutoPagerize
 *     [http://userscripts.org/scripts/show/8551]
 *  by swdyh [http://d.hatena.ne.jp/swdyh/] , and
 *     nanto_vi [http://nanto.asablo.jp/blog/]
 */

function getElementsByXPath(xpath, node) {
    var nodesSnapshot = getXPathResult(xpath, node,
        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE)
    var data = []
    for (var i = 0; i < nodesSnapshot.snapshotLength; i++) {
        data.push(nodesSnapshot.snapshotItem(i))
    }
    return data
}

function getFirstElementByXPath(xpath, node) {
    var result = getXPathResult(xpath, node,
        XPathResult.FIRST_ORDERED_NODE_TYPE)
    return result.singleNodeValue
}

function getXPathResult(xpath, node, resultType) {
    var node = node || document
    var doc = node.ownerDocument || node
    var resolver = doc.createNSResolver(node.documentElement || node)
    // Use |node.lookupNamespaceURI('')| for Opera 9.5
    var defaultNS = node.lookupNamespaceURI(null)
    if (defaultNS) {
        const defaultPrefix = '__default__'
        xpath = addDefaultPrefix(xpath, defaultPrefix)
        var defaultResolver = resolver
        resolver = function (prefix) {
            return (prefix == defaultPrefix)
                ? defaultNS : defaultResolver.lookupNamespaceURI(prefix)
        }
    }
    return doc.evaluate(xpath, node, resolver, resultType, null)
}

function addDefaultPrefix(xpath, prefix) {
    const tokenPattern = /([A-Za-z_\u00c0-\ufffd][\w\-.\u00b7-\ufffd]*|\*)\s*(::?|\()?|(".*?"|'.*?'|\d+(?:\.\d*)?|\.(?:\.|\d+)?|[\)\]])|(\/\/?|!=|[<>]=?|[\(\[|,=+-])|([@$])/g
    const TERM = 1, OPERATOR = 2, MODIFIER = 3
    var tokenType = OPERATOR
    prefix += ':'
    function replacer(token, identifier, suffix, term, operator, modifier) {
        if (suffix) {
            tokenType =
                (suffix == ':' || (suffix == '::' &&
                 (identifier == 'attribute' || identifier == 'namespace')))
                ? MODIFIER : OPERATOR
        }
        else if (identifier) {
            if (tokenType == OPERATOR && identifier != '*') {
                token = prefix + token
            }
            tokenType = (tokenType == TERM) ? OPERATOR : TERM
        }
        else {
            tokenType = term ? TERM : operator ? OPERATOR : MODIFIER
        }
        return token
    }
    return xpath.replace(tokenPattern, replacer)
}

カイゼン

// ==UserScript==
// @name           InvisibleFisherman
// @namespace      http://d.hatena.ne.jp/atkatanto/
// @include        http://*
// @include        https://*
// ==/UserScript==

var xpath1 = '//text()[contains(.,"タイトルは釣り")]/ancestor::*[local-name()="DIV" or local-name()="div"][1]';
var xpath2 =['//text()[substring(.,string-length(.)-3)="タイトル"]/../..', 
             '//text()[substring(.,string-length(.)-4)="タイトルは"]/../..',
             '//text()[starts-with(.,"釣り")]/../..'].join('|');
var xpath3 = 'ancestor::*[local-name()="DIV" or local-name()="div"][1]';

var patrol = function(i){
    var fisher = document.evaluate(xpath1, document, null, 7, null);
    for(i=0; i<fisher.snapshotLength; i++)
        fisher.snapshotItem(i).style.visibility = 'hidden';
    var suspect = document.evaluate(xpath2, document, null, 7, null);
    for(i=0; i<suspect.snapshotLength; i++)
        if(!!~suspect.snapshotItem(i).textContent.indexOf('タイトルは釣り'))
            document.evaluate(xpath3, suspect.snapshotItem(i), null, 9, null).singleNodeValue.style.visibility = 'hidden';
};
patrol();

document.addEventListener('DOMNodeInserted', patrol, false);

特徴

  • コードをみじかく
  • AutoPagerizeに限らず動的コンテンツに対応
  • GM_系メソッドに非対応なブラウザのユーザースクリプトにしても動的コンテンツに対応*2

*1:Greasemonkeyアイコンの右クリックから「新規ユーザスクリプト」をえらびソースをコピー

*2:DOMNodeInsertedイベントをサポートしていれば