anti scroll

ブラウザと小説の新しい関係を模索する

縦書き文庫に「チップ記法」を追加しました

チップ記法について

エディタに以下のようなボタンが追加されていますが、これは「チップ記法」という新しい記法のボタンです。

f:id:convertical:20131220172515p:plain

押すと

((タイトル:内容))

などと注入されますが、これがそのままチップ記法の文法になっています。

実際に使ってみる

例えば

新しい((パソコン:パーソナルコンピューターの略))が欲しい。

と書くと、本文ではこんな風に表示されます。

f:id:convertical:20131220173020p:plain

ここで「パソコン」というリンクをクリックすると

f:id:convertical:20131220173046p:plain

みたいに開きます。新しいパソコンが欲しい。

表示は縦書きにしようか横書きにしようか迷ったのですが、補足情報なので横書きを選びました。

アメリカにおける技術書の電子出版ノウハウを綴ったブログが面白かった

アメリカではすっかり電子出版が一般化しているようです。しかし実際はどんな感じなのか知らなかったので、興味本位で読み始めたら面白かった記事を紹介します。

$16,228 earned, 1014 copies sold: Lessons learned from a year as a self-published author

sublime textというエディタの技術書を出版した経験について綴られています。

結果的にかなり儲かったみたいです。副業で書いた電子書籍が1000部も売れて、日本円で160万ぐらいになったなら、そこそこ成功した例なんじゃないでしょうか。

内容を要約すると、こんな感じです。

  • twitterは営業ツールとしては殆ど役に立たない。フォロアーをカスタマーに変えることは難しい。
  • 一方でメールは最強の営業ツールである。
  • 値段は相場(アメリカでは$9.99が相場だと思われている)じゃなく、自分が考える価値で付ける。安売りしない。
  • 自分の場合は$19で売り出した。sublime textを使うような顧客はリッチな客が多いと踏んだからだ。
  • 自分の使っているLeanPubというサイトでは、購入者が好意で余計に払うことが出来るオプションを設定できるのだが、38%もの購入者が余計に払ってくれた(中には$50出してくれた人もいた!)。それは全売り上げの48%にも上った。
  • 営業活動が突如として泣かず飛ばすになって、売り上げが横ばいになることもあるけど、めげない。いつブレークするかは誰にもわからない。
  • 自分の場合は有名ブロガーに紹介されたり、他の出版社から出版を依頼されたり、他の執筆者に一緒に書籍のセット売りをしないか、と持ち掛けられたりした。
  • 技術書を出版した上で後悔していることとしては、初版に内容を盛り込みすぎたこと。技術変革のスピードは速い。書いてすぐに内容が陳腐化してしまった。コンパクトなボリュームを、タイムリーに出すほうが良さそうだ。


こういう感じです。

とにかく営業ツールとしてのメールの強力さを強調しておられるように感じました。

実際この方のブログを見ても、記事の最後とかサイドバーの目立つ場所にメーリングリストへの登録案内があります。

それだけメールを重視しているということなんでしょう。

ホームページもツイッターも、こういう風に明確な目的を持った上で運営したら、結果に結びつきやすいのかもしれませんね。

この方も始めたばかりの頃は、100人ぐらいしかフォロアーがいなかったし、ブログも両手で数えられるぐらいの人数しかアクセスがなかった、とのこと。

何事もやり方しだいってことなのかなあ……

何かしらの電子出版を志している人にとっては、参考になる話しなんじゃないでしょうか。

unicode substring of OCaml for application layer

cwn経由でextlibにUTF8.substringが定義されたと知り、じゃあオレオレな実装からサヨナラできるかな、と思って試してみましたが、

assert_equal (UTF8.substring "hoge" 0 5) "hoge"

は残念ながら "out of range" という例外で終わってしまう……

extlibのsubstringは、LLなsubstringとは違い、境界を超えるような値の定義を許さないみたいです。

でもこれだと、常に利用者が境界値のチェックを義務付けられるわけで、アプリケーション側からは使いにくいかなあと。

だって、例外を捕まえるにしても out of rangeってだけじゃ、どういう風に溢れたのかわからないですし。

substring "hoge" 0 100 で溢れるのも、substring "hoge" 100 1で溢れるのも、同じ例外ですよね。

でもLLな感覚のsubstringなら、前者は"hoge"を返したいし、後者は空文字を返したい。

というわけで、以下がjingooでの実装です(jingoo/jg_utils.mlより抜粋)。

open ExtLib

let rec substring base count str =
  let len = UTF8.length str in
  if base >= len || count = 0 then
    ""
  else if base = 0 && count >= len then
    str
  else if base < 0 then
    substring (len + base) count str
  else if base + count >= len then
    let lp = UTF8.nth str base in
    let rp = UTF8.next str (UTF8.last str) in
    String.sub str lp (rp - lp)
  else
    let lp = UTF8.nth str base in
    let rp = UTF8.nth str (base + count) in
    String.sub str lp (rp - lp)


