2012-02-28

BOTは電気羊の(ry

ボットはいかにして私から価格付けの力を奪ったのか
How Bots Seized Control of My Pricing Strategy

Amazonには自動的に本をマーケットプレイスで出品するBOTがいる。そのBOTは正規品より値段が高い。とはいっても、別に違法な複製品を売っているわけではない。何をやっているかというと、マーケットプレイスで注文が入った時点で、正規品を購入して送りつけ、その差額を得ているのだ。つまり、たまたま高い方を選んでしまったカモを相手にしている商売だ。まあ、商売の基本は安く仕入れて高く売るわけだから、このBOTは普通の商人という見方もできる。

この手のBOTは複数いて、BOT同士で競争するので、時に、値段は正規品より下がることがある。この場合、送料などで利益を得ているそうだ。

さらに、アマゾンがこのBOTの出品の動きにつられて、自動的に正規品の値下げをすることまであるらしい。値下げ分の損失はアマゾンが引き受けるそうだが、それにしてもBOT同士の競争につられてアマゾンBOTが動くのだから興味深い。

But I can't help but think about that old gambler's proverb: “If you can't spot the sucker, it's you.”

こと、こうなってくると、博打打ちの諺を思わずに入られない。「誰がカモなのかわからないならば、自分がカモなのだ」

著作権法に反対したら著作権侵害

Key Techdirt SOPA/PIPA Post Censored By Bogus DMCA Takedown Notice | Techdirt

著作権法について反対する意見を述べているページへのGoogleのリンクが、なぜかDMCA takedownを受けたという話。

著作権法がいかに悪用されやすいかという自己言及的な例といえよう。

追記:後日談。DMCA takedownを要求した奴らいわく、「当時、キーワードで自動的に取り下げ要求送ってた。間違いだった。すまん、すまん。」
Company That Issued Bogus Takedown Says It Was All A Mistake, Apologizes | Techdirt

そんなに簡単に間違いが効果を発揮する今の法律はおかしい。

2012-02-27

partial orderingの訳語が思いつかない

partial orderingの訳語について思案中。

2012-02-26

HDDの価格は依然として高い

こと、コンピューターとその周辺機器に関しては、「思い立ったが吉日」である。と同時に、常に「今は時期が悪い」状態でもある。だから、必要になった時点で待たずに買うべきなのだ。それにしても、今は時期が悪い。

色々と考えた挙句、やはり将来的には、Linuxに移行すべきであると結論した。もはやWindowsは終わった。ソフトウェアを文化財産とみた場合、これからは、ソフトウェアは最低でもオープンソース、望ましくはフリーであるというのが必須になってくることに疑いはない。なぜならば、文化財産は次世代のために保存しなければならないからだ。ともかくLinuxをインストールしてみようと思ったのだが、これがまた厄介なのだ。問題はLinuxとは関係がない。

まず、今のWindowsをインストールしているHDDには手を付けたくない。いまのWindowsの環境は、念のためにこのままの状態で残しておくべきだ。パーティションも切りたくない。デュアルブートも面倒だ。そこで、物理的に別なHDDを用意して、そこにLinuxをインストールしようと、こう考えた。物理的に今のHDDを接続しなければ、どんなヘマをやらかしても今の環境を壊す恐れはない。

そのため、HDDを買おうと考えたのだが・・・色々と問題が多い。

まず値段だ。やはりタイ洪水の影響は未だに大きく、HDDの値段は高い。まあ、2TiBや3TiBのHDDを買うのに一万円かかるというのであれば、納得できる。しかし、1TiBのHDDも同様に一万円するというのは納得できない。いや、もちろん、タイ洪水という納得できる事情があるのだが、やはり納得できない。同じ一万円なら、同じく一万円程度の値段の120GiBのSSDを買うべきかもしれない。

そもそも、なぜ1TiBにこだわるのか。それは、私のマザーボードがUEFIを使っていないからだ。したがって、ブート可能なHDDに、2TiBの壁が横たわっている。そのため、2TiBのHDDは使えない。

いまさら容量の壁というのもおかしな話だが、現実問題なのだからどうしようもない。HDDに加えてマザーボードとメモリとCPUを交換するというのは経済的に苦しい。

それにしても、今のHDDの価格の高止まりは凄い。現時点では、HDDの値段に容量はあまり影響しないようだ。

GPTにより、容量の壁は、9.4ゼッタバイト(zettabyte)、もしくは、8ゼビバイト(zebibyte)まで遠ざけられた。さて、この壁は、将来問題になるだろうか。思うに、まだ私が生きているうちに問題になるだろうとおもう。(うっわ、しょうもな。たったの8ZiBまでしか使えへんなんて、今時ありえへんやろ。当時の奴ら、何考えてたんや、アホちゃうか)

まあ、今の環境でも、ビルド済みのgccのweekly snapshotのバイナリは手に入るので、ひとまずC++の参考書の執筆には問題がない。どちみち、今はまだ、満足できる品質のC++11コンパイラーが存在しないので、gccもclangも、C++11の参考書の執筆においては、信用するに足りない。それに、いまの環境を激的に変えるのは、時間的な損失が大きいのでやりたくない。参考書を書き終えてから、ともかくも考えよう。おそらく、OSをインストールするディスクは、将来的にはSSDを使うべきだと思う。

追記:どうも現実は非常にやっかいなようだ。というのも、Linuxは基本的にBIOS経由のGPTでのブートをサポートしている。これは、理論上、技術的に可能である。しかし、現実には、多くのBIOSが規格違反のバグを抱えているらしい。GRUB2はBIOS上でのGPTブートをサポートしているそうだ。つまり、BIOSでGPTが使えないというのは、ブートローダーがサポートしていないということである。GRUB2を使えば、BIOS環境でGPTを利用したHDDからOSをブート可能である。OSがGPTをサポートしてさえいれば、あとはOSの仕事である。だから、Linuxのインストール用であれば、BIOSであっても、どんな容量のHDDでも構わないということになる。

将棋終了のお知らせ

将棋連盟が棋譜の著作権を主張し始めた? ニコニコの将棋動画が次々と削除される 将棋速報FUZIP

コンピューターが人間を破るまでもなく、将棋の終了が近づいてきた。しかも、完全な人災だ。もし、単なる情報であるはずの棋譜に著作権を主張し始めたら、今後将棋の発展はなくなる。むしろ、これからの対戦では、既存の棋譜と同じ手を使えなくなるだろう。なぜならば、著作権侵害だからだ。

2012-02-25

最初のWebサーバーを保存せよ

Help save the worlds first webserver. We need to track down a copy of the original tarball WWWDaemon_0.1.tar.Z to preserve in the annals of history. [x-post] : compsci

1991年8月20日、Tim Berners-Leeはcomp.sys.next.announceに対して、歴史に残る告知を投稿した。「info.cern.chから、WorldWideWebアプリケーションのアルファ版リリースをソースコードとバイナリの両方で公開する」
この投稿は、以下の三つのソフトウェアを示し、我々が今知る、いわゆるインターネッツを開始したのだ。

/pub/WWWNeXTStepEditor_0.12.tar.Z NeXT application + sources
/pub/WWWLineMode_0.11.tar.Z    Portable Line Mode Browser
/pub/WWWDaemon_0.1.tar.Z     Simple server

エディターとブラウザーは、多くのサイトで保存されている。例えばここだ。しかし、最初のWebサーバーは、どこにも見当たらない。これは憂慮すべき事態である。公開アクセスできるFPSサーバーを大量に漁った末、容易に入手できる中でもっとも初期の版は、v 2.08bである(apacheとw3.rogの両方の公開FTPにコピーが置いてある)。しかし、これ以前のものは見当たらない。

Redditターよ。汝ら猫を救い、政府と戦い、正義を守り続けてきた(あと他にも下らないことたくさんしてるけどな)。この歴史的に重要な財産を守り、もって次の世代に伝えよ。

追記:Henrik Frystyk Nielsen、WWWの最初のコントリビューターも参加した。
https://twitter.com/#!/frystyk/status/173128770763501568

追記2:Timのw3.orgのアドレスにメール送ってみた。でも多分、「インターネット早くしてよ!」ってなメールに埋もれてしまうだろうけど。

これは非常に興味深い問題である。もし歴史家が、インターネットの歴史について研究する場合、当然、最初のWebサーバーのソースコードの改良具合を順々に検証できる必要がある。しかるに、たったの21年しか立っていない今、その最初のオリジナルのソースコードが見つからないとは脅威だ。堂々と公開されていたはずなのに存在しないのだ。いわんや著作権とDRMで保存を妨害しているコンテンツをや。

そろそろ、我々は将来のために、文化的財産を保存する方法を真剣に考えるべきだ。さもなければ、将来の歴史家は海賊たちに感謝することになる。

参考:本の虫: なぜ歴史には海賊が必要なのか

追記:なんと見つかったそうだ。しかし、utilities.hがないのでコンパイルできないそうだ。どうもコメントから察するに、何らかのマクロを定義したヘッダのようだが。実際にコンパイルしていないので、どこで引っかかるのかわからないが、コードからどんなマクロなのか推測できそうなものである。

/*  TCP/IP based server for HyperText   TCPServer.c
**  ---------------------------------
**
**  History:
** 2 Oct 90 Written TBL. Include filenames for VM from RTB.
*/
/* Module parameters:
** -----------------
**
**  These may be undefined and redefined by syspec.h
*/
 
#define LISTEN_BACKLOG 2 /* Number of pending connect requests (TCP)*/
#define MAX_CHANNELS 20  /* Number of channels we will keep open */
#define BUFFER_SIZE 4096 /* Arbitrary size for efficiency */
 
#define WILDCARD '*'     /* Wildcard used in addressing */
 
#define NETCLOSE close     /* Routine to close a TCP-IP socket  */
#define NETREAD  read     /* Routine to read from a TCP-IP socket */
#define NETWRITE write     /* Routine to write to a TCP-IP socket */
 
#define FIRST_TCP_PORT 5000 /* When using dynamic allocation */
#define LAST_TCP_PORT 5999
 
#ifdef NeXT
#define SELECT   /* Handle >1 channel if we can.  */
#include <libc.h>  /* NeXT has all this packaged up */
#define ntohs(x) (x)
#define htons(x) (x)
#include <streams/streams.h>
 
#else
#include <stdio.h>
/* VM doesn't have a built-in predefined token, so we cheat: */
#ifdef __STDIO__
#define VM
#else
#include <string.h>  /* For bzero etc - not NeXT or VM */
#endif
#define SELECT   /* Handle >1 channel if we can.  */
#endif
 
 
#ifndef VM
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>     /* Must be after netinet/in.h */
#include <netdb.h>
#include <errno.h>     /* independent */
#include <sys/time.h>     /* independent */
#define TOASCII(c) (c)
#define FROMASCII(c) (c)                                   
 
#else    /* VM */
#include <manifest.h>                                                           
#include <bsdtypes.h>                                                           
#include <stdefs.h>                                                             
#include <socket.h>                                                             
#include <in.h>
#include <netdb.h>                                                                 
#include <errno.h>     /* independent */
extern char asciitoebcdic[], ebcdictoascii[];
#define TOASCII(c)   (c=='\n' ?  10  : ebcdictoascii[c])
#define FROMASCII(c) (c== 10  ? '\n' : asciitoebcdic[c])                                   
#include <bsdtime.h>                                                            
#define index(a,b) my_index(a,b) /* Why index() doesn't work I don't know  */
#endif
 
#include "utilities.h"  /* Coding convention macros */
 
/* Default macros for manipulating masks for select()
*/
#ifndef FD_SET
typedef unsigned int fd_set;
#define FD_SET(fd,pmask) (*(pmask)) |=  (1<<(fd))
#define FD_CLR(fd,pmask) (*(pmask)) &= ~(1<<(fd))
#define FD_ZERO(pmask)   (*(pmask))=0
#define FD_ISSET(fd,pmask) (*(pmask) & (1<<(fd)))
#endif
 
 
 
 
/* Module-Global Variables
** -----------------------
*/
PRIVATE enum role_enum {master, slave, transient, passive} role;
PRIVATE struct sockaddr_in  soc_address;  /* See netinet/in.h    */
PRIVATE int      soc_addrlen;
PRIVATE int      master_soc;   /* inet socket number to listen on */
PRIVATE int      com_soc;   /* inet socket number to read on */
PRIVATE fd_set      open_sockets; /* Mask of channels which are active */
PRIVATE int      num_sockets;  /* Number of sockets to scan */
PRIVATE BOOLEAN      dynamic_allocation; /* Search for port? */
PRIVATE char      buffer[BUFFER_SIZE];
 
/* Find character within string
**
** See the ANSI definition of index()
*/
#ifdef VM
char * my_index(char *s, char c)
{
    char *p;
    for(p=s; *p && (*p!=c); p++) /*scan */ ;
    return *p ? p : 0;
}
#endif
 
 
/*  Read a file
**  -----------
**
** The function reads a file from the given file POINTER
** into the given file DESCRIPTOR!
**
** On exit,
** returns  0 for end of file, <0 for error.
*/
PRIVATE int read_file(int dest, FILE * source)
{
   unsigned char * status;
   for(;;) {
       status = fgets(buffer, BUFFER_SIZE, source);
       if (!status) return -1;
       (void) write(dest, buffer, status);
   }
}
 
 
/* Strip white space off a string
**
** On exit,
** Return value points to first non-white character, or to 0 if none.
** All trailing white space is overwritten with zero.
*/
PRIVATE char * strip(char * s)
{
#define SPACE(c) ((c==' ')||(c=='\t')||(c=='\n')) 
    char * p=s;
    for(p=s;*p;p++);          /* Find end of string */
    for(p--;p>=s;p--) {
     if(SPACE(*p)) *p=0; /* Zap trailing blanks */
 else break;
    }
    while(SPACE(*s))s++; /* Strip leading blanks */
    return s;
}
 
/* Send a string down a socket
** ---------------------------
**
** The trailing zero is not sent.
** There is a maximum string length.
*/
PRIVATE int write_ascii(int soc, char * s)
{
    char * p;
    char ascii[255];
    char *q = ascii;
    for (p=s; *p; p++) {
        *q++ = TOASCII(*p);
    }
    return write(soc, ascii, p-s);
}
 
/* Handle one message
** ------------------
**
** On entry,
** soc  A file descriptor for input and output.
** On exit,
** returns  >0 Channel is still open.
**   0 End of file was found, please close file
**   <0 Error found, please close file.
*/
int handle(int soc)
{
#define COMMAND_SIZE 255
#define MAX_LINES 10000   /* Maximum number of lines returned */
    char command[COMMAND_SIZE+1];
    int status;
    char * arg;  /* Pointer to the argument string */
    char * filename; /* Pointer to filename or group list*/
    char * keywords; /* pointer to keywords */
    char system_command[COMMAND_SIZE+1];
    int lines, i;
    int fd;  /* File descriptor number */
   
    for (;;) {
 status = read(soc, command, COMMAND_SIZE);
 if (status<=0) {
     if (TRACE) printf("read returned %i, errno=%i\n", status, errno);
     return status; /* Return and close file */
 }
 command[status]=0; /* terminate the string */
#ifdef VM
 {
     char * p;
     for (p=command; *p; p++) {
  *p = FROMASCII(*p);
  if (*p == '\n') *p = ' ';
     }
 }
#endif
 if (TRACE) printf("read string is `%s'\n", command);
 
 arg=index(command,' ');
 if (arg) {
     *arg++ = 0;    /* Terminate command & move on */
     arg = strip(arg);   /* Strip leading & trailing space */
     if (0==strcmp("GET", command)) { /* Get a file    */
 
/*  Remove host and any punctuation. (Could use HTParse to remove access too @)
*/      
  filename = arg;
  if (arg[0]=='/') {
      if (arg[1]=='/') {
          filename = index(arg+2, '/'); /* Skip //host/ */
   if (!filename) filename=arg+strlen(arg);
      } else {
          filename = arg+1;  /* Assume root: skip slash */
      }
  }
 
  if (*filename) {  /* (else return test message)  */
      keywords=index(filename, '?');
      if (keywords) *keywords++=0; /* Split into two */
#ifdef VM
 
/* Formats supported by FIND are:
**
** /FIND    Help for find general index
** /FIND/    (same)
** /FIND/?    (same)
** /FIND/group.disk.ft.fn  Get a document from a disk
** /FIND/grouplist?  Help for group index
** /FIND/grouplist?keylist  Search a set of disks for keywords
**     where grouplist = group+group+group.. or void
**     keylist = key+key+key... (not void)
*/
      if((0==strncmp(filename,"FIND",4)) &&
         ((filename[4]==0) || (filename[4]=='/') || (filename[4]='?'))) {
      
          filename=filename+4;
   if (*filename=='/') filename++;  /* Skip first slash */
    
   if (!*filename) {  /* If no data at all,    */
       if (!keywords)
        keywords = filename; /* Respond as if just "?" given */   
   }
    
   if (keywords) {
       char *p;
       while((p=index(filename,'+'))!=0) *p=' ';/* Remove +s */
       while((p=index(keywords,'+'))!=0) *p=' ';/* Remove +s */
       
       if (!*keywords) { /* If no keywords    */
    write_ascii(soc,
    "<IsIndex><TITLE>Keyword Index</TITLE>\n");
    if (*filename) {
        write_ascii(soc, "This index covers groups: `");
        write_ascii(soc, filename);
    } else {
            write_ascii(soc,
            "This is the general CERN public document index");
    }
    write_ascii(soc,
  ".<P>Use a keyword search to get documents, help files, etc.<P>\n");
    return 0;
       }
    
       strcpy(system_command,"EXEC NICFIND1 ");
       strcat(system_command, keywords);
       if (*filename) {
    strcat(system_command, " ( ");
    strcat(system_command, filename);
       }
   } else {
       strcpy(system_command,"EXEC NICFOUN1 ");
       strcat(system_command, filename);
   }
   
   lines = system(system_command);
   if (TRACE) printf("Command `%s' returned %i lines.\n",
     system_command, lines);
   if (lines<=0) {
       write_ascii(soc,
       "\nSorry, the FIND server could not execute\n  `");
       write_ascii(soc, system_command);
       write_ascii(soc, "'\n\n");
       return 0;
   }
   
   /* Copy the file across: */
   
   for(;lines; lines--){  /* Speed necessary here */
       char *p;
       fgets(buffer, BUFFER_SIZE, stdin);
  /*     if (strcmp(buffer,"<END_OF_FILE>")==0) break; */
       for(p=buffer; *p; p++) *p = TOASCII(*p);
       write(soc, buffer, p-buffer);
   }
   return 0;
   
      }  /* if FIND */
#else
      fd = open(arg, O_RDONLY, 0 ); /* un*x open @@ */
      if (fd<0) return fd;
      status = read_file(soc, fd);
      close(fd);
      return 0;
#endif
  }
     } 
 }
 
 write_ascii(soc, "<h1>Small network access test v1.01</h1>\n");
 write_ascii(soc, "\n\nThe (unrecognised) command used was `");
 write_ascii(soc, command);
 write_ascii(soc, "'.\n\n");
 
 if (TRACE) printf("Return test string written back'\n");
 return 0;  /* End of file - please close socket */
    } /* for */
    
} /* handle */
 
/*_____________________________________________________________________________________
*/
/* Encode INET status (as in sys/errno.h)      inet_status()
** ------------------
**
** On entry,
** where  gives a description of what caused the error
** global errno gives the error number in the unix way.
**
** On return,
** returns  a negative status in the unix way.
*/
#ifdef vms
extern int uerrno; /* Deposit of error info (as perr errno.h) */
extern int vmserrno; /* Deposit of VMS error info */
extern volatile noshare int errno;  /* noshare to avoid PSECT conflict */
#else
#ifndef errno
extern int errno;
#endif
#endif
PRIVATE int inet_status(where)
    char    *where;
{
    CTRACE(tfp, "TCP: Error %d in `errno' after call to %s() failed.\n",
     errno,  where);
#ifdef vms
    CTRACE(tfp, "         Unix error number (uerrno) = %ld dec\n", uerrno);
    CTRACE(tfp, "         VMS error (vmserrno)       = %lx hex\n", vmserrno);
#endif
    return -errno;
}
 
 
/* Parse a cardinal value            parse_cardinal()
** ----------------------
**
** On entry,
** *pp     points to first character to be interpreted, terminated by
**      non 0:9 character.
** *pstatus    points to status already valid
** maxvalue    gives the largest allowable value.
**
** On exit,
** *pp     points to first unread character
** *pstatus    points to status updated iff bad
*/
PRIVATE unsigned int parse_cardinal(pstatus, pp, max_value)
   int   *pstatus;
   char   **pp;
   unsigned int  max_value;
{
    int   n;
    if ( (**pp<'0') || (**pp>'9')) {     /* Null string is error */
 *pstatus = -3;  /* No number where one expeceted */
 return 0;
    }
 
    n=0;
    while ((**pp>='0') && (**pp<='9')) n = n*10 + *((*pp)++) - '0';
 
    if (n>max_value) {
 *pstatus = -4;  /* Cardinal outside range */
 return 0;
    }
 
    return n;
}
/*  Bind to a TCP port
**  ------------------
**
** On entry,
** tsap is a string explaining where to take data from.
**  ""  means data is taken from stdin.
**  "*:1729" means "listen to anyone on port 1729"
**
** On exit,
** returns  Negative value if error.
*/
int do_bind(const char * tsap)
{
 
    FD_ZERO(&open_sockets); /* Clear our record of open sockets */
    num_sockets = 0;
    
/*  Deal with PASSIVE socket:
**
** A passive TSAP is one which has been created by the inet daemon.
** It is indicated by a void TSAP name.  In this case, the inet
** daemon has started this process and given it, as stdin, the connection
** which it is to use.
*/
    if (*tsap == 0) {   /* void tsap => passive */
 
 dynamic_allocation = FALSE;  /* not dynamically allocated */
 role = passive; /* Passive: started by daemon */
 
#ifdef vms
 {   unsigned short channel;     /* VMS I/O channel */
     struct string_descriptor {     /* This is NOT a proper descriptor*/
      int size;      /*  but it will work.       */
      char *ptr;      /* Should be word,byte,byte,long  */
     } sys_input = {10, "SYS$INPUT:"};
     int status;      /* Returned status of assign */
     extern int sys$assign();
 
     status = sys$assign(&sys_input, &channel, 0, 0);
     com_soc = channel; /* The channel is stdin */
     CTRACE(tfp, "IP: Opened PASSIVE socket %d\n", channel);
     return -BAD(status);
 } 
#else
 com_soc = 0;     /* The channel is stdin */
 CTRACE(tfp, "IP: PASSIVE socket 0 assumed from inet daemon\n");
 return 0;  /* Good */
#endif
 
/*  Parse the name (if not PASSIVE)
*/
    } else {    /* Non-void TSAP */
 char *p;  /* pointer to string */
 char *q;
 struct hostent  *phost;     /* Pointer to host - See netdb.h */
 char buffer[256];  /* One we can play with */
 register struct sockaddr_in* sin = &soc_address;
 
 strcpy(buffer, tsap);
 p = buffer;
 
/*  Set up defaults:
*/
 sin->sin_family = AF_INET;     /* Family = internet, host order  */
 sin->sin_port = 0;      /* Default: new port,    */
 dynamic_allocation = TRUE;     /*  dynamically allocated */
 role = passive;       /*  by default */
 
/*  Check for special characters:
*/
 if (*p == WILDCARD) {  /* Any node */
     role = master;
     p++;
 }
 
/*  Strip off trailing port number if any:
*/
 for(q=p; *q; q++)
     if (*q==':') {
         int status;
  *q++ = 0;  /* Terminate node string */
  sin->sin_port = htons((unsigned short)parse_cardinal(
         &status, &q, (unsigned int)65535));
  if (status<0) return status;
  
  if (*q) return -2;  /* Junk follows port number */
  dynamic_allocation = FALSE;
  break;     /* Exit for loop before we skip the zero */
     } /*if*/
 
/* Get node name:
*/
 if (*p == 0) {
     sin->sin_addr.s_addr = INADDR_ANY; /* Default: any address */
 
 } else if (*p>='0' && *p<='9') {   /* Numeric node address: */
     sin->sin_addr.s_addr = inet_addr(p); /* See arpa/inet.h */
 
 } else {      /* Alphanumeric node name: */
     phost=gethostbyname(p); /* See netdb.h */
     if (!phost) {
  CTRACE(tfp, "IP: Can't find internet node name `%s'.\n",p);
  return inet_status("gethostbyname");  /* Fail? */
     }
     memcpy(&sin->sin_addr, phost->h_addr, phost->h_length);
 }
 
 CTRACE(tfp, 
     "TCP: Parsed address as port %4x, inet %d.%d.%d.%d\n",
      (unsigned int)ntohs(sin->sin_port),
      (int)*((unsigned char *)(&sin->sin_addr)+0),
      (int)*((unsigned char *)(&sin->sin_addr)+1),
      (int)*((unsigned char *)(&sin->sin_addr)+2),
      (int)*((unsigned char *)(&sin->sin_addr)+3));
 
    } /* scope of p */
 
 
/*  Master socket for server:
*/
    if (role == master) {
 
/*  Create internet socket
*/
 master_soc = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
   
 if (master_soc<0)
     return inet_status("socket");
      
 CTRACE(tfp, "IP: Opened socket number %d\n", master_soc);
 
/*  If the port number was not specified, then we search for a free one.
*/
 if (dynamic_allocation) {
     unsigned short try;
     for (try=FIRST_TCP_PORT; try<=LAST_TCP_PORT; try++) { 
  soc_address.sin_port = htons(try);
  if (bind(master_soc,
   (struct sockaddr*)&soc_address, /* Cast to generic sockaddr */
   sizeof(soc_address)) == 0)
      break;
  if (try == LAST_TCP_PORT)
      return inet_status("bind");
     }
     CTRACE(tfp, "IP:  Bound to port %u.\n",
      ntohs(soc_address.sin_port));
 } else {     /* Port was specified */
     if (bind(master_soc,
       (struct sockaddr*)&soc_address, /* Case to generic address */
       sizeof(soc_address))<0)
  return inet_status("bind");
 }
 if (listen(master_soc, LISTEN_BACKLOG)<0)
     return inet_status("listen");
 
 CTRACE(tfp, "TCP: Master socket(), bind() and listen() all OK\n");
 FD_SET(master_soc, &open_sockets);
 if ((master_soc+1) > num_sockets) num_sockets=master_soc+1;
 
 return master_soc;
    } /* if master */
    
    return -1;  /* unimplemented role */
 
} /* do_bind */
 
 
/*      Handle incomming messages     do_action()
** -------------------------
**
** On entry:
**
**      timeout         -1 for infinite, 0 for poll, else in units of 10ms
**
** On exit,
** returns  The status of the operation, <0 if failed.
**
*/
#ifdef __STDC__
PRIVATE int do_action(void)
#else
PRIVATE int do_action()
#endif
{
    int tcp_status;  /* <0 if error, in general */
    int timeout = -1;  /* No timeout required but code exists */
    for(;;) {
 
/*  If it's a master socket, then find a slave:
*/
     if (role == master) {
#ifdef SELECT
     fd_set  read_chans;
     fd_set  write_chans;
     fd_set  except_chans;
     int   nfound;     /* Number of ready channels */
     struct timeval max_wait;   /* timeout in form for select() */
    
     FD_ZERO(&write_chans);     /* Clear the write mask */
     FD_ZERO(&except_chans);     /* Clear the exception mask */
 
/*  If timeout is required, the timeout structure is set up. Otherwise
**  (timeout<0) a zero is passed instead of a pointer to the struct timeval.
*/
     if (timeout>=0) {
  max_wait.tv_sec = timeout/100;
  max_wait.tv_usec = (timeout%100)*10000;
     }
    
     for (com_soc=-1; com_soc<0;) { /* Loop while connections keep coming */
    
  
/*  The read mask expresses interest in the master channel for incomming
**  connections) or any slave channel (for incomming messages).
*/
 
/*  Wait for incoming connection or message
*/
         read_chans = open_sockets;     /* Read on all active channels */
  if (TRACE) printf(
 "TcpAccess: Waiting for connection or message. (Mask=%x hex, max=%x hex).\n", 
    *(int *)(&read_chans), num_sockets);
  nfound=select(num_sockets, &read_chans,
      &write_chans, &except_chans,
      timeout >= 0 ? &max_wait : 0);
 
  if (nfound<0) return inet_status("accept");
  if (nfound==0) return 0; /* Timeout */
 
/*  If an incomming connection has arrived, accept the new socket:
*/
  if (FD_ISSET(master_soc, &read_chans)) {
    
   CTRACE(tfp, "TCP: New incomming connection:\n");
   tcp_status = accept(master_soc,
     (struct sockaddr *)&soc_address,
     &soc_addrlen);
   if (tcp_status<0)
       return inet_status("accept");
   CTRACE(tfp, "TCP: Accepted new socket %d\n",
       tcp_status);
   FD_SET(tcp_status, &open_sockets);
   if ((tcp_status+1) > num_sockets) num_sockets=tcp_status+1;
   nfound--;
    
  } /* end if new connection */
    
/*  If a message has arrived on one of the channels, take that channel:
*/
  if(nfound) {
   int i;
   for(i=0;;i++)
       if(FD_ISSET(i, &read_chans)) {
    if (TRACE) printf("Got new socket %i\n", i);
    com_soc = i;  /* Got one! */
    break;
       }
   
   break;   /* Found input socket com_soc */
  } /* if message waiting */
    
     } /* loop on event */
 
#else /* SELECT not supported */
    
     if (com_soc<0) { /* No slaves: must accept */
      CTRACE(tfp, "TCP: Waiting for incomming connection...\n");
      tcp_status = accept(master_soc,
        &rsoc->mdp.soc_tcp.soc_address,
        &rsoc->mdp.soc_tcp.soc_addrlen);
      if (tcp_status<0)
   return inet_status("accept");
      com_soc = tcp_status; /* socket number */
      CTRACE(tfp, "TCP: Accepted socket %d\n", tcp_status);
     } /* end if no slaves */
    
#endif
 
 }  /* end if master */
 
 
/* com_soc is now valid for read */
 
/*  Read the message now on whatever channel there is:
*/
        CTRACE(tfp,"TCP: Reading socket %d\n", com_soc);
 tcp_status=handle(com_soc);
 
 if(tcp_status<=0) {    /* EOF or error */
     if (tcp_status<0) {    /* error */
         CTRACE(tfp, "TCP: Error %i handling incomming message (errno=%i).\n",
    tcp_status, errno);
         /* DONT return inet_status("netread");  error */
     } else {
  CTRACE(tfp, "TCP: Socket %d disconnected by peer\n",
      com_soc);
            }
     if (role==master) {
  NETCLOSE(com_soc);
  FD_CLR(com_soc, &open_sockets);
     } else
#ifdef VM
  return -69;
  ;
#else
  return -ECONNRESET;
#endif
 } /* end if handler got EOF or error */
    
    }; /* for loop */
/*NOTREACHED*/
} /* end do_action */
 
 
/*  Main program
**  ------------
*/
int main(int arc, char*argv[])
{
    int status;
    
    status = do_bind("*:2784");
    if (status<0) {
     printf("Bad setup.\n");
 exit(status);
    }
    
    status = do_action();
    if (status<0) {
     printf("Error in server loop.\n");
 exit(status);
    }
    
    exit(0);
    return 0;  /* For gcc */
}
  

2012-02-24

C++11におけるモダンなhas_xxxの実装

久しぶりにメタプログラミングをしようと思う。特に、has_xxxをC++11で書くことに挑戦してみる。has_xxxとは、ある型がネストされた名前を持っているかどうかを確認するメタ関数である。名前は、型、もしくは非型のどちらかになる。

まず、型の方から。

namespace detail {
    template < typename T, typename U = typename T::type >
    std::true_type check_type( int ) ;
    template < typename T >
    std::false_type check_type( long ) ;
}

template < typename T >
struct has_type
    : decltype( detail::check_type<T>( 0 ) )
{ } ;

なんと、たったのこれだけのコードで、UnaryTypeTrait要求を満たしたメタ関数が書ける。

次に非型の方。

namespace detail {
    template < typename T, decltype(&T::value) = &T::value >
    std::true_type check_value( int ) ;
    template < typename T >
    std::false_type check_value( long ) ;
}

template < typename T >
struct has_value
    : decltype( detail::check_value<T>( 0 ) )
{ } ;

これまた非常に簡単だ。もちろん、ちゃんとUnaryTypeTrait要求を満たしたメタ関数である。例えば以下のように使う。

struct Yes
{
    using type = int ;
    static constexpr int value = 0 ;
} ;

struct Yes_for_value
{
    void value() { } // 非型には関数名も含まれる
} ;

struct No { } ;

int main()
{
    has_type< Yes >::value ; // true
    has_value< Yes >::value ; // true
    has_type< Yes_for_value >::value ; // false
    has_value< Yes_for_value >::value ; // true
    has_value< int >::value ; // false
}

何故こんなに簡単になったのかというと、decltypeの存在が大きい。C++11以前では、sizeofを利用した変なコードを書かなければならなかったのだが、C++11では、decltypeで式の型を直接得られるため、std::true_typeやstd::false_typeを返す関数を書き、decltypeの中で関数呼び出し式を書き、そのdecltypeをそのままベースクラスとして書くだけでよくなったのだ。

しかし、これだけ簡単になってしまうと、昔が懐かしく感じる。もはや、C++のメタプログラミングは、規格をつつきまわさずとも、だれにでも書けるほど簡単になったのだ。

ちなみに、ある型に、ネストされた型名xxxがあるかどうかというメタ関数は、標準ライブラリの実装に必要である。たとえば、std::allocator_traitsなどで必要になる。

追記、SFINAEをテンプレートパラメーターで行うように修正。また、リファレンス型にも対応。ellipsisではなくlongを使うように変更。

追記:int型の引数を渡している理由は、オーバーロード解決を行わせるためである。typename U = typename T::typeというデフォルト実引数付きのテンプレート仮引数で、SFINAEのトリックを使っている。もし、型Tにネストされた型名typeがなければ、つまり、substitionが失敗すれば、そのテンプレートは候補関数にはならない。ただし、substitution自体はオーバーロード解決には影響しないので、別の方法でオーバーロードの優先度を指定する必要がある。そのため、実引数にint型の0を渡し、仮引数にint型と、...を指定して、オーバーロード解決を行わせている。これは、オーバーロード解決でより優先されれば何でもいい。だから、intとlongでもよい。

どうもエイリアステンプレートってうまい使い方が思いつかない

C++11では、エイリアス宣言のテンプレート化ができるようになったが、どうもうまい使い方が思いつかない。無論、私の言う「うまい使い方」とは、世間とは少し離れている。

唯一思いつくものとしては、メタ関数の呼び出しにnested typeが必要なくなるというものだ。

template < typename T >
using add_pointer = typename std::add_pointer< T >::type ;

template < typename T >
void f( T t )
{
    using namespace std ; 
    // めんどくさい
    typename add_pointer< T >::type a = nullptr ;
    // 簡単
    add_pointer< T > b = nullptr ;
}

エイリアステンプレートを使えば、nested typeを書かなくてよくなるばかりか、typenameすら必要なくなる。なぜならば、エイリアス宣言は文脈上、必ず型であるので、typenameを明示的に書く必要がないからだ。

譲渡権とインターネット

著作権を考えていて、ふとデジタルデータと譲渡権は実情に合っていないのではないかと思った。譲渡権とは、著作権法、第二十六条の二に規定されている。著作者は、譲渡権を専有する。つまり、他人の著作物を勝手に譲渡してはいけない。

しかし、もし、私が紙の本を1000円で買えば、その本は古本屋に売ることができるし、他人に渡すこともできる。これは著作者もしくはその承諾を得たものから譲渡された複製物には、第二十六条の二が適用されないためである。

この例では、私は1000円を支払うことによって、著作物の複製物である紙の本を、正規の方法で入手したわけである。そのため、私の所有している本には、譲渡権は及ばない。譲渡権が及ばないのだから、古本屋に売ったり、他人に譲り渡したりできる。別に、金銭が関わる必要はない。著作権者から、あるいはその承諾を得ている者から著作物の複製物を入手した場合、それには譲渡権が及ばないということである。

これを現代のデジタルデータの頒布方法のひとつ、つまりインターネットに当てはめると、私の見解では、非常に不思議なことになる。

今、私が著作者の承諾を得ている方法でサーバーから著作物のデジタルデータをダウンロードしたとする。ということは、ダウンロードした著作物であるこのデジタルデータには、譲渡権が及ばないことになる。私はこのデジタルデータを、紙の本と同じく、他人に売り払ったり、他人に譲り渡したりできるはずである。したがって、10回ダウンロードしたならば、10回譲渡できるはずである。

はて、おかしい。何か解釈を間違えているのだろうか。

2012-02-22

今日の猫動画

ナデナデを催促する猫。

2012-02-21

Linuxに移行しようかな

もはや、WindowsでC++を続けていくのが難しくなってきた。まともなC++コンパイラーは、*unix系の環境では簡単に手に入るが、Windowsでは困難だ。GCCをWindows上でビルドする方法は理解出来ないし、LLVMのWindowsサポートは存在しないも同義だ。Windows上でgitを使うのも嫌になる。

それにしても、どうしてこうなってしまったんだろう。WindowsはどんどんC++から離れて行っている。MSの最近のC++における態度たるや、互換性という理由だけで規格違反の挙動を放置し、さらに独自拡張を大量に突っ込んでカオスになっている。もはや、WindowsではまともにC++できない。

Linuxで一つ懸念があるとすれば、IMEだろう。mozcはあるが、WindowsやMac向けのWebデータから生成された辞書は含まれていないらしい。つまり、「ただし魔法は尻から出る」とか、「覇王翔吼拳を使わざるを得ない」などの予測変換や、ネット上でミームとなっている流行語は変換できないのだろう。

しかし、その手の流行語はほとんど名詞なのだから、例えば、Wikipediaとかニコニコ大百科とか、そういうところからクロールして生成した辞書を突っ込めばいいのではないだろうか。調べてみたら、やはりそういう風に生成された辞書は存在する。

IME以外は、問題にならないはずだ。ただし、使いやすいエディタはどうだろう。世間では、viとemacsのどちらが優れているかどうかで宗教戦争が起こるそうだが、私はコマンドモードと入力モードが切り替わるようなエディタには馴染めない。

ユーザー定義リテラルの応用

本の虫: ユーザー定義リテラルのすべて
本の虫: ユーザー定義リテラル補足

先日、ユーザー定義リテラルについて全てを解説した。すでに、十分実験に耐えるコンパイラーもある。しかし、どうもユーザー定義リテラルは使われていない。そこで、ユーザー定義リテラルの活用法を考えて見ることにした。

自作クラスのリテラル

まず最も簡単に思いつくのは、自作クラスのリテラルを作ることだ。たとえば、任意の精度の演算を実現する、架空の整数クラス、bigintを考える。

bigint x("10854312124826591996706630") ;
bigint y("9809954364263402890285234523") ;
bigint z = x + y ; 

変数というのは素晴らしいものだが、やはり我々は、時には直接ハードコードした値を書きたいものである。これを従来の関数とユーザー定義リテラルで実現すると、どうなるだろうか。まずは宣言から。

// 従来の関数
bigint to_bi( std::string val ) ;

// ユーザー定義リテラル
bigint operator "" _bi( char const * str ) ;

ユーザー定義リテラルの宣言は少し見慣れぬが、まあ、これはライブラリ側の実装なので、多少汚くても問題はない。では、ユーザー側はどうか。

// キャスト記法
bigint x = bigint("10854312124826591996706630") + bigint("9809954364263402890285234523") ;

// 従来の関数
bigint x = to_bi("10854312124826591996706630") + to_bi("9809954364263402890285234523") ;

// ユーザー定義リテラル
bigint x = 10854312124826591996706630_bi + 9809954364263402890285234523_bi ;

微妙なところだ。結局、unsigned long long intで表現できない精度の整数は、文字列という形でわたさなければならない。キャスト記法や従来の関数に比べて、括弧を記述しなくても良くなった。しかし、やはりダブルクオートを記述しなくてはならない。読みやすくはなっていると思うた。

単位系の構築

我々は様々な単位を使う。例えば、長さは、メートルが基準だが、キロメートルやセンチメートルなどといった単位も使う。これをいちいち変換するのは面倒だから、ユーザー定義リテラルで自動的に変換させるというのはどうか。

constexpr long double operator "" _km ( long double value ) { return value * 1000.0L ; }
constexpr long double operator "" _m ( long double value ) { return value ; }
constexpr long double operator "" _cm ( long double value ) { return value / 100.0L ; }
constexpr long double operator "" _mm ( long double value ) { return value / 1000.0L ; }

constexpr long double d = 1.0_km + 500.0_m ;

ユーザー定義リテラルはconstexprにできるので、定数もバッチリだ。

constexprを外した上で実行時チェックを行うだとか、long doubleではなくクラスなどを返し、たとえば長さと重さの加算を禁止したり、あるいは逆に、別の単位の演算、例えば、長さ割る時間が速度クラスを返したりなどと、応用することもできる。

メタプログラミングのタグ

メタプログラミングでは、実行時オブジェクトをタグとして使うこともある。これは、オブジェクトではなく、そのオブジェクトの型を利用している。たとえば、std::bindだ。

void f( int x, int y ) { }

int main()
{
    using namespace std::placeholders ;
    auto func = std::bind( f, _2, _1 ) ;
    func( 1, 2 ) ; // f( 2, 1 )として呼ばれる
}

ここで使っている、_1, _2というのは、std::placeholders名前空間で宣言されているオブジェクトである。このオブジェクトどういうものなのかということは、ユーザーは気にする必要がない。実際のところ、実装ですらオブジェクト自体は気にしない。_1, _2の具体的な型は未規定であるが、重要なことは、実装は_1, _2の型を区別することができるようになっている。そのため、ユーザーが何番目の引数にどれを渡したかということを判断でき、operator ()を呼び出した時に渡された実引数を、実際の関数呼び出しの際に、正しくマップすることができる。

そう、型さえ違っていれば、何でもいい。だから、例えばこういう実装でもいいわけだ。

// 実装の一例
// 規格上、型は未規定であり、実装によって都合のいい型が使われる。
namespace std { namespace placeholders {
    template < unsigned long long int > struct holder { } ;
    extern holder<1> _1 ;
    extern holder<2> _2 ;
    // ...
} }

ということは、ユーザー定義リテラルの1_とか2_に対して、対応する型を返してやればいいということになる。しかし、これが結構厄介だ。というのも、通常の関数は使えないからである。通常の関数の戻り値の型は、宣言した時点で決まっている。ユーザーが使った時点で型を決定するには、インスタンス化が必要である。インスタンス化のためには、テンプレートを使わなければならない。では、テンプレートは・・・。

ユーザー定義リテラルのテンプレートは、C++の文法マニアでなければ使いこなせない。しかし、言語マニアとプログラマーとして優れているかどうかは、別問題だ。無論、本物のプログラマーは、使っているプログラミング言語の文法を理解するべきである。しかし、大多数のプログラマーは、言語マニアとなるべきではない。普通のプログラマーは本物の問題を解決するべきなのだ。

偉そうな話はこの辺でやめて、具体的に言おう。問題が多すぎるのだ。ユーザー定義リテラルのオーバーロード演算子のテンプレートは、整数リテラルと浮動小数点数リテラルを区別しないし、その中身も区別しない。

template < char ... Chars >
std::string operator "" _to_string( )
{
    std::string buf ;
    for ( auto c : std::initializer_list<char>{ Chars... } )
    {
        buf.push_back( c ) ;
    }
    return buf ;
}

int main()
{
    std::cout << 0xabcdefABCDEF_to_string << std::endl ;
    std::cout << 01234567_to_string << std::endl ;
    std::cout << 3.141592_to_string << std::endl ;
    std::cout << 123e456_to_string << std::endl ;
}

当然、16進数リテラルや8進数リテラルも、プレフィクスまで含めて文字としてテンプレート実引数に渡されるし、同じ関数テンプレートに、浮動小数点数リテラルも渡される。これを防ぐ方法はない。だから、std::bindに使うとすれば、ユーザー側に10進数整数リテラルのみ使うよう申し送るか、あるいは自前でエラーチェックをするか。

もちろん、戻り値の型を指定するには、テンプレートメタプログラミングが必要だ。

しかも、関数テンプレートに渡されるのは、Variadic Templatesのテンプレート実引数としてのcharの塊である。何とかしてこれを数値に変換しなければならない。atoiは、プログラマーなら誰でも一度ぐらいは実装したことがあるだろう。しかし、テンプレートメタプログラミングやconstexpr関数でatoiを書いた人間はどれだけいるだろうか。std::tupleを使えば、ランダムアクセスは可能である。しかしループはどうしようもない。

もちろん、実装は可能である。再帰はプログラミングの初歩に学ぶことだ。ただ、ちょっとめんどくさいだけだ。試しに、gccのstd::bindの実装で動くものを書いてみたが、コードが無駄に長くなったのでここには貼らない。私が書けたのだから、誰にだって書けるはずだ。

しかし、std::bindの場合、そこまでして_1を1_にする積極的な理由がないのだ。現実的に使われる関数の引数の数などたかが知れている。千個も二千個も引数を渡したりしない。ライブラリ実装者からも、ユーザーからも、あまり利点のない機能である。それに、1_とやって返されるのは、値である。型ではない。関数の実引数としてのタグ以外の、汎用的なテンプレートメタプログラミング用途で使うには、decltypeを利用して、型にしなければならない。しかし、decltype(1_)と書くなら、本来の目的が損なわれてしまう。これだけのためにこんな苦労をするのは馬鹿げている。しかも、浮動小数点数リテラルを間違えて与えてしまう場合もあるのだ。従来のテンプレート、つまり、std::integral_constant<int, 1>のような指定なら、そんな問題は起こらないし、必要ならばconstexpr関数テンプレートを使って、to_int<1>()という形にすることも可能だ。この場合、ちゃんと非型テンプレートとして渡されるので、ユーザー定義リテラルのように文字の塊として処理する必要もない。本末転倒だ。

私はユーザー定義リテラルが嫌いだ。ユーザー定義リテラルなしでは極端に冗長なコードになるということもない。見た目には簡単なコードである。しかし、ユーザー定義リテラルを正しく実装するには、C++11の文法を本当に理解していなければならない。しかし、大多数のプログラマーは、言語の文法を表面上でしか理解していないのだ。普通は、それで十分なのだ。

2012-02-19

Chromeが起動時に三つのランダムなドメインに接続しようとする理由

“Chrome connects to three random domains at startup.” — Mike West

Chromeを起動した際、http://aghepodlln/とかhttp://lkhjasdnpr/のようなランダムなドメインへの接続を試みる。何でこんなコトをしているのかという見当はずれの推測が、いくつか出回っている。事実としては、この挙動は必要なのだ。以下の説明で、この疑問を晴らす。

このような接続要求の目的は、現在使用しているネットワークが、存在しないホスト名への接続要求を検知して勝手にリダイレクトするかどうかを判定するものである。例えば、少なからぬISPが、http://text/のようなDNSルックアップの失敗に対し、http://your.helpful.isp/search?q=text(あなたの親切なISP)へリダイレクトしている。この「親切」な挙動の可否はさておくとして、この挙動はChromeにとって問題を引き起こすのだ。特に、Omniboxで使われている、ユーザーの入力がキーワード検索なのか非標準のドメイン名へのアクセスなのかを判定するヒューリスティックを阻害するのだ。

問題の1つとして、Googleの内部ネットワークはいい例となる。Google内部のgoというショートリンクサービスは、共有しやすい簡潔なリンクを提供している。今、"go"とChromeのOmniboxにタイプしてエンターキーを押した時、それはhttp://go/へのアクセスを意味するのか、あるいは"Go"を検索したいのか曖昧である(ところで、Goは面白いプログラミング言語である)。Chromeはできるだけ「正しいこと™」をするため、検索を実行しつつ、裏で、ひょっとしたらドメインかもしれないこの入力に対してHEADリクエストも発行する。サーバーが応答したならば、グーグルはinfobarを表示して、もしかしてhttp://go/を意図していたのかどうかを尋ねる。そして、ユーザーの回答を、将来のために覚えておくのだ。

お分かりのように、この機能は、ISPがそのようなリクエストを補足している場合、ぶっ壊れてしまう。どんな一単語のキーワードに対しても、infobarが表示されてしまうのだ。だから、Chromeは起動時とIPアドレスの変更時にチェックを行なっているのだ。ところで、Chromeは美しくもオープンソースであるので、この実装を確認してみるとしよう。

まず読むべきなのはIntranetRedirectDetectorだ。Chromeが起動した時、IntranetRedirectorDetectorのオブジェクトを生成する。これは短時間のディレイ(現在、7秒にハードコートされている)を設けて、起動時の重要な処理を妨害しないようにし、その後、IntranetRedirectDetector::FinishSleep()を呼び出して実際の処理をはじめる。このメソッドはランダムなドメイン名を三つ生成して、それぞれのドメイン名に対して非同期HEADリクエストを、キャッシュの生成とcookieの保存を行わないようにした上で、発行する。リクエストが完了したならば、IntranetRedirectDetector::OnURLFetchComplete() が呼ばれ、結果を記録する。もし、三つのリクエストのうち、いずれか二つが同じホストに名前解決された場合、そのホストは、ネットワークの"redirect origin"として記録される。簡単だ。

この情報は、AlternateNavURLFetcherで、Omniboxによる検索に対しinfobarを表示するべきかどうかという判断に使われる。もし、HEADリクエストがChrome起動時に得たredirect originと同じサイトを返した場合、無視される。もし、別物であれば、便利なinfobarの出番だ。

とまあ、こんなところだ。Chromeは起動時に三つのランダムなドメイン名に対するリクエストを発行することによって、Omniboxのヒューリスティックがユーザーの意図に添うようにしている。このリクエストは、ユーザーの個人情報を悪意ある目的でどこかに送信しているわけではないし、トラッキング用でもない。crbug.com/18942を修正するために必要なリクエストであり、他意はない。

以上、Chromeが起動時にランダムなドメインにアクセスを試みる理由の説明。それにしても、そんな馬鹿なことをするISPを糾弾するべきだ。ドメイン名が解決できないならば、解決できないと返すべきなのだ。DNS名前解決をハイジャックすべきではない。

新たな期待できるC++インタプリター、Cling

Cling | ROOT

llvmとそのフロントエンドであるclangを利用したC++インタプリターがROOTから登場した。その名もCling。

ROOTといえば、あのCINTを提供していた有名なROOTである。CINTは、インタプリターにしてはよくやっていた方であるが、いかんせん、古すぎるし、規格準拠度も低い。しかし、Clingではclangを利用している。つまり、高い規格互換性が期待できる。これはすばらしい。

2011年にはすでにアナウンスされていたとのことだが、全然気が付かなかった。

2012-02-18

なぜアメリカは自国の著作権法を世界に押し付けようとしているのか

最近、アメリカが自国の著作権法を世界に押し付けようとやっきになっている。何故だろうかと考えてみたところ、その理由が分かった。このままでは、アメリカの一人負けになってしまうからだ。

疑問は、チャップリンから始まった。チャップリンの後期の作品のいくつかは、まだ日本国内でも保護されているらしい。これには、様々な要素が関わってくる。主な理由としては、旧法と現行法で保護期間の長いほうが優先されるということと、チャップリンの作品はチャップリン個人の著作であるということと、戦時加算らしい。

しかし、チャップリンの多くの作品の著作権は、作品が発表された国、米国内ではすでに消失しているはずではなかったか。ベルヌ条約の相互主義はどうなったのか。短いほうが適用されるはずではないのか。この疑問を解消しようと調べたところ、これが厄介なのである。

アメリカはベルヌ条約の加盟に手こずった国である。何しろ、アメリカの著作権法とベルヌ条約は互換性がない上、法律を大幅に改正する気もなかったからだ。そのために、別の条約、万国著作権条約が作られた。その後、アメリカもベルヌ条約に加盟した。問題は、ベルヌ条約の条文には、アメリカ国内では法による制定がない限りベルヌ条約が適用されないという文面があるのだ。だから、アメリカ国内では、対応する国内法を制定しない限り、ベルヌ条約は効力を持たない。もちろん、これはアメリカ国外には関係がない。両国がベルヌ条約や万国著作権条約に加盟している限り、米国内で発行された著作物は、国外では条約に従う。

しかし、アメリカの条約加盟前に発表された作品では、そもそも条約がなかったのだから、条約の効力は及ばない。つまり、万国著作権条約加入前の1956年4月28日以前のアメリカの作品には、日本で相互主義は適用されないのである。しかし、それ以降には、相互主義が適用される。チャップリンの著作権問題とは、要するにこういう法の合間を利用した問題だったのだろう。しかし、映画がチャップリン個人の作品というのには、未だに疑問だが。

しかし、アメリカ国内では、未だに相互主義を採用していない。

これは、アメリカの昔の作品を保護するには、とても便利なのだが、今の作品を保護するには、どうも不利であるように思う。

今、ある団体もしくは企業が映画以外の著作物を発表したならば、その保護期間は、日本国内では発表後50年である。これは、著作物の発表が日本であろうとアメリカであろうと同じだ。日本国内では50年だ。しかし、アメリカでは公表後95年もしくは創作後120年のうちのどちらか短い方が適用される。

つまり、アメリカが相互主義を採用していないがために、日本国内で発表された著作物の著作権が、50年たって日本国内で消失したとしても、米国内では依然として保護される。にもかかわらず、米国内で発表された著作物は、50年経てば日本国内では消失している。映画でも、日本の70年に対し、アメリカは95年だ(映画の創作に25年以上かかることはあるまい)。これは、日本とアメリカはベルヌ条約と万国著作権条約に署名しているので、ベルヌ条約に変な例外のない日本では相互主義が働くが、アメリカ国内では国内法による定めがないために相互主義が働かないためである。

なるほど、ベルヌ条約の相互主義を勘違いしているアメリカ人とよく出会うのは、このためだったのか。そりゃ、アメリカ国内からみれば、相互主義は自国の長い保護期間を優先するようにみえる。実際は、そもそもアメリカ国内では相互主義が働かないだけなのに。

このまま現状を放置すると、今の著作権が消失し始める頃、アメリカ合衆国とそれ以外のほとんどの国で、非常にアメリカ側に不利な保護期間の差がでてくる。

アメリカがACTAやTPPのような条約を猛烈にプッシュして、アメリカ国内の腐った著作権法を世界に押し付けようとしているのは、こういう理由もあるのかもしれぬ。しかし、これはアメリカの自業自得ではないか。アメリカが本当に行うべきなのは、国内法を改正してベルヌ条約の相互主義を取り入れることだ。そうしなければ、相互主義が相互にならず、外国のみに適用されるので、アメリカにとって不平等条約になってしまうからだ。しかし何故かアメリカは、そういう本来ベルヌ条約加盟の際におこなっておくべきことはいつまでたってもやらず、不平等な国内法を改正せず、自国の著作権法を他国にも押し付けようとしている。

指輪物語の翻訳権ってもしかして切れてる?

J.R.R.TolkienのThe Lord of the Ringsは、1954年から1955年にかけて発表された。一方、日本語の最初の翻訳である指輪物語、1972年に発表されている。

イギリスと平和条約を締結したのは1952年1月3日なので、戦時加算はない。The Hobbitの発表は1937年なので戦時加算があるが、The Lord of the Ringsには戦時加算がない。1970年以前に発表されている著作物なので、いわゆる10年留保で、公開から10年以内に翻訳が出ない場合、翻訳権が消失するはずである。1965年に日本語訳は出ていない。

ということは、指輪物語は、いま日本国内では自由に翻訳していいのだろうか。

The Hobbitの翻訳権がどうなるのかは気になるところである。もともと10年留保で1947年に切れるはずだった翻訳権が約十年+翻訳権の六ヶ月だけ延長されるのだろうか。だとしても、ホビットの冒険は1965年に発表されているので、やはり翻訳権は切れている。

ちなみに、トールキンの没年が1973年なので、いずれにせよ日本国内では、2024年に著作権が切れる。あとたったの12年だ。

雪のおもしろう降りたりし朝

今夜は雪がおもしろう積りたるいとをかし。

京都に来て、始めて、「雪のおもしろう降りたりし朝」という言葉の意味がわかった。京都の雪は美しいのだ。人を煩わすほど積もるのは稀だし、寒さといっても、それほどでもない。ただ、かろうじて地面を白くするのに十分な程度降る。京都では、雪はおもしろい。

2012-02-16

ユーザー定義リテラル補足

前回、ユーザー定義リテラルのすべてを説明したつもりであったが、いくつか細かい漏れがあった。

まず、ユーザー定義リテラルの宣言であるが、実は文法上、空白文字が必要な箇所がある。""と識別子の間だ

int operator "" /*ここに少なくともひとつの空白文字が必要*/ _identifier( unsigned long long int ) ;

空白文字を入れないと、""_identiferがひとつのトークンだとみなされてしまう。

もちろん、この宣言を参照する際にも、空白文字が必要である。

operator "" /*空白文字が必要*/ _identifer( 123ULL ) ;

また、ユーザー定義リテラルのオーバーロードは、あらかじめ定められた仮引数リストやテンプレートしか使えないが、その他は、普通の関数と変わりないということである。もちろん、inlineやconstexprで宣言することもできる。当然、内部リンケージを持つ可能性もある。

inilne unsigned long long int operator "" _inline_udl( unsigned long long int value ) { return value ; }
constexpr unsigned long long int operator "" _constexpr_udl( unsigned long long int value ) { return value ; }

もちろん、アドレスだって取得できる。

unsigned long long int operator "" _udl( unsigned long long int value ) { return value ; }

int main()
{
    using udl_pointer_type = unsigned long long int (*)( unsigned long long int ) ;
    // アドレスの取得
    udl_pointer_type p = &operator "" _udl ;
}

また、当然ながら、名前空間スコープ内に宣言される。名前解決も通常通りである。

namespace foo
{
unsigned long long int operator "" _udl( unsigned long long int value ) { return value ; }
}


int main()
{
    123_udl ; // エラー
    {
        using namespace foo ;
        123_udl ; // OK
    }

    {
        using foo::operator "" _udl ;
        123_udl ; // OK
    }
}

もちろん、オーバーロード解決も通常通りである。

void operator "" _udl( unsigned long long int value ) {  } // #1
void operator "" _udl( long double value ) { } // #2

int main()
{
    operator "" _udl( 123ULL ) ; // #1
    operator "" _udl( 1.23L ) ; // #2
}

また、文字列やテンプレート実引数で受け取る場合のオーバーロードの優先順位は、

  1. 整数リテラルの場合、仮引数unsigned long long int、浮動小数点数リテラルの場合、long double
  2. それが見つからない場合、char const *
  3. それが見つからない場合、テンプレート実引数

となっている。したがって、

void operator "" _u1( unsigned long long int value ) {  } // #1
void operator "" _u1( char const * ) { } // #2

void operator "" _u2( char const * ) {  } // #3
template < char ... Types >
void operator "" _u2( ) { } // #4

int main()
{
    123_u1 ; // #1
    123_u2 ; // #3
}

このようにオーバーロード解決される。

つまり、いくつかの制限と例外的なルールを除けば、およそ関数に適用されるルールは、ユーザー定義リテラルのオーバーロードにも適用される。

ユーザー定義リテラルのすべて

C++11にはユーザー定義リテラルというものがある。私はこの機能が嫌いだ。しかし、どうやらgcc 4.7がある程度の実装を終えたらしい。日本一C++に詳しい男を自称する私としては、試さなくてはならない。そこで、この記事を書く。この記事を読めば、今日から君もユーザー定義リテラルをバリバリに使えるようになる。

まず、C++11では、非常に不思議な理由により、ユーザー定義リテラルは、演算子のオーバーロードという形で実装する。はて、リテラルにつくサフィックスは演算子なのだろうか。それはともかく、ユーザー定義リテラルの識別子には、一言注意が必要である。ユーザー定義リテラルの識別子は、必ずアンダースコアからはじめなければならない。

私が注意深く、「識別子」と書いているのには訳がある。ユーザー定義リテラルの識別子は、「名前」ではないからだ。この詳細を理解する必要はない。重要なことは、必ず「識別子」をアンダースコアからはじめなければならないということだ。ちなみに、通常の「名前」では、アンダースコアから始まる「名前」はグローバル名前空間において予約されているので、使ってはいけないし、アンダースコアに大文字から始まる「名前」はあらゆる箇所で予約語なので使ってはいけない。ふたつの連続しているアンダースコアを含む「名前」も予約語である。「識別子」だからこそ、許される所業である。詳しく説明すると、「名前」は「エンティティ」か「ラベル」を意味する「識別子」でなければならないからだ。これはC++プログラマーにとっては一般常識である。

前置きが長くなった。さっそくユーザー定義リテラルを使ってみよう。

#include <iostream>

// 整数の実引数を二倍にして返すユーザー定義リテラル
unsigned long long int operator "" _twice ( unsigned long long int value )
{
    return value * 2 ;
}

int main()
{
    std::cout << 1234_twice << std::endl ;
}

結果として、2468と出力されるはずだ。まあ、とりあえずこれで、ユーザー定義リテラルというものが何かは分かったはずだ。

ユーザー定義リテラルは、

戻り値の型 operator "" _identifer (仮引数リスト)

という形で宣言する。使う際には、

リテラル _identifer

となる。一応、明示的に呼び出すこともできる。もっとも、明示的に呼び出すぐらいなら、普通の人は普通の関数を使うだろうが。

operator "" _identifer() ;

その他は、通常の関数のように使える。中でどのような処理をしてもいいし、戻り値の型は自由だ。

ユーザー定義リテラルには、大きく分けて四種類ある。ユーザー定義整数リテラル、ユーザー定義浮動小数点数リテラル、ユーザー定義文字列リテラル、ユーザー定義文字リテラルである。

ユーザー定義整数リテラル

ユーザー定義整数リテラルを整数型の値として受け取るには、仮引数の型がunsigned long long intでなければならない。もし、単項マイナス演算子を使ったとしたならば、それはユーザー定義リテラルの戻り値に対して適用される。ユーザー定義リテラルに渡される値は、常にunsigned long long intである。

ユーザー定義浮動小数点数リテラル

ユーザー定義浮動小数点数リテラルを浮動小数点数型の値として受け取るには、仮引数の型がlong doubleでなければならない。

long double operator "" _udl( long double value )
{ 
    return value ;
}

int main()
{
    3.14_udl ;
}

浮動小数点数の場合も、負数は受け取れない。単項マイナス演算子は、ユーザー定義リテラルの評価の結果に適用される。

ユーザー定義整数リテラルとユーザー定義浮動小数点数リテラルをその他の方法で受け取る方法

この整数リテラルと浮動小数点数リテラルのユーザー定義リテラルは、別の方法で受け取ることもできる。文字列と、テンプレート実引数である。

文字列として受け取る場合、char const *を使う。

// 単にstd::string型として返す
std::string operator "" _to_string( char const * s )
{
    return std::string( s ) ; 
}

int main()
{
    // operator "" _to_string("1234")
    std::string s = 1234_to_string ;
    // operator "" _to_string("1.234")
    s = 1.234_to_string ;
}

何のひねりもない単純なコードだが、みれば分かるだろう。整数リテラルを、あたかも""で囲んで通常文字列リテラルにして、そのまま実引数に渡したかのように、ユーザー定義リテラルのオーバーロード関数に渡される。

テンプレート実引数として受け取る場合は、仮引数を取らない。Variadic Templateを使って実装する。

// 単に結合してstd::string型として返す
template < char ... Chars >
std::string operator "" _to_string( )
{
    std::string buf ;
    for ( auto c : std::initializer_list<char>{ Chars... } )
    {
        buf.push_back( c ) ;
    }
    return buf ; 
}

int main()
{
    // operator "" _to_string<'1', '2', '3', '4'>()
    std::string s = 1234_to_string ;
    // operator "" _to_string<'1', '.', '2', '3', '4'>()
    s = 1.234_to_string ;
}

これまた、なんのひねりもない単純なコードだが、みれば分かるだろう。整数リテラルと浮動小数点数リテラルの、各文字がそれぞれ、<'1', '2', '3', '4'>という形で、テンプレート実引数として渡される。

すでに説明したので、いまさら言うまでもないと思うが、この文字列とテンプレート実引数で受け取る機能は、整数リテラルと浮動小数点数リテラル限定である。この次に出てくる文字列リテラルと文字リテラルには使えない。もっとも、聡明な読者には余計なおせっかいであろう。失礼失礼。

ユーザー定義文字列リテラル。

C++11では、5種類の文字列がある。このうち、通常の文字列リテラルとUTF-8文字列リテラルは、型を共有しているので、おなじ仮引数で受け取る。

// 通常の文字列リテラルとUTF-8文字列リテラル
void operator "" _udl( char const * str, std::size_t size ) { }
// ワイド文字列リテラル
void operator "" _udl( wchar_t const * str, std::size_t size ) { }
// char16_t文字列リテラル
void operator "" _udl( char16_t const * str, std::size_t size ) { }
// char32_t文字列リテラル
void operator "" _udl( char32_t const * str, std::size_t size ) { }

int main()
{
    // operator "" _udl("test", 4)
    "test"_udl ;
    // operator "" _udl(u8"test", 4)
    u8"test"_udl ;
    // operator "" _udl(L"test", 4)
    L"test"_udl ;
    // operator "" _udl(u"test", 4)
    u"test"_udl ;
    // operator "" _udl(U"test", 4)
    U"test"_udl ;
}

戻り値の型がvoidという、何の意味もないコードだが、あくまで例示のためである。第二引数は、NULL文字を除く文字数を格納している。第一引数はちゃんとNULL終端されているので安心して欲しい。

また老婆心で忠告するが、char *という仮引数リストは、ユーザー定義文字列リテラルのためにあるのではない。整数リテラルと浮動小数点数リテラルを文字列として受ける場合の仮引数リストである。文字列リテラルには使えない。

とても大事な事なのでもう一度ぐらい忠告しても許されると思うから言っておくが、Variadic Templateは、整数リテラルと浮動小数点数リテラルを受け取る場合の宣言である。文字列リテラルには使えない。

ユーザー定義文字リテラル

文字列リテラルの場合とほとんど変わりない。

// 通常の文字リテラルとUTF-8文字リテラル
void operator "" _udl( char c ) { }
// ワイド文字リテラル
void operator "" _udl( wchar_t c ) { }
// char16_t文字リテラル
void operator "" _udl( char16_t c ) { }
// char32_t文字リテラル
void operator "" _udl( char32_t c ) { }

int main()
{
    // operator "" _udl("a")
    "a"_udl ;
    // operator "" _udl(u8"a")
    u8"a"_udl ;
    // operator "" _udl(L"a")
    L"a"_udl ;
    // operator "" _udl(u"a")
    u"a"_udl ;
    // operator "" _udl(U"a")
    U"a"_udl ;
}

ユーザー定義リテラルとは、これで全てである。ユーザー定義リテラルは、戻り値の型や識別子を除いては、上記のいずれかの宣言でなければならない。これ以外の仮引数リストやテンプレートは使えない。例えば、以下のようなコードはエラーとなる。宣言すらできない。

// エラー、この仮引数リストは不正
void operator "" _udl ( int ) ;

// エラー、通常の関数でなければならない
template < typename T >
void operator "" _udl ( T ) ;

// これもエラー、Variadic Templatesでなければならない。
template < char c1, char c2, char c3, char c4  >
void operator "" _udl () ;

これでユーザー定義リテラルのすべてを解説した。ところで、私はユーザー定義リテラルが嫌いである。

2012-02-15

URLに著作権を主張したマヌケな会社

URLは著作物ではない。もし、コンテンツを保護したければ、アクセスには認証を設けるべきである。鍵のかかっていない家といえども盗みを働くのは違法だ。しかし、公共の場で勝手に音楽をたれ流しておいて、聞いたものを著作権侵害で訴えるのは馬鹿げている。課金ユーザーにのみ音楽を聞かせたいのであれば、最初から公共の場で流すべきではないのだ。

しかし、この通りのマヌケな事件が、すでに自由過ぎる国アメリカで起こっていたようだ。

Can You Copyright a Publicly Accessible URL? | Gear Diary
MobiTV tries (and fails) to censor Internet | Surveillance State - CNET News

MobiTVは携帯向けに動画の課金ストリーミングを提供しているWebサイトである。ところが、このサービスには問題があった。URLさえ知っていれば、誰でも有料コンテンツにアクセスできるという、セキュリティ上マヌケな問題があった。これを受けて、あるフォーラムにそのURLを書き込んだ者がいた。MobiTVはそのフォーラムに対し、DMCAによる取り下げ要求を出した。結果、大いに叩かれた。当たり前だ。MobiTVがすべきなのは、URLへのアクセスに認証をかけることである。秘密のURLは正しいセキュリティではない。単なる偽装である。

さらに悪いことに、そのフォーラムとやらは、HowardForumsだったのである。これは携帯ユーザー御用達のフォーラムである。結果として、MobiTVは大恥をかいた。

URLに著作権を主張した例がないかどうか調べていて発見し、面白かったので書いた次第。

2012-02-14

Gumroadを使ってみた感想

さっそく、Gumroadを使ってみた。例えば以下のようになる。

RFC 2606で規定されている例示用に予約されたセカンドレベルドメイン名情報の購入

上記のリンクから、RFC 2606で規定されている例示用に予約されたセカンドレベルドメイン名の情報を購入することができる。このドメイン名は、IANAが保持しており、ドキュメント内にドメイン名の例が必要になった際に、安心して記述できるので、万人におすすめだ。

と、冗談はさておき、Gumroadは、本当に極限までシンプルにしたサービスであると感じた。機能を絞り、煩わしい設定を出来るだけ省き、誰でも手軽に使えるようにしている。

あとは、悪用をどう防ぐかということだけだろう。まだ試していないが、ファイルのホスト機能もあるようだ。URLはともかく、ファイルは著作物となりうる。Gumroadが本気でサービスを継続する気があるのあらば、他人の著作物を無断で使われることを、何とかして防がなければならない。ハッシュ値チェックなどで、ある程度は自動化出来るだろうが、万能ではない。有人による確認が必要となるだろう。

しかし、動画投稿サイトだって、一昔前は、様々な理由から、非現実的だと思われていたのだ。こんなサービスも、将来当たり前になっているかもしれない。

ともかく、C++の参考書の執筆に戻る。将来、ちょっとしたプログラミングの記事とかがこういうサービスで売り出される時代が来るだろうか。いや、果たして売れるのだろうか。

Gumroadで他人のURLを売るのは違法か?

例示用に予約されたセカンドレベルドメイン名情報の購入

Gumroadを使えば、どんなURLでも売ることができる。すると問題になるのが、自分のコンテンツを他人に販売されるということである。Gumroadが日本の法律に照らしてどうなのかという疑問は、すでに発表されている。しかし、Gumroadを技術的に考えてみると、なにか問題があるとは思えない。

なぜかというと、Gumroadで課金するのは、コンテンツではない。URLである(追記:どうもURLの他に、ファイルをホストするサービスもあるらしい)。URLとは、単にWeb上のコンテンツを指し示す文字列に過ぎない。書籍のISBNと、技術的に異なることはない。URLに著作権を主張できるはずがないので、URLを教えたとしても著作権侵害にはあたらない。

今、私が、近所のコンビニの場所への案内を頼まれて、その案内に対し対価を得たとしても、それは違法ではない。私はコンビニを売った事にはならないし、コンビニで窃盗を働いたわけでもないし、ましてやコンビニの著作権を侵害したわけでもない。私は単に、道案内の対価を得ただけである。

同様に、私がAmazon.co.jpのサイトでの購入の際の操作方法を教えることに対して対価を得たとしても、違法ではない。私はアマゾンを売ったわけではないし、アマゾンの著作権を侵害したわけでもない。私はコンピューターの操作方法の説明の対価を得ただけである。

今、私が、かくかくしかじかに関する書籍はないかと質問されて、その本のISBNを教えることに対価を得たとする。これは違法ではない。私はその本の著作権を侵害したわけではない。私は単に本の存在を教えることに対して対価を得ただけである。ISBNは書名や著作者や出版社などと同じく、単なる情報である。著作物ではない。

とすれば、URLを教えたことに対して対価を得るのは、果たして何か問題になるだろうか。URLにアクセスして、その結果としてコンテンツをダウンロードされたとする。なにか違法なことが起こっただろうか。もし、あるURLに自由にアクセスされたくなければ、そのURLのWebサイトで認証を設けるべきである。それは、URLによって指し示されたWebサイトを提供する者の責任である。

第一、URLを教えて対価を取るようなサイトは、もう五万とあるはずである。リンクを張り、広告を設置していれば、直接ユーザーから対価を取ってはいないとはいえ、対価を得ていることには代わりはない。書名やISBNを教えることに対価をとっても問題にならないように、WebサイトのURLを教えることに対価をとっても違法性はないはずである。

ゆえに、私はGumroadでは、誰でも、どんなURLでも合法的に販売できると思う。誰か反論して欲しい。

唯一何か問題があるとすれば、そのURLが、著作物の違法なダウンロード(著作権侵害)であるとか、日本国政府を打倒して革命を起こすよう主張していたり(内乱罪)、わいせつ物を頒布(わいせつ物頒布等の罪)していたなど、犯罪に関わっている場合、そのURLを教えたことにより、該当の罪の幇助に問われるかもしれない。しかし、幇助というのは、私の知る限り、かなりややこしい概念で、その認定方法もややこしい。そもそも、著作者がそのURLを公開していて、認証なしで著作物をダウンロード出来る場合、それは違法ダウンロードにはならないだろう。違法ダウンロードにならないのだから、著作権侵害幇助にもならないはずだ。

というわけで、私はRFC 2606で規定されている例示用に予約されたセカンドレベルドメイン名の情報を売ることにする。なんと、太っ腹にもたったの100円である。

例示用に予約されたセカンドレベルドメイン名情報の購入

Gumroadの仕組み

今、Gumroadが話題となっている。いったいこれはどういうサービスなのか。どのような仕組みなのか。それを調べたので、メモがわりに書いておく。

Gumroadは、URLの販売代行を行うサービスである。

たとえば、芸術的な絵を描いて、そのデジタルデータを売りたいとする。別に高く売るものではない。例えば、数百円で売りたいとする。広く売るものではない。例えば、せいぜい数十人から数百人程度に売れれば良しとする。デジタルデータに対する課金は、何も目新しいものではない。そのためにはまず、そのデータをダウンロードできるURLを用意する。これは簡単である。何しろ、レンタルサーバーは月数百円から存在するし、無料のファイルホストサービスも存在する。URLを、たとえば、http://www.example.com/art.zipとする。ここまでは簡単である。問題は、このURLを公開してしまっては、金が手に入らない。何とかして、ユーザーに課金させる仕組みを作らなければならない。

ところが、実際にユーザー登録などの機能を提供し、クレジットカード会社と契約してユーザーに課金し、課金済みのユーザーのみに使用が制限されたURLを用意してダウンロードさせる、などという大掛かりなWebサイトを、個人で始めるのは負担が重すぎる。第一、数百円程度のデジタルデータを小規模に売りたい時に、そんな大掛かりなサービスを実装するのは大赤字である。鶏を割くに焉んぞ牛刀を用いんや。

ここに、Gumroadが割り込む。Gumroadで販売したいURL(http://www.example.com/art.zip)を値段とともに設定する。すると、Gumroadはその課金用URLを生成する。そのURLにアクセスすると、課金画面がでる。課金したユーザーには、設定したURL(http://www.example.com/art.zip)が告げられる。Gumroadは手数料を徴収した差し引きを、販売者に渡す。

これがうまくいくかどうかは、興味深いところだ。確かに、購入者の誰かひとりでもURL(http://www.example.com/art.zip)を漏らしてしまえば、この仕組は破綻する。これを防ぐために自前でアクセスに認証を設けるならば、Gumroadを利用する意味がない。しかし、今の時代、デジタルデータを共有するのは、非常に簡単になってる。容量はもはや問題ではない。数十GB、数百GBであっても、違法なアップロードとダウンロードの障害にはならない。破られなかったDRMは存在しない。もしあるとすれば、それはコンテンツに人気がなかっただけだ。とすれば、認証なしのURLが違法に漏れるのと、実際のデジタルデータが違法に共有されるのとは、結局は同じ事だとも言える。(そもそも、インターネット上のURLを公開するのが著作権侵害になるかというと疑問だが)たとえるならば、田舎の畑の前の無人の野菜販売所みたいなものだろう。カネを払わずに野菜を盗むのは簡単である。

だから、なにか安価なデジタルデータを少人数に販売する場合には、Gumroadでも、案外うまくいくのではないだろうかとも思う。あるいは、一昔前に流行った、気に入ったなら金を払う仕組みのいわゆるドネーションウェアなら、まあ損ではないともいえる。

Gumroadの試みは、果たしてうまくいくのだろうか。数年ほど観察すれば分かるはずだ。

2012-02-10

Eolas敗訴! 繰り返す、Eolas敗訴!

Eolas Loses in Web Patents Claim Against Google and Others | PCWorld Business Center

Eolasの持つ二つの特許、特許番号5,838,906(Flashで有名になったHTMLへの外部アプリ起動埋め込み特許)と、7,599,985(インタラクティブなWeb)が、ふたつとも無効になった。

やれやれ、まだアメリカにも一片の良心は残されているらしい。

野郎ども、お宝のハッシュ値のハッシュ値を盗みに行くぜ。でも親分、それって違法?

Avast, Me Hearties: How The Pirate Bay Changed The Way We Steal | TechCrunch
Magnet-hashes for all torrents on The Pirate Bay: 164 MB | Hacker News
The whole Pirate Bay magnet archive (download torrent) - TPB

The Pirate Bayがtorrentファイルのホストをやめて、マグネットリンクをデフォルトにすると発表した時、The Pirate BayをすべてUSBメモリに格納することができるだろうというジョークが出た。そこで、それを実行にうつした人間がいた。PerlでThe Pirate Bayをクロールして、The Pirate Bay上のユニークID、名前、クロール時のピア数、infoセクションのハッシュ値を記録した。その結果、ファイルサイズは164MB、圧縮して90MBとなった。十分USBメモリに収まるサイズである。その記録のハッシュ値のハッシュ値が以下となる。

magnet:?xt=urn:btih:938802790a385c49307f34cca4c30f80b03df59c

なんと、UTF-8ならたったの60バイトだ。紙にすら書ける。先頭の20バイトは余分なので、根本的には、938802790a385c49307f34cca4c30f80b03df59cというたったの40バイトの情報だ。もちろん、これはHEXなので、実際には20バイト分の情報である。

さて、この20バイトの情報は違法だろうか。

前回も書いたように、どこまでを違法とするのかを考えると、とてもメタな考え方をしなければならない。

torrentファイルに記録されているのは、torrentファイル自体を表示するのに使われるべき名前、トラッカーサイト、ファイル名、ファイルの各ピースのハッシュ値などである。技術的に考えて、ハッシュ値にまで著作権が及ぶはずはない。

ところが、世間では、根本的には単なるファイルのハッシュ値であるtorrentファイルが違法であるらしい。実際、The Pirate Bayのファウンダー達は有罪判決を受けている。そこで、The Pirate Bayは、torrentファイルすらやめてしまった。マグネットリンクになった。

マグネットリンクとは、torrentファイル内のinfoセクションのハッシュ値である。つまり、938802790a385c49307f34cca4c30f80b03df59cのようになる。この数字は、The Pirate Bayをクロールして最小限の情報を格納したファイルのtorrentファイルへの識別子とみなすことができる。実際に目的にファイルを得るには、この識別子を元に、他のピアからtorrentファイルを入手、そのtorrentファイルを元に、他のピアから目的のファイルを入手することになる。これは、違法な数字なのだろうか。

著作権はハッシュ値にまで及ぶのか? ハッシュ値のハッシュ値ではどうか? ハッシュ値のハッシュ値を掲載しているWebサイトはどうか。ハッシュ値のハッシュ値を掲載しているWebサイトをクロールしたデータはどうか。ハッシュ値のハッシュ値を掲載しているThe Pirate Bayをクロールしたデータのハッシュ値のハッシュ値、すなわち、938802790a385c49307f34cca4c30f80b03df59cは、果たして違法なのか。著作権侵害幇助だとしても、どこまでが幇助になるのだろうか。

追記:もちろん、このリンクを延々とたどって、最終的にファイルを違法ダウンロードすれば、それは著作権侵害となる事にかわりはない。問題は、ハッシュ値を掲載することが違法になるのか、つまり、The Pirate Bayは違法かどうかということだ。

2012-02-09

Eolasの悪夢、再び

Patent Troll Claims Ownership of Interactive Web – And Might Win | Threat Level | Wired.com

Eolasと聞いただけで身震いする者は、有能な技術者である。わからないものは、未熟者である。Eolasは「HTML内に自動的に外部アプリを起動させてやり取りをさせ埋め込みオブジェクトを表示するための手法」というあまりにも明白過ぎる特許を取って、一時期Webを混乱に陥れた特許ゴロである。わかりやすく言うと、Flashを埋め込むHTMLコード、embedやobject要素がこの特許に抵触する。

Eolasはマイクロソフトを相手取って特許侵害の訴訟を起こした。この訴訟には、WWWの父であるW3Cの長であるTim Berners-Lee本人をはじめ、様々な企業、団体がマイクロソフトを援護したにもかかわらず、結局、特許を覆すことはできなかった。最終的に、マクロソフトは和解をし、非公開の額の賠償金を支払ったということである。一説によると、3040万ドルだそうだ。

もちろん、これはアメリカ国内の話なのだが、ブラウザーを作っている企業の多くはアメリカにいるし、外国企業とて、アメリカという市場を無視することはできないため、この特許を回避する方向に動いた。回避方法とは、Flashの再生には、ユーザーの明示的なマウスクリックを必要とする変更であった。わけが分らない。さらに、Javascriptで動的にFlash埋め込みコードを追加するのも、特許回避であるので、様々なFlash埋め込み用のJavascriptライブラリが流行した。これも技術者の端くれとして本質的に訳がわからない。

そういうわけで、Eolasは特許ゴロの名を(ほしいまま)にしたわけだが、実は、第二弾があった。「インタラクティブなWeb」という特許である。

この特許は、とにかくインタラクティブなWeb全般に関わる。またもや、Tim Berners-Leeご本人をはじめ、主要なWeb企業は裁判で戦っている。

ソフトウェア特許という奇妙極まりないものは廃止すべきである。恐らく特許自体、廃止、ないしは極端に制限すべきだろう。著作権も、もはや時代にあっていない。

2012-02-07

続Appleの罠エディタ

Apple Clarifies iBooks Author Licensing Situation in New Software Update - Mac Rumors

なんでも、あの不思議なEULAの意図は、iBooksというフォーマットにかかる権利らしい。

しかし、日本において純粋なファイルフォーマットが特許性を有するのだろうか。ファイルフォーマットの文法は、プログラミング言語の文法と同じで、特許性も著作権も持たないのではないだろうか。

そういえば、日本でLZWアルゴリズムの特許を取れたのも、これまた未だに理解出来ない。LZWは純粋なアルゴリズムであり、どうやって日本で特許をとれたのだろう。

著作物の定義

たまたま、著作物について面白いニュースが二つ会ったので紹介する。

You Can’t Copyright Porn, Harassed BitTorrent Defendant Insists | TorrentFreak

アメリカ合衆国で、ポルノを違法ダウンロードした者が著作権侵害で訴えられたもので、反論として、そもそもポルノには著作権は認められないと主張している。

これは、合衆国の憲法が、著作権について、

To promote the Progress of Science and useful Arts, by securing for limited Times to Authors and Inventors the exclusive Right to their respective Writings and Discoveries

科学と有用な技術の発展を促進するため、その作者と発明者に期間を限定した排他的な権利を保証する

としているため、そもそもポルノはこの憲法の定義に当てはまらず、故に著作権は主張できない。著作権が主張できないのだから、著作権侵害など存在しない、という論法らしい。

もっとも、なぜか違法ダウンロードをしたとされている日時が、問題の映画の公開一ヶ月前で、そもそもダウンロード不可能であったらしい。

ちなみに日本ではどうかというと、著作権法では、

著作物 思想又は感情を創作的に表現したものであつて、文芸、学術、美術又は音楽の範囲に属するものをいう。

とされている。日本では、憲法で著作権を規定しているような記述がちょっと見つからない。著作権法に著作物が規定されている。

これを考えるに、「思想か勘定を創作的に表現したものではない」か、「文芸、学術、美術、音楽の範囲に属さない」のであれば、それは著作物ではないということになる。

さて、わいせつ物は美術だろうか。四畳半襖の下張事件の上告棄却の判決文によれば、

なお、文書のわいせつ性の判断にあたつては、当該文書の性に関する露骨で詳細な描写叙述の程度とその手法、右描写叙述の文書全体に占める比重、文書に表現された思想等と右描写叙述との関連性、文書の構成や展開、さらには芸術性・思想性等による性的刺激の緩和の程度、これらの観点から該文書を全体としてみたときに、主として、読者の好色的興味にうつたえるものと認められるか否かなどの諸点を検討することが必要であり、

と書いてある。「さらには芸術性・思想性等による性的刺激の緩和の程度」ということは、わいせつ物は芸術性、思想性によって緩和されるものらしい。ということは、わいせつ物自体には芸術性も思想性も認められないのではないだろうか。芸術性も思想性も認められないのであれば、わいせつ物、すなわちポルノは著作物ではないということになる。いや、これは言葉遊びだろう。わいせつ物が、その自身の芸術性、思想性によって緩和されるのだから、わいせつ物にも芸術性、思想性はあるはずである。

ただし、日本でわいせつ物だと認定されれば、それを頒布する行為は、刑法175条によって禁じられている。すると、同様な訴訟が日本であったとしても、わいせつ物に著作権が認められたとしても、わいせつ物であると訴えて、認められれば、相手も刑事罰を食らうのだろうか。わいせつ物であるかどうかというのは、裁判所が判定することである。なにも警察のOBが天下っている団体によって判定されるわけではない。あれはあくまでレーティングであって、わいせつ物ではないというお墨付きを得たわけではないのだ。なにしろ、警察がわいせつ物であるかどうかを判断してしまうのは検閲に当たる。わいせつ物の判断は裁判所によってなされねばならないはずだ。

ちなみに、この訴訟は、アメリカではお決まりの、「お前は著作権侵害をしたはずだ。今すぐ数千ドルを払えば訴えないでおいてやる」という、推定有罪の通告に対し、「そもそも俺はやってねーよ」と主張して反訴したものである。

Oracle sues Google for Java patent, copyright infringement

これは古いニュースだが、興味深かったので考えてみる。OracleはGoogleがJavaの著作権と特許を侵害したとして訴えている。

私の理解する所では、日本では、プログラミング言語を解説した文書や、実装には著作権が与えられるが、プログラミング言語自体には著作権は認められない。同様に、プログラミング言語自体の特許も認められない。日本から見ると、アメリカは不思議な国だ。まあ、Flashを参照するURLを記述するembed要素に特許が認められる国なのだから、なんでもありか。

2012-02-06

親知らずを抜いた後がだいぶ治ってきた

親知らずを抜いた後が、だいぶ治ってきた。一時は頬が腫れ、喉が痛く、熱も出て、最悪だったが、ようやくマシになってきた。今日抜糸予定。

もうすぐ、まともに物を食べられるようになるだろう。おかゆばかりすすり続けていると、なんだか無性にジャンクフードが食べたくなる。

2012-02-05

アゴを印刷する時代

3ders.org - 83 year-old woman got 3D printed mandible | 3D Printing news

従来の方法でインプラントのアゴを作るのは数日かかるが、3Dプリンターを使えば数時間ですむらしい。

2012-02-03

なぜ歴史には海賊が必要なのか

Why History Needs Software Piracy | PCWorld

SOPAとPIPAのような反海賊法を巡っての議論において、我々の関心は、主として現代と近未来に置かれがちである。職と利益に対する被害に対しては、今日、誰が被害をうけるのかということに着目しがちである。

ここでひとつ、ソフトウェア海賊に対して、別の視点からの関心ごとを述べてみようと思う。未認可のソフトウェアの複製が、短期的にみて、いくらかの商業的利益を損ねることに疑いはないが、ここで一歩下がってみると、すこし違った状況が見えてくる。歴史的に考えると、ソフトウェア海賊のもたらす利益は、短期的な損失を大幅に上回っているのだ。もし、技術史を気にかけるのであれば、許諾なくソフトウェアをコピーする連中がいることに、感謝しなければならないのだ。

一件奇妙に聞こえるかもしれないが、海賊が救ったソフトウェアの数は、破壊したソフトウェアの数より多いのだ。すでに、海賊は何万ものプログラムを絶滅から救っており、図らずもデジタル文化の守り手としての地位を証明している。

ソフトウェア海賊は、データをより簡易かつ記録媒体から独立させることによって、データの消失を防いでいる。巨大なシステムの一部として働く全体の知識を持たぬアリのように、デジタル海賊の献身的な行動は、全体として観察すれば、数多くのデジタルコンテンツを保存しているのだ。

海賊による保存効果とは、あまり良く知られてないが、何も目新しいことではない。何世紀にもわたり、石版、巻物、本に至るまで、その最も複製され、頒布されたものが、現代もっともよく保存されているのである。ホメーロス、ベオウルフ、ましてやキリスト教の聖書までも、もし未許諾の複製によらずんば、今日の図書館には残っていなかっただろう。

昔と今の違いは、ソフトウェアの劣化は、世紀という単位ではなく、年単位であり、複製による保存を違法行為にしてしまうのだ。これは深刻な問題である。何千もの文化的に重要なデジタルコンテンツが、今我々がこうして論じている間にも、虚無に消え去ろうとしているのだ。

ソフトウェアの消失

現代におけるソフトウェアの消失の問題は、磁気的媒体に起因する。かつてパーソナルコンピューターで広く使われた記録媒体であったフロッピーディスクは、約束された有限の寿命を持つ。推定寿命は、一年から、保存状態が良くてせいぜい三十年といったところだ。

フロッピーはプラスティックの円盤上の磁気変化によってデータを保持している。時間経過と共に、データを表現する磁気変化は、弱くなり、最終的にフロッピードライブが読み込めないレベルにまで低下する。その時点で、ディスクの中身は、実質的に、消失するのだ。

これは、三十年以上前に、パブリッシャーがソフトウェアをフロッピーディスクで出荷していたことを考慮すると、とても困ったことである。ほとんどの当時のディスクは、もはや読めない状態にあり、その上に記録されていたソフトウェアは、修復不可能なほどに損傷してしまっている。もし、読者が今からバックアップを進めようなどと思っているのならば、悪いニュースがある。時すでに遅からん[訳注:リンク先は、Web Archivesの運営者の一人が、どんなフロッピーでも保存するから送ってくれと頼んでいるサイト、しかし、すでに大半のディスクは手遅れな状態にあることを危惧している]

さらに悪いことに、1980年代のソフトウェアパブリッシャーは、数えきれないほどの人月をかけて、コンテンツの保存を妨害するように努めてきたのだ。海賊行為を妨げるため、彼らは様々な手法で、ソフトウェアをひとつの認可されたディスケット上にロックしようと試みた。ある有名なコピープロテクトの手法は、意図的に壊れたデータブロックをディスク上に配置して、コピー時のエラーチェック機能に引っかからせるようにしている。この手法は実によく働いたので、正規ユーザーの正規購入ソフトウェアの正当なバックアップすら妨げてしまった。

これらのコピープロテクトが、その意図通り、ド素人避けであり、著作権法に従うとすると、これらの手法を用いたディスクで出荷されたソフトウェアは、今や永久に失わてしまっていたであろう。多くの時代を主張する文化財が、メディアコントロールという強欲によって消失してしまったのだ。

危機にひんしているのはフロッピーディスクだけではない。ROMカートリッジによって出荷された何千ものゲームや、アーケード基盤は、今や見つけることすら難しい。しかも、これらは永遠の動作寿命を持たない電子機器上でしか動作しないのだ。パブリッシャーは、いくつかの有名なゲームについては、新しいプラットフォーム上でリリースしている。しかし、大多数のレガシーなゲームは、そのような処置を受けてはいない。海賊はこのようなデータをROMチップから解放し、ソフトウェア上のエミュレーションによって、新しいゲーム機やPC上で、プレイできるようにしているのだ。

また、海賊行為によって、歴史家が海外のゲームを研究するのを容易にしている。いくつかのゲームは、日本限定の書き込み可能なディスクにダウンロードする形で頒布されていたのだ。例えば、ニンテンドーパワーのフラッシュカートシステムやBS-Xサテラビューのような仕組みでだ。これらのゲームは、もし過去に違法にバックアップする試みがなされていなければ、西洋の歴史家からは手に入らなかった代物なのだ。

[訳注:Wiiを持っていないので詳しいことはわからないが、商業的に価値のあるほんの一部のゲームは、WiiのVCで提供されているかもしれない。余りはどうしようもない。これらを合法的に手に入れる方法は、現時点では存在しない。とくにBS-Xは、テレビの電波を利用した配信だったので、正規の方法で購入できるカートリッジがないのだ。もし当時、デジタル文化財の保存の重要性に気がついてバックアップしていなかったとしたら、お手上げである。ちなみにこれらのゲームは、1990年代後半に公開されているので、著作権法が今後も変わらないとしたら、ゲームは映画であるからして、今から約60年後の2070年あたりには、著作権が切れる。しかし、この記事でも言っているように、そもそも保存が難しいのだ。]

ソフトウェアの消失の問題の例として、ゲーム業界をみてみよう。Web上の最大のゲームデータベース、MobyGamesは、現在、約6万件のゲーム情報を載せている。約2万3千ほどのタイトルは、フロッピーディスクやカセットテープを記録装置や、配信媒体として使うコンピューター向けに出荷されていた。

2万3千ものゲーム! もし、ゲームパブリッシャーと著作権法が横たわっていれば、このうちほとんどのゲームは、今後10年以内に、記録媒体の劣化によって、地上から一掃されてしまう。すでに、多くが失われている。

近年、コレクターと保存家は、ビンテージマシン(AppleIIとかコモドール64とかのたぐい)上で動く絶版になったソフトウェアを、ファイル共有サービスや、"abandonware"なウェブサイト上で交換している。この過程で、彼らは地下ソフトウェア図書館を作り上げているのだ。これは、新しいとはいえ、古代の失われたデジタル文明の記録を保存する図書館のようなものだ。

Abandonwareについて

abandonwareとは、すでに販売もサポートもされていないソフトウェアを頒布する大義名分である。これらのソフトウェアは、その著作者から「放棄(abandoned)」されたのだ。それにもかかわらず、ソフトウェアに著作権があり、著作者から、ソフトウェアの頒布許諾を得ていなければ、頒布はまだ違法なのである。

ジャーナリストかつ歴史家として、私はこのような海賊ソフトウェアを使って、仕事をしている[訳注:著者のBenj Edwardsは、コンピューター史とゲーム史専門の歴史家を自称している]。私だってこんなことはしたくないのだが、合法的な方法が存在しないのだ(詳しくは後述)。

この未来の歴史家にとって必要な資料である地下図書館による収集は、我々のデジタル遺産を保護するための、人類の反骨精神あふれる勇敢な行動である。この後に述べるように、ソフトウェア史における脅威は、過去にあるのではない。目前にあるのである。

何故ソフトウェアを保護するのか

さらにこの問題に詳しく踏み込む前に、そもそも、何故我々はソフトウェアを保護しなければならないのかという理由について考えよう。ソフトウェアは、そのあまりに短い寿命から、それほど重要視されていない。ソフトウェアとは電子による動的な表現を、コンピュータースクリーン上に投影したものであり、それ自体はさほどの意味をもたない。我々は、物理的な物体に価値を見出すのである。

しかし、ソフトウェアは強力な道具であり、我々の文明にとっても重要な意味を持つ。博物館を眺めると、道具の存在によって、ある社会がどのような状態にあったのかを知ることができる。もし、例えば、ある文明に脱穀機があったならば、我々はその文明が、100年前に比べて、麦の収穫をより速く行えたと知ることができる。これは、転じて、人口の爆発的増加を説明できるだろう。

同様にして、我々は人類の状態を、ソフトウェアツールを調べることで推定できるのだ。未来の歴史家は、今日の我々はいかにして音楽における音程調整を実現していたかとか、いかにしてCGIアニメーション映画を制作していたのであろうか、などという疑問を持つかもしれない。知識と、様々なバージョンのAuto-TuneやPixar RenderManやAdobe Photoshopの体験なくしては、彼ら歴史家が、そのような疑問を解き明かすことは難しいであろう。

ソフトウェアは娯楽でもある。文化である。本や、音楽や、映画と同じく、芸術がソフトウェアによって表現されている。多くはゲームだ。これらは各世代の文化を反映している。

マリオが誰であるかを知らない15-35才のアメリカ人がいるであろうか?(もしそんな奴を見つけ出してきたとしたら、そいつは1980年から1999年にかけて、地下室に監禁されていたに違いない)

法律に抵抗する保存家達の活動に感謝するべきである。これによって未来の歴史家はマリオが文化に与えた影響をより深く考察することができる。「なぜ古代人はみな、ドット絵のキノコ人間がプリントされたTシャツを着用していたのであろうか」とか、「どのゲームにマリオが登場したのか、それはなぜか」などという疑問も、明らかにすることができるであろう。

任天堂がこの先200年生き延びることは、可能である。しかし、彼らはこのような疑問に全て正しく答えることはできないであろう。企業は顧客に見せられる商業的価値のあるものしか残さないものだ(例えば、スーパーマリオブラザーズ3を繰り返しみせる)。歴史家は、すべてを見せる。ホテルマリオ[訳注:CD-iで公式な許諾を得て発売されたお世辞にも出来がいいとは言えないマリオのゲーム、アメリカ限定]、マリオルーレット[訳注:コナミから発売されたアーケードゲーム、いわゆるメダルゲー、日本限定]、アイアムアティーチャー スーパーマリオのセーター[訳注:ディスクシステムにてこのタイトルで発売されたマリオをつかった編み物の教育ソフト、日本限定]。これらのゲームは、海賊によらなければ、200年を生き延びることはできない。なぜならば、任天堂はこのような低品質のゲームを、やや恥じており、著作権法で封印して、朽ちるに任せているからだ。

我々には失うものが多すぎる

ソフトウェアの消失が過去の話であってくれればいいのだが、現代のソフトウェア市場においても、まだこの問題は存在する。App Storeやその他のデジタル配信システムは、よくヘンテコで制限されたライセンス契約をさせたがる。時には、デバイスすら制限することもある。我々がソフトウエアを所有することすら認めていないのだ。

横暴なDRMと統一された配信方法のおかげで、ほとんどのデジタル配信されているソフトウェアは、これらの配信ストアが廃止された場合、歴史的資料から消失する。これは事実である。彼らはいずれの日か、サービスを廃止する。もしこの事実に恐怖を覚えないとしたならば、歴史の授業が必要だ。

50万種類もの本が、急にその機能を終え、魔法のように世界中のすべての複製物が機能しなくなったとしたらどうであろうか。ポン。その中にあった情報は、消えてしまうのだ。紀元前48年のアレクサンドリア大図書館の焼亡に匹敵する文化的ダメージである。あの炎によって、それまでの西洋文化の歴史は、灰燼に帰したのだ。

さて、iTunes App Storeをみてみよう。デジタル文化の象徴である50万ものアプリを保持している。これは、たったひとつの企業によって支配されている。そして、将来廃止されるときには(もしくは、古いアプリをサポートしなくなった時には、Appleはすでに昔のiPodでやらかしている)、これらのアプリに対する合法的なアクセス方法も消え去るのだ。iDeviceに縛られた、購入したアプリは、このシステムが機能をやめた時、寿命を迎える。すでに、古いアプリは消え去っているのだ。なぜならば、開発者がアプリをストアに保持しておくための年会費100ドルを払わないがために。

歴史的にみれば、我々はハッカーと海賊たちが、iTunes App StoreやPlayStation StoreやWii Shop ChannelやXBox Live Arcadeやその他のオンラインのダウンロードサービスから入手できるあらゆるアプリを、秘密裏に保存してくれていることを願うしかないのだ。

クラウドソフトウェアはどうだ? もし、我々のソフトウェアツールが中央管理され、インターネットを介して実行されるようになれば、海賊行為は困難になる。これは、保存されないということを意味する。歴史にとって、最悪である。

もし、古人類学者が、1万3千年前のやじりでバイソンを仕留めることができるかどうか疑問に思ったならば、単に槍の先にくくりつけて投げてみればよい。もし、やじりがその開発期間中、クラウドによって自動的にアップデートされていたならば、我々は最新設計のものしか知りえないことになる。やじりは現存していないであろう。我々は、古代のアメリカ原住民が、どうやって劣化ウラン弾でゲームをプレイしていたのかなどと疑問に思うことであろう。

考えてみたまえ。一年前のGmailのインターフェースってどんなだった?ストリートビューが搭載される前のGoogle Mapってどんなだった? クラウドベースのソフトウェアツールの昔のバージョンにアクセスできない未来の歴史家は、スクリーンショットや個人の証言記録により、昔のツールがどのくらいの性能を持っていたのかを推し量るしかないであろう。もし、まだ存在していればの話だが。

クラウドではないソフトウェアならば、未来の歴史家は古いバージョンを試すことも可能だ。歴史家はやじりと同じようにツールを使い、当時の人間と同じ活動を再現できるのだ。たとえば、歴史家はAtari 800エミュレーター上でAtariWriter を実行して、1980年代のドキュメント作成を再現でき、これによって当時のフォーマットの必然性を実感できるであろう。

完全なるクラウド依存のゲーム(たとえばOnLive)は、脅威である。OnLiveにゲームソフトフェアの保存を期待するなどというのは、近所の映画館が、フィルムの歴史を保存していてくれるだろうを期待するようなものである。彼らは単に、その時点で商業的に価値のあるものしか上演しない。残りは破棄してしまうのだ。クラウドゲームも同じである。

新たな大図書館は、すでに焚書され始めている。我々はすでに、その煙を嗅いでいるのだ。

企業が歴史を支配すると、改変する

今日のデジタルApp Storeで使われているDRMは、将来の正しい歴史認識に対する重大な脅威である。そりゃ、ソフトウェアを作った企業は、製品に対する権利を、今は持っている。しかし、ひとたびコンテンツが消費され、大衆文化に取り入れられたならば、それは世代のものである。それは単なる商業製品以上の役割をはたすのだ。そして、コンテンツは文化財として、保護、保存されるべきである。

いつ変更され、消え去るとも分からないものを保護、保存するのは難しい。もし、VHSテープがApp Storeのように機能していたのならば、ジョージ・ルーカスは我々が購入したスター・ウォーズ映画を強制的に特別版にアップデートできるのだ(もちろん、ルーカスOSとの互換性を保つためである)。その過程で、昔のバージョンは上書きされてしまう。誰か知らん、将来、ルーカスは自分の映画が気に食わなくなり、Willowに書き換えてしまうかもしれない。法的な権利が彼にあったとしても、文化に対する窃盗である。

[訳注:日本において、Steamではすでに似たようなことが起きている。何故か正規購入したはずの日本語版が、後で英語版に差し替えられることがままある。そして、日本語版は購入不可能になる。]

iOSのアップデートの頻度に筆者は参っている。アップデートに伴い、過去のバージョンを消してしまうのだ。保存作業も面倒だ。確かに便利で強力な機能には違いないのだけれど、ダウンロードするたびに、歴史が書き換えられてしまうのだ。もし、1990年代からPhotoshopがこのようにアップデートされていたならばどうなっただろうか。レイヤーをサポートした最初のバージョンを持っている人はいるだろうか。そのような歴史的に重要なソフトウェアが消失してしまうのだ。同様に、もし我々が、すべてのPCアプリケーションに対し、完全にコントロールされた唯一の供給方式による自動アップデートを受け入れたならば―もうすぐそこに控えているよ、Windows 8のことさ―我々は、将来のデジタル遺産を破壊してしまうのだ。

制限的なDRMを受け入れた時、我々はソフトウェアとメディアのパブリッシャーに、デジタル文化の歴史を、意のままに削除、コントロール、捏造する力を与えているのだ。だからこそ、DRMは人類にとって根本的に悪であると感じるのだ。DRMは人類の正当たる文化遺産を破壊してしまうのだ。

もちろん、ソフトウェア作者は、今行われているように、ある一定の期間において、独占的な権利を与えられ、それによって対価を得るべきである。しかし、だからといって、歴史的な保存に対する妨害を許してはいけない。

アレキサンドリアで二千年前に起こった出来事を繰り返してはならない。大図書館の焚書を生き延びた巻物は、著作者の許諾なしに、複製され頒布されたものだけなのだ。残念なことに、当時の図書館職員は複製を防ぐために入館を厳しく制限していたので、焚書を生き延びた書物は非常に少ない。もし、我々がソフトウェアの保存に対する法的根拠を作らなければ、今から数千年先の文明には、海賊によって違法に複製されて頒布されたコピー品しか残らないであろう。

ソフトウェアの文化に与えた影響は、他の著作物に匹敵する。今こそ、書籍や映画と同等に、図書館で電磁的芸術品を合法的に保存するべきなのだ。そのような図書館を設置するのは、しかし、非常に難しいのだ。

デジタル図書館の困難性

もし、我々の文化の歴史を今日までたどって検証したければ、図書館に行くであろう。図書館には、無料で利用できる広範なアナログデータが収集されている。ソフトウェアを同じ方法で検証しようとしても、不可能だ。実用的で広範なソフトウェア図書館というものは、現在、合衆国では違法だからだ。

誤解しないで欲しい。ソフトウェア図書館を作るのは可能である。しかし、その実装は、現実的に役立たずなのだ。法律の範囲内で、実現可能な図書館というのは、公式に複製された物理的なソフトウェアの記録媒体を、物理的な書架に格納することである。つまり、記録媒体の劣化と時代遅れの問題がついてまわる。書架に陳列されているものが動く保証なんてどこにもない。

ソフトウェア図書館のより現実的な方法は、固定された記録媒体からデータを解放し、冗長化されたハードディスク群に収めることである。司書はハードディスクをアップグレードすることによって、劣化による消失を防ぐことができる。ソフトウェアは、ネットワーク間で自由に転送でき、エミュレーター(ソフトウェアのオリジナルなプラットフォームをシミュレートしている)上で実行されることにより、歴史的な検証に利用できる。

残念ながら、この実用的な方法は使えない。何故ならば、現在のところ、合衆国アメリカの著作権法においては、著作者の許諾を得ずして、ソフトウェアをコピー―オリジナルの記録媒体からの解放のために必要な作業―して、公衆で共有するのが、違法だからだ。(法律は合法的なバックアップを認めているが、他人と共有することはできない)。さらに、デジタルミレニアム著作権法においては、コピープロテクション技術を迂回して、そのような複製を作成することすら違法なのだ。

現在、フロッピーディスクを、まるで書籍と同じように、書架に収めている図書館が存在する。これらの団体は、本と同じように、十分に注意を払って保護すれば、コンピューターディスク上のデータは永久に保持されるなどという間違った思い込みをしているのだ。しかし、彼らにデータの消失を防ぐ方法は何もない。データは新しい記録媒体に複製されねばならないのだ。将来いずれ、法律が無視されるか―あるいは改正されるだろう。

著作権の時代遅れの前提条件

現在の合衆国アメリカの著作権法は、良い意図を持っているものの、デジタル財産の保存に対する大いなる脅威がある。なぜならば、電子的記録媒体の劣化速度と、フォーマットが時代遅れになる速さを考慮に入れていないからだ。

著作権法を制定した政治家は、19世紀風の法的な前提条件をもとにしている。著作物は、パブリックドメインになって自由に複製できるようになるまでまで、記録媒体に安全に固定されるという思い込みだ。例えば、紙の本だ。紙の本は保存条件次第で、数千年もの間、データを保存できる。

デジタルデータの場合、多くのプログラムは保護期間終了(合衆国アメリカでは著作者の死後70年)の数十年も前に、地上から消え去ってしまう。記録媒体の劣化と、フォーマットが時代遅れになることにより、どの図書館が合法的にバックアップするよりも速く、消え去ってしまう。

解決法のひとつとしては、ソフトウェアの著作権の保護期間を、妥当な期間に制限することだ。たとえば、最大で20年とか。そうすれば、保存家は、古いソフトウェアが忘却の異次元に消え去ってしまう前に保存できる確率が飛躍的に上昇する。

また、司書がソフトウェアを保存する目的でコピープロテクトを解除するのを完全に合法にする必要がある。現在のDMCAにおける一時的なDRM解除を可能にする条件は、あまりにも厳しすぎて、十分ではない。

別の解決法としては、新たな法を制定して、著作権保護を受けたいパブリッシャーは、DRMフリーなバージョンのソフトウェアを、合衆国アメリカの国立図書館の記録媒体に依存しないアーカイブに納めなければならないとする。ソフトウェアは後に、研究者のために提供される。必要であれば、このようなデジタル図書館のコンテンツは、商業的活動を保護するために、一定の猶予期間をおいた後に公開される。たとえば、5年とか。

ソフトウェアの消失を看過してはならない

我々の文明は、商業により支配されており、利益を受けている。そのため、商業に従事するものを保護したいと思う。その中で、とある連中が、目的を達成するため、海賊を厳しい法規制によって撲滅したいと考えている。しかし、海賊とデジタル配布とは切っても切り離せないものであり、自由を放棄しない限り、完全にコントロールすることはできない。そのような法規制は、海賊活動を地下に潜らせるだけで、むしろ海賊にかかわらない、正当な技術の発展を妨げる。その技術こそが、今日のソフトウェアの存在を成立させたというのに。

ソフトウェアの劣化を司る4つの力

ソフトウェアを消失に追いやる主な力が4つある。

力その1:物理的劣化

いかなるデジタル記録媒体も、データを永久に保持できない。すべてのコンピューターのデータの記録媒体は、時間経過に伴い、次第に劣化していく。その過程で、データは失われる。

力その2:記録媒体の時代遅れ

技術が進歩するにつれ、あらゆるストレージのフォーマットは、時代遅れになり、ついに、全く使われなくなる。これにより、将来データを取得するのが困難になる。

力その3:コピー防止

商業的理由により、ソフトウェアパブリッシャーは歴史的に、ユーザーがパブリッシャーの許諾なくソフトウェアを複製できないように細工してきた。このような手法は、ソフトウェアの正当な保存を妨げる。

力その4:商業的価値の消失

すべてのソフトウェアには、ある期間の商業的寿命がある。技術革新の圧倒的な速度によるものだ。これにより、ソフトウェアはある短い期間にしか、商業的に複製、頒布されない。

現在のところ、我々は、十分に頑張れば、いかなる娯楽品、あるいはソフトウェアプログラムであっても、無料で手に入れられる。いまだに何百万人もの人々が正規のソフトウェアや映画や音楽などの複製物を取得するために現金を払っているにもかかわらずだ。これにより、業界は大きく、より高売上になっている。

まだ人々がデジタル媒体を購入するという事実は、海賊というのはそれほど問題ではないということだ。実際、海賊というのは別の問題への解決方法なのだ。過剰に保護された知的財産という問題だ。あの無茶苦茶なDRMを使い、より過剰な反海賊法規制を支援する企業が、歴史的価値を保存するためには、多少の商業的利益の減少も致し方ないという見解をもってくれればいいんだが、それはフリーマケットですら目的ではないのだから、期待はできない。

今、この文化の歴史を保存するのは、この我々の世代にかかっている。我々は著作権法を改正し、海賊行為によらずに、ソフトウェアを歴史的資料として保存できるように働きかけねばならない。

ソフトウェアを愛するのであれば、買って、使って、作者を対価を与えるべきである。私はちゃんとやっている。私は製品によって金銭を得る権利を支持している。しかし、文化的権利の為に立ち上がることを恐れるな。もし無茶なDRMやコピープロテクトが、歴史の保存の脅威であると感じたならば、戦え。コピーしろ。安全な場所で確保しろ。そしていずれ共有して、消失しないようにしろ。

現代では、いくらかの連中から犯罪者だとみなされるかもしれないが、彼らは歴史の悪の方のいる連中だ。今から500年後に、古代のプログラムをロードして実際に検証するときに、当時から生存していてお前を著作権侵害者だなどとそしる者などいやしない。

一年前のGmailのインターフェースを覚えているかというところで、ハッとした。Gmailは毎日使っているのに、実際、覚えていない。もし、電子メールの歴史について書こうとしたならば、Gmailについて述べない訳にはいかない。Gmail以前にもクラウドベース(個人的に、クラウドという言葉はバズワード臭くて好きではないが。)のメールサービスはあった。しかし、Gmailが画期的な容量とインターフェースを提供したからこそ、爆発的に流行ったのだ。

MegaUploadで、まさに焚書がおきようとしている。FBIはキム・ドットコムと他の社員を逮捕し、会社の資産を差し押さえた。まだ裁判は始まってすらいない。キム・ドットコムが実際に犯罪者であるかどうかは、まだ裁かれていない。たとえ違法であったとしても、その利用者は、全員が違法な目的でサービスを使っていたわけではない。なにしろ、オンラインストレージである。しかし、MegaUploadの口座を凍結されているために、MegaUploadはサーバーの維持費が払えず、MegaUploadを合法的に利用していた人達のファイルが、いま消失しようとしている。アレキサンドリア図書館の焼失、秦の始皇帝の焚書坑儒は、いまだに行われているのだ。

もちろん、クラウドにデータを預ける危険性というのは、オンラインストレージやGmailだけではなく、このブログにも言えるわけだが。

2012-02-02

だいぶ良くなってきた

いまだにおかゆしか食べられないが、親知らずを抜いた後は、だいぶよくなってきた。とりあえず熱も下がり、頬の腫れもだいぶひいてきた。今は、むしろ糸に痛みを感じる。早く抜糸してもらいたいところだが、残念ながら来週まで待たなければならないとか。前回も、最終的には糸の存在が気になるようになった。

2012-02-01

噛みごたえのあるものが食べたい。

親知らずを抜いた後はまだ痛む。昨日は頬が腫れてふくれあがり、熱が出て頭がぼーっとし、悲惨な目にあった。今日は、熱はないし、腫れも治まってきているようだ。しかし、アゴを動かすと傷が痛む。唯一食べられるのは、水を米の10倍いれて作った、液体のようなおかゆだ。野菜も相当細かく刻まないと食べられない。ああ、何か噛みごたえのあるものが食べたい。