タグ: ruby

  • テンプレートの階層

    音声対話ツールキット Galatea のプロジェクトに関わり、マルチモーダル対話記述・アーキテクチャの標準化に関わり、Ruby on Rails による VoiceXML アプリケーションの実装について考えた末、「階層化されたシステムの実装は、テンプレートエンジンの階層になる」と思うようになりました。

    Webアプリケーションのフレームワークの多くがテンプレートエンジンを提供しています。いくつもの階層でそれぞれに記述の標準化を行っていることのメリットは十分にありますが、記述が冗長になるというデメリットもあります。テンプレートエンジンはこうした問題を解決する一つの手段です。

    音声対話技術コンソーシアム(ISTC)では、音声入出力、GUI入出力などを有するインタフェースシステムについて、各階層でのインタラクション記述仕様の標準化について検討を行い、いわゆる Model/View/Controller の構造をさらに詳細化した6階層を提案しています。

    私が開発を続けている Galatea Dialog Studio という対話制御エンジンについて考えると、いくつかの階層は Ruby on Rails のMVCフレームワークに対応していると考えられます。また、いわゆる Web アプリケーションをまず実装して、HTML に依存するレイヤーだけを VoiceXML に差し替える、という音声対話システムの実装も、妥当な手法になると思います。

    Galatea Toolkit の Linux 版について、インストールや設定の難しさという課題が残っています。当初の設計によって各モジュールを統合することに成功しました。しかし、カスタマイズやデバイス設定が必要な場合に、現時点では、多くの箇所を矛盾なく変更する必要があります。

    インタラクション記述ではないものの、各階層を動作させるためには、それぞれ必要なパラメータや設定情報があります。例えば音声合成であれば、言語処理や話者モデルのリストを与える必要があります。音声認識についてはオーディオ入力、音声検出、音響モデルなど数多くのパラメータが存在します。これらの設定を Galatea Toolkit で統合的に扱いたいと考えています。設計していくとこれはやはり「テンプレートの階層」になりそうです。

    具体的には、以下のようなアーキテクチャで実装を進めています。

    1. システム領域にインストールされるファイルやツール
      • 汎用的なエンジンやファイル
        • プロジェクトファイルのテンプレート
        • 実行時ファイルのテンプレート
      • パスを固定してインストールする必要があるツール
      • プロジェクトを生成する galatea-generate コマンド
    2. ユーザ領域にプロジェクトファイルとして生成されるファイル群
      • config ディレクトリ
        • システム設定ファイル: システム領域の path などを参照する
        • プロジェクト設定ファイル:プロジェクト固有の設定
      • 拡張モジュール用ディレクトリ:プロジェクト固有のモダリティ拡張など
      • log / tmp 用ディレクトリ
      • runner スクリプト
      • プロジェクトの編集を支援するスクリプト
      • 対話アプリケーションサーバ:例えば rails のプロジェクト
    3. 実行時に生成されるファイル群
      • システム領域のテンプレートとプロジェクト設定ファイルに基づいて生成

    これらをできるだけ Ruby on Rails の作法に馴染むように設計を行っています。

    私は Rails に触れる前に、Java で簡易テンプレートエンジンを実装してみたり、PHP や Perl のテンプレートエンジンを使ったりしましたが、インストールが面倒であったり、テンプレートエンジン独自の記述言語を使用していたり、といったところに不満を感じました。

    Ruby が ERB というテンプレートエンジンを標準で備えていて、ERB の中で Ruby 言語そのものが使用できる、自作の Ruby スクリプトから簡単に ERB の機能が利用できる、ということに、好感を持っています。

    VoiceXML ブラウザと HTML ブラウザは完全に等価な位置づけと見なせない部分がいくつかあります。またモダリティを使い分けるのではなく組み合わせる場合にはさらに考慮が必要となります。どの階層をどのように補っていくべきか、今後システム実装の経験を踏まえた提案をしていきたいと考えています。

  • Python vs Ruby

    「Python チュートリアル」という本を読み、同じことを Ruby でやったら、と思って書きかけたエントリを、この日記に刺激されて、第3章「気楽な入門編」の部分だけ、とりあえず公開することにしました:

    • ruby 1.8 / python 2.6 で確認。

    電卓として使う

    Python

    $ python
    >>> 2+2
    4
    

    Ruby

    $ irb
    irb(main):001:0> 2+2
    => 4
    
    • 以下、irb のプロンプトは略記

    複素数

    Python

    >>> a=1.5+0.5j
    >>> a.real
    1.5
    >>> a.imag
    0.5
    >>> abs(a)
    1.58113...
    
    • a.abs とはできない(’complex’ object has no attribute ‘abs’)

    Ruby

    > require 'complex'
    > a = Complex(1.5, 0.5)
    > a.real
    => 1.5
    > a.imag
    => 0.5
    > a.abs
    => 1.58113...
    
    • abs は Python ではビルトイン関数だが Ruby では Complex オブジェクトのメソッド

    文字列

    Python

    >>> '\"Yes,\" he said.'
    '"Yes," he said.'
    >>> '"Isn\'t," she said.'
    '"Isn\'t," she said.'
    >>> "'Isn\'t,' she said."
    "'Isn't,' she said."
    
    • \ は常にエスケープ記号。
    • ‘ ‘の場合は’ ‘で表示され、” “の場合は” “で表示される。内部的な区別あり?

    Ruby

    > '\"Yes,\" he said.'
    => "\\\"Yes,\\\" he said."
    > '"Isn\'t," she said.'
    => "\"Isn't,\" she said."
    > "'Isn\'t,' she said."
    => "'Isn't,' she said."
    
    • \ は “” の場合のみエスケープ記号。
    • 常に “” で表示される。内部的な区別がない?

    Python

    >>> hello = "multi-line string\n\
     ... second line\n\
     ...     third line with indent"
    >>> print hello
    multi-line string
    second line
    third line with indent
    >>>
    

    Ruby

    > hello = "multi-line string
     " second line
    "     third line with indent"
    => "multi-line string\nsecond line\n    third line with indent"
    > puts hello
    multi-line string
    second line
    third line with indent
    => nil
    >
    
    • irb ではコンテクストに応じてプロンプトが変わる

    Python

    >>> print """line 1
     ... line 2
     ... """
    line 1
    line 2
    >>>
    

    Ruby

    > puts """line 1
     " line 2
    " """
    line 1
    line 2
    => nil
    
    • トリプルクオートは Ruby でも使える(ように見える)
    • 最後の改行の扱いが Python と Ruby で異なる
    • puts は返り値 nil を返す

    Ruby

    > puts <<EOS
     " line 1
     " line 2
     " EOS
     line 1
     line 2
     => nil
    
    • 念のためにヒアドキュメントも実験。最初の行の直前の改行の扱いがトリプルクオートと異なる。

    文字列の結合

    Python

    >>> word = 'a' + 'b'
    >>> word
    'ab'
    >>>
    

    Ruby

    > word = 'a' + 'b'
    => "ab"
    > word
    => "ab"
    >
    

    Python

    >>> 'str' 'ing'
    'string'
    >>>
    

    Ruby

    > 'str' 'ing'
    => "string"
    >
    
    • 隣接する2つの文字列リテラルは Ruby でも自動的に連結される

    文字列のインデックス付け

    Python

    >>> 'abcdefg'[4]
    'e'
    
    • Python にはキャラクタ型は存在せず、1文字のキャラクタは長さ1の文字列。

    Ruby

    > 'abcdefg'[4]
    => 101
    
    • Ruby の文字列は文字コード列であることを意識しなくてはならない。C 言語に似ているとも言えるが、うっかり文字列が得られると思いこみがち。
    • Python における ‘abcdefg'[4] と同じようにインデックス4の文字から成る新たな文字列を得るには。。
    • self[nth,len] : nthバイト番目から長さlenバイトの部分文字列を作成
    > 'abcdefg'[4, 1]
    => "e"
    
    • self[first..last] : インデックス first から last までのバイトを含む文字列を作成
    > 'abcdefg'[4..4]
    => "e"
    
    • self[first…last] : 文字列先頭を0番目の隙間として、fist 番目の隙間から last 番目の隙間までに含まれるバイト列を含んだ新しい文字列
    > 'abcdefg'[4...5]
    => "e"
    

    文字列のスライス

    Python

    >>> 'abcdefg'[0:2]
    'ab'
    >>> 'abcdefg'[1:3]
    'bc'
    >>> 'abcdefg'[:3]
    'abc'
    >>> 'abcdefg'[3:]
    'defg'
    

    Ruby

    > 'abcdefg'[0...2]
    => "ab"
    > 'abcdefg'[1...3]
    => "bc"
    > 'abcdefg'[0...3]
    => "abc"
    > 'abcdefg'[3...-1]
    => "def"
    > 'abcdefg'[3...0]
    => ""
    > 'abcdefg'[3..-1]
    => "defg"
    
    • Python における : でのスライスは Ruby では … とほぼ同じ。
    • ただし Python における [3:] は Ruby では [3..-1] となる。
    • Ruby では .. / … は Range というオブジェクトである。.. 演算子で生成されれば終端を含む。… 演算子で生成されれば終端を含まない。

    インデックスにおける負の数

    Python

    >>> 'abcdefg'[-1]
    'g'
    >>> 'abcdefg'[-2]
    'f'
    >>> 'abcdefg'[-2:]
    'fg'
    >>> 'abcdefg'[:-2]
    'abcde'
    

    Ruby

    > 'abcdefg'[-1]
    => 103
    > 'abcdefg'[-1,1]
    => "g"
    > 'abcdefg'[-2,1]
    => "f"
    > 'abcdefg'[-2..-1]
    => "fg"
    > 'abcdefg'[0..-2]
    => "abcdef"
    > 'abcdefg'[0...-2]
    => "abcde"
    

    文字列の長さ

    Python

    >>> len('abcdefg')
    7
    >>> 'abcdefg'.len
    AttributeError: 'str' object has no attribute 'len'
    
    • Python ではビルトイン関数 len を使う

    Ruby

    > 'abcdefg'.length
    => 7
    > 'abcdefg'.len
    NoMethodError: undefined method `len' for "abcdefg":String
    > length('abcdefg')
    NoMethodError: undefined method `length' for main:Object
    
    • Ruby では String オブジェクトのメソッド length を使う

    マルチバイト文字列

    • UTF-8 でソースを記述して、Python 2.6 / Ruby 1.8.6 の Win32 版で実行。

    Python

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    print u'あいうえお'
    print u'あいうえお'[2]
    print len(u'あいうえお')
    
    C:\>python mbstring.py
    あいうえお
    う
    5
    

    Ruby

    puts 'あいうえお'
    puts 'あいうえお'[2]
    puts 'あいうえお'.length
    
    縺ゅ>縺・∴縺
    130
    15
    
    • 望ましい挙動を実現する例(1)
    #!/usr/bin/env ruby -Ku
    # -*- coding: utf-8 -*-
    require 'iconv'
    class String
    def to_sjis
    Iconv.conv('shift-jis', 'utf-8', self)
    end
    def chars
    self.split(//)
    end
    end
    puts 'あいうえお'.to_sjis
    puts 'あいうえお'.chars[2].to_sjis
    puts 'あいうえお'.chars.length
    
    C:\>ruby mbstring.rb
    あいうえお
    う
    5
    
    • 望ましい挙動を実現する例(2)
    #!/usr/bin/env ruby -Ku
    # -*- coding: utf-8 -*-
    require 'iconv'
    class String
    def chars
    self.split(//)
    end
    end
    module Kernel
    alias_method :orig_puts, :puts
    def puts(s)
    orig_puts Iconv.conv('shift-jis', 'utf-8', s.to_s)
    end
    end
    puts 'あいうえお'
    puts 'あいうえお'.chars[2]
    puts 'あいうえお'.chars.length
    
    C:\>ruby mbstring.rb
    あいうえお
    う
    5
    

    リスト

    Python

    C:\>python
    >>> a = ['ab', 'cd', 123, 45]
    >>> a
    ['ab', 'cd', 123, 45]
    

    Ruby

    C:\>irb
    > a = ['ab', 'cd', 123, 45]
    => ["ab", "cd", 123, 45]
    

    個別の要素の操作

    Python

    >>> a[2] = a[2] + 23
    >>> a
    ['ab', 'cd', 146, 45]
    >>> a[2] += 23
    >>> a
    ['ab', 'cd', 169, 45]
    

    Ruby

    > a = ['ab', 'cd', 123, 45]
    => ["ab", "cd", 123, 45]
    > a[2] += 23
    => 146
    > a
    => ["ab", "cd", 146, 45]
    > a[2] = a[2] + 23
    => 169
    > a
    => ["ab", "cd", 169, 45]
    

    要素の置換と挿入

    Python

    >>> a
    ['ab', 'cd', 169, 45]
    >>> a[0:2] = [111,222]
    >>> a
    [111, 222, 169, 45]
    >>> a[1:1] = ['ab', 'cd']
    >>> a
    [111, 'ab', 'cd', 222, 169, 45]
    

    Ruby

    > a
    => ["ab", "cd", 169, 45]
    > a[0...2] = [111,222]
    => [111, 222]
    > a
    => [111, 222, 169, 45]
    > a[1...1] = ['ab', 'cd']
    => ["ab", "cd"]
    > a
    => [111, "ab", "cd", 222, 169, 45]
    
    • 文字列と同じく : は … に置き換えられる

    リストの入れ子

    Python

    >>> q = [2,3]
    >>> p = [1,q,4]
    >>> p
    [1, [2, 3], 4]
    

    Ruby

    > q = [2,3]
    => [2, 3]
    > p = [1,q,4]
    => [1, [2, 3], 4]
    
    • ここまでは全く同じ

    Python

    >>> p
    [1, [2, 3], 4]
    >>> len(p)
    3
    >>> p[1].append('xtra')
    >>> p
    [1, [2, 3, 'xtra'], 4]
    

    Ruby

    > p
    => [1, [2, 3], 4]
    > p.length
    => 3
    > p[1].append('xtra')
    NoMethodError: undefined method `append' for [2, 3]:Array
    > p[1] << 'xtra'
    => [2, 3, "xtra"]
    > p
    => [1, [2, 3, "xtra"], 4]
    > p[1].push 'xtra'
    => [2, 3, "xtra", "xtra"]
    > p
    => [1, [2, 3, "xtra", "xtra"], 4]
    
    • Python のビルトイン関数 len は Ruby では length メソッド
    • Python の append メソッドは Ruby では << または push メソッド

    プログラミングの基礎

    Python

    >>> a, b = 0, 1
    >>> while b < 10:
    ...   print b
    ...   a, b = b, a+b
    ...
    1
    1
    2
    3
    5
    8
    >>>
    

    Ruby

    > a, b = 0, 1
    => [0, 1]
    > while b < 10 do
    *   puts b
    >   a, b = b, a+b
    > end
    1
    1
    2
    3
    5
    8
    => nil
    >
    
    • Python はブロックをインデントで表現する
    • Ruby はブロックを do end で表現する
    • Python の print は Ruby では puts

    Python

    >>> a, b = 0, 1
    >>> while b < 1000:
    ...   print b,
    ...   a, b = b, a+b
    ...
    1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
    >>>
    

    Ruby

    irb(main):035:0> a, b = 0, 1
    => [0, 1]
    irb(main):036:0> while b < 1000 do
    irb(main):037:1*   print b
    irb(main):038:1>   a, b = b, a+b
    irb(main):039:1> end
    1123581321345589144233377610987=> nil
    irb(main):040:0>
    irb(main):050:0> a, b = 0, 1
    => [0, 1]
    irb(main):051:0> while b < 1000 do
    irb(main):052:1*   print "#{b} "
    irb(main):053:1>   a, b = b, a+b
    irb(main):054:1> end
    1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 => nil
    irb(main):055:0>
    
    • Python の “print ,” のような自動空白挿入は Ruby の print では行われない
  • YAML+ERB

    前回の続きで、ERB に埋め込む情報を YAML で記述する実験。

    # config.yml
    books:
    -
    title: あいうえお
    author: かきくけこ
    price: 400
    -
    title: さしすせそ
    author: たちつてと
    price: 800
    
    #!/usr/bin/ruby -Ku
    require 'erb'
    require 'yaml'
    erb_doc = <<EOS
    hello world 1
    <% config['books'].each_with_index do |i,n| -%>
    [<%= n %>] title:<%= i['title'] %> / author:<%= i['author'] %>
    <% end -%>
    hello world 2
    EOS
    class MyTemplate
    def initialize
    @config = YAML.load(File.new('config.yml'))
    end
    def config
    @config
    end
    def result(script)
    ERB.new(script, nil, '-').result(binding)
    end
    end
    puts MyTemplate.new.result(erb_doc)
    # hello world 1
    # [0] title:あいうえお / author:かきくけこ
    # [1] title:さしすせそ / author:たちつてと
    # hello world 2