振る舞い方はこんなです。

assert_equal (substring  0  0)  "hoge"  ""
assert_equal (substring  0  1)  "hoge"  "h"
assert_equal (substring  0  4)  "hoge"  "hoge"
assert_equal (substring  0  5)  "hoge"  "hoge"
assert_equal (substring  1  1)  "hoge"  "o"
assert_equal (substring  2  1)  "hoge"  "g"
assert_equal (substring  3  1)  "hoge"  "e"
assert_equal (substring  4  1)  "hoge"  ""
assert_equal (substring  5  0)  "hoge"  ""
assert_equal (substring  5  1)  "hoge"  ""

(** negative base *)
assert_equal (substring  (-1)  1)  "hoge"  "e"
assert_equal (substring  (-2)  1)  "hoge"  "g"
assert_equal (substring  (-3)  1)  "hoge"  "o"
assert_equal (substring  (-4)  1)  "hoge"  "h"
assert_equal (substring  (-4)  2)  "hoge"  "ho"
assert_equal (substring  (-4)  3)  "hoge"  "hog"
assert_equal (substring  (-4)  4)  "hoge"  "hoge"
assert_equal (substring  (-4)  5)  "hoge"  "hoge"
assert_equal (substring  (-5)  1)  "hoge"  "e"

手っ取り早くchromeを高速にしたい

chromeそのものを速くするわけじゃないですが、大抵のウェブサイトを高速に表示させる方法があります。

それは、今やどこもかしこも「埋めこむのが当然のマナー」みたいになってる「twitterボタン」と「facebookボタン」をブロックすることです。

chromeにはNotScriptsという、firefoxのNoScriptと同じようなことをしてくれるアドオンがあるので、これを導入します。

Chrome ウェブストア - NotScripts

じゃあなぜSNSボタンをブロックすると早くなるのか。

それは、これらのボタンがiframeを使っているからです。

iframeはブラウザ版のforkのようなもので、個別にjs/css/domなどのトップレベル環境を新規に作成するタグです。

もちろん独立したトップレベルの組版環境は有意義なものではあるのですが、たかだかボタン一つのために作るのはちょっと……と思います。

それが主要SNSの数だけ埋め込まれるわけですから、重くなって当然ですよね。

実際このアドオンを入れて、SNSボタンが表示されなくなるだけで、殆どのサイトが軽くなります(このアドオン、scriptだけじゃなくてiframeのブロックにも対応しているのです)。

ただ、スクリプトが走らないと動かないサイトもあるので、サイトごとにリソースのブロック・アンブロックを設定したいところですが、それをするためには、ちょっとだけアドオンのソースに書き加える必要があります。

でもそんなに複雑なことじゃありません。

インストールするとタブが開いて「パスワード設定して」と案内が出るので、その通りにするだけです。

具体的には、Windows XPなら

%userprofile%\Local Settings\Application Data\Google\Chrome\User Data\Default\Extensions\odjhifogjcknibkahlpidmdajjpkkcfn

Windows Vista/7なら

%userprofile%\AppData\Local\Google\Chrome\User Data\Default\Extensions\odjhifogjcknibkahlpidmdajjpkkcfn

Mac OS Xなら

~/Library/Application Support/Google/Chrome/Default/Extensions/odjhifogjcknibkahlpidmdajjpkkcfn

Linuxなら

~/.config/google-chrome/Default/Extensions/odjhifogjcknibkahlpidmdajjpkkcfn

にそれぞれ移動し、その中にある「CHANGE__PASSWORD__HERE.js」というファイルを開いて、中の

const ENCRYPTION_PASSWORD="";

の部分に自分のパスワードを打ち込んで、ブラウザを再起動するだけです。

ただこのパスワード、最低でも20文字以上が必要なんで、それだけ注意が必要ですね。

opensslが入ってるなら

openssl rand -base64 32

とかが手っ取り早いかも。

jingooの更新(white space controll用のシンタックス追加、およびスレッド版パッケージの分離)

ほぼjinja2互換なocaml template engineであるところのjingooなのですが、久しぶりにいくつかの修正を加えました。

  1. {%- と -%} のサポート(white space controll)
  2. マルチスレッドバージョンとノーマルバーションのパッケージを分離

1.{%- と -%}のサポート(white space controll)

{%-は、このマーク「以前」にある空白改行を削除し、-%} は、このマーク「以後」にある空白改行を削除するためのシンタックスです。

参考:http://jinja.pocoo.org/docs/templates/#whitespace-control

だから例えば

aaa
{% for i in [1,2,3] -%}
{{i}}
{% endfor -%}
bbb

とすれば、

aaa
1
2
3
bbb

と出力するし、

aaa
{%- for i in [1,2,3] -%}
{{i}}
{%- endfor -%}
bbb

とすれば、

aaa123bbb

と一切の改行空白が無い表示が出力されます。

これでまた一歩、jinja2に近づいたかな……

マルチスレッドバージョンとのパッケージ分離

次にマルチスレッド対応パッケージを分離したことについてですが、今まではスレッドを使わないプログラムでもコンパイル時に「-thread」というコマンドラインオプションを強要してました。

これがスレッドを使わないプログラムでjingooをリンクしたい人にとって不満だったらしく、これに対応した形です。

今後は「-thread」をつけなかったときはjingoo.cm[x]aがリンクされ、つけたときはjingoo.threads.cm[x]aがリンクされるようになります。

OCamlでObject-relational mapping(ORM)みたいなやつ

ocorm表題の件をなんとなく実現するライブラリです。

https://github.com/tategakibunko/ocorm

ocormが提供するMakeRelationMapファンクターの定義はこのようなものです。

module MakeRelationMap (F:FieldMap)(S:Schema):RelationMap with type t = F.t

このファンクターは、スキーマの定義モジュール(Schema)と、データ型の変換モジュール(FieldMap)を受け取り、次のことをするモジュールを生成します。

  • (string, string) listを、FieldMapが定義するオブジェクトに変換
  • (string, string) listを、Schemaが定義する制約に基づいてバリデーション

Schemaモジュールの定義

こんな風に定義します。

module User = struct
  let table_name = "user"
  let props = [
    ("id", PrimaryKeyProperty);
    ("name", StringProperty [NotNull; MinLength 3; MaxLength 16]);
    ("point", IntProperty [NotNull; Default (`Int 0)]);
  ]
end

フィールド名と属性のセットをpropsにセット。属性定義には、制約を付けることができます。

FieldMapモジュールの定義

例としてjson-wheelライブラリの定義するJson_type.tに変換する処理を定義してみます。

module JsonFieldMap = struct
  type t = Json_type.t
  let null = Json_type.Null
  let map_value prop value =
    match prop with
      | PrimaryKeyProperty -> Json_type.Int (int_of_string value)
      | StringProperty _ -> Json_type.String value
      | TextProperty _ -> Json_type.String value
      | IntProperty _ -> Json_type.Int (int_of_string value)
      | FloatProperty _ -> Json_type.Float (float_of_string value)
      | BooleanProperty _ -> Json_type.Bool (value = "t")
  let map_alist alist = Json_type.Object alist
  let map_list lst =  Json_type.Array lst
end

null/int/string/float/list/alistといった属性を、自分が変換したいオブジェクトにマッピングする処理を記述します(alistは属性リスト)。

ちなみに上の変換処理ですが、本当はもう少し厳密に変換したほうが良いと思います(空文字だったらどうする?とか)。

MakeRelationMapファンクター

これら二つのモジュールをMakeRelationMapに渡すと、UserテーブルをJSONに変換したり、バリデーションしたりできるモジュールが手に入ります。

module UserMap = MakeRelationMap(JsonFieldMap)(User)

オブジェクト変換

JSONに変換するには、UserMap.objectifyを使います。

let table_row = [("id", "1"); ("name", "taro"); ("point", "100")] in
let json = UserMap.objectify table_row in (** Json_type.t が取得できる *)
Json_io.string_of_json json

=> {"id":1, "name":"taro", "point":100}

バリデーション

データをバリデーションするには、UserMap.validateを使います。

(** nameには3文字以上、16文字以下の制約が付いている *)
UserMap.validate [("id", "1"); ("name", "a"); ("point", "100")]
=> exception: name is too short(min 3, max 16)

jingooオブジェクトを出力させる

schemaの定義と変換のモジュールが別々に定義できるので、例えばjingooのテンプレートに渡せるオブジェクトを作りたい場合は、JsonFieldMapモジュールを、こんな感じのモジュールに差し替えるだけで対応できます。

open Jg_types

module JingooFieldMap = struct
  type t = tvalue
  let null = Tnull
  let map_value property value =
    match property with
      | PrimaryKeyProperty -> Tint (int_of_string value)
      | StringProperty _ -> Tstr value
      | TextProperty _ -> Tstr value
      | IntProperty _ -> Tint (int_of_string value)
      | FloatProperty _ -> Tfloat (float_of_string value)
      | BooleanProperty _ -> Tbool (value="t")
  let map_alist alist =
    let hash = Hashtbl.create (List.length alist) in
    Thash (List.fold_left (fun h (n,v) -> Hashtbl.add h n v; h) hash alist)
  let map_list rows = Tlist rows
end

(** jingooのオブジェクトに変換するモジュール *)
module UserMap2 = MakeRelationMap(JingooFieldMap)(User)

ORM……なのか?

……ご覧の通り、ORMといってもActiveRecordみたいにSQLに関わる部分まで抽象化しているわけではなく、あくまでSQL呼び出しの結果が(string, string) list で取得できたとして、それを特定のオブジェクトに変換したり、ついでにバリデーションしたりっていう仕事をするためのライブラリです。