Open Books

O'Reilly では Open Books といって一部の書籍がまるまる読めるらしい。


concurrency を調べて、groovy and concurrency を見ていたら Groovy の O'Reilly 本が目に入る。
そういえばドイツ語の本があったなということで調べてみる。


というわけで公開中でした。
2007年で Groovy 1.1 のころみたい。
Groovy in Action と同じ時期で内容も似ている気がする。
コードだけしか見てないけどオペレータに in も載ってた。

itertools in Groovy

Generator を手に入れたので、再びハミングの問題に挑戦する。

同じように実装するためには itertools が必要だ。
Python documentation に Python で書かれた仕様が載っているのでそのまま実装する。

Changes

実装する際に Generator の方も変更した。

  • 名前を gen にした
  • worker thread で NoSuchElementException が発生した場合は StopIteration とみなすことにした
  • worker thread の例外を拾って main thread で再スローすることにした(izipLongest で必要)

Itertools

class Itertools {
  static def gen(Closure generator) {
    return new Iterable() {
      @Override Iterator iterator() {
        def queue   = new LinkedList()  // size == 0 or 1
        def getLock = new java.util.concurrent.Semaphore(0)
        def putLock = new java.util.concurrent.Semaphore(0)
        def done    = false
        def error   = null
        def yield = {
          queue.addLast(it)
          getLock.release()
          putLock.acquire()
        }
        def worker = new Thread({
          try {
            putLock.acquire()
            generator(yield)
          } catch (NoSuchElementException stop) {
          } catch (InterruptedException ignore) {
          } catch (Throwable t) {
            error = t
          } finally {
            done = true
            getLock.release()
          }
        })

        worker.daemon = true
        worker.start()
        return new Iterator() {
          @Override void finalize() {
            worker.interrupt()
          }

          @Override boolean hasNext() {
            if (!done && queue.empty) {
              try {
                putLock.release()
                getLock.acquire()
              } catch (InterruptedException e) {
                worker.interrupt()
                return false
              }
              if (error) throw error
            }
            return !done
          }

          @Override def next() {
            if (hasNext()) return queue.removeFirst()
            throw new NoSuchElementException()
          }

          @Override void remove() {
            throw new UnsupportedOperationException()
          }
        }
      }
    }
  }

  static def iter(iterable) {
    return iterable.iterator()
  }

  // heapq
  static def merge(Object... iterables) {
    def iterators = iterables.toList().collect{ iter(it) }
    return gen{ yield ->
      def pqueue = new PriorityQueue(iterators.size(), { a, b -> a[0] <=> b[0] } as Comparator)
      for (iterator in iterators) {
        if (iterator.hasNext()) pqueue.add([iterator.next(), iterator])
      }
      while (!pqueue.empty) {
        def (min, iterator) = pqueue.poll()
        yield(min)
        if (iterator.hasNext()) pqueue.add([iterator.next(), iterator])
      }
    }
  }

  // Infinite Iterators:
  static def count(start = 0, step = 1) {
    def n = start
    return gen{ yield ->
      while (true) {
        yield(n)
        n += step
      }
    }
  }

  static def cycle(iterable) {
    def saved = []
    return gen{ yield ->
      for (element in iterable) {
        yield(element)
        saved << element
      }
      while (saved)
        for (element in saved)
          yield(element)
    }
  }

  static def repeat(object, times = null) {
    return gen{ yield ->
      if (times == null)
        while (true)
          yield(object)
      else
        for (i in 0..<times)
          yield(object)
    }
  }

  // Iterators terminating on the shortest input sequence:
  static def chain(Object... iterables) {
    return gen{ yield ->
      for (iter in iterables)
        for (element in iter)
          yield(element)
    }
  }

  static def compress(data, selectors) {
    return izip(data, selectors).findAll{ d, s -> s }.collect{ d, s -> d }
  }

  static def dropwhile(Closure predicate, iterable) {
    iterable = iter(iterable)
    return gen{ yield ->
      for (x in iterable)
        if (!predicate(x)) {
          yield(x)
          break
        }
      for (x in iterable)
        yield(x)
    }
  }

  static def groupby(iterable, Closure key = Closure.IDENTITY) {
    def iterator = iter(iterable)
    def target   = new Object()
    def current  = target
    def value    = target
    def queue    = [] as Queue
    def done     = false
    return new Iterator() {
      @Override boolean hasNext() {
        if (!done && queue.empty) {
          try {
            while (current == target) {
              value   = iterator.next()
              current = key(value)
            }
            target = current
            def group  = gen{ yield ->
              while (current == target) {
                yield(value)
                value   = iterator.next()
                current = key(value)
              }
            }
            queue << [current, group]
          } catch (NoSuchElementException stop) {
            done = true
          }
        }
        return !queue.empty
      }

      @Override def next() {
        if (hasNext()) return queue.remove()
        throw new NoSuchElementException()
      }

      @Override void remove() {
        throw new UnsupportedOperationException()
      }
    }
  }

  static def ifilter(predicate = { it as boolean }, iterable) {
    return gen{ yield ->
      for (x in iterable)
        if (predicate(x))
          yield(x)
    }
  }

  static def ifilterfalse(predicate = { it as boolean }, iterable) {
    return gen{ yield ->
      for (x in iterable)
        if (!predicate(x))
          yield(x)
    }
  }

  static def islice(iterable, Object... args) {
    def (start, stop, step) = args.size() == 1 ? [null, args[0], null] : [*args]
    start = start != null ? start : 0
    stop  = stop  != null ? stop  : Integer.MAX_VALUE
    step  = step  != null ? step  : 1
    def iterator = iter(gen{ yield ->
      for (int i = start; i != stop; i += step)
        yield(i)
    })
    int nexti = iterator.next()
    return gen{ yield ->
      iterable.eachWithIndex{ element, i ->
        if (i == nexti) {
            yield(element)
            nexti = iterator.next()
        }
      }
    }
  }

  static def imap(Closure function = null, Object... iterables) {
    iterables = iterables.collect{ iter(it) }
    return gen{ yield ->
      while (true) {
        def args = iterables.collect{ it.next() }
        if (function == null)
          yield(args)
        else
          yield(function(*args))
      }
    }
  }

  static def starmap(Closure function, iterable) {
    return gen{ yield ->
      for (args in iterable)
        yield(function(*args))
    }
  }

  static def tee(iterable, int n = 2) {
    def iterator = iter(iterable)
    def queues   = (0..<n).collect{ [] as Queue }
    return queues.collect{ myqueue ->
      return gen{ yield ->
        while (true) {
          if (myqueue.empty) {
            def newval = iterator.next()
            for (d in queues)
              d << newval
          }
          yield(myqueue.remove())
        }
      }
    }
  }

  static def takewhile(Closure predicate, iterable) {
    return gen{ yield ->
      for (x in iterable)
        if (predicate(x))
          yield(x)
        else
          break
    }
  }

  static def izip(Object... iterables) {
    def iterators = iterables.toList().collect{ iter(it) }
    return gen{ yield ->
      while (iterators)
        yield(iterators.collect{ it.next() })
    }
  }

  static class ZipExhausted extends RuntimeException {}
  static def izipLongest(Map keywords = [:], Object... args) {
    def fillvalue = keywords.get('fillvalue')
    def counter   = args.size() - 1
    def sentinel = {
      return gen{ yield ->
        if (!counter) throw new ZipExhausted()
        counter -= 1
        yield(fillvalue)
      }
    }
    def filters   = repeat(fillvalue)
    def iterators = args.collect{ iter(chain(it, sentinel(), filters)) }
    return gen{ yield ->
      try {
        while (iterators)
          yield(iterators.collect{ it.next() })
      } catch(ZipExhausted ignore) {}
    }
  }

  // Combinatoric generators:
  static def product(Map keywords = [:], Object... args) {
    def pools  = args.toList().collect{ it as List } * keywords.get('repeat', 1)
    def result = [[]]
    for (pool in pools)
      result = gen{ yield ->
        for (x in result)
          for (y in pool)
            yield(x+[y])
      }.collect()
    return gen{ yield ->
      for (prod in result)
        yield(prod)
    }
  }

  static def permutations(iterable, r = null) {
    def pool = iter(iterable).collect()
    def n    = pool.size()
    r = r == null ? n : r
    return gen{ yield ->
      for (indices in product(0..<n, repeat:r))
        if (indices.unique().size() == r)
          yield(pool[indices])
    }
  }

  static def combinations(iterable, r = null) {
    def pool = iter(iterable).collect()
    def n    = pool.size()
    return gen{ yield ->
      for (indices in permutations(0..<n, r))
        if (indices.sort(false) == indices)
          yield(pool[indices])
    }
  }

  static def combinationsWithReplacement(iterable, r) {
    def pool = iter(iterable).collect()
    def n    = pool.size()
    return gen{ yield ->
      for (indices in product(0..<n, repeat:r))
        if (indices.sort(false) == indices)
          yield(pool[indices])
    }
  }
}

itertools 以外のメソッドで iter と merge を用意した。

Test

import static Itertools.*

assert iter(merge([1,3,5,7],[2,4],[6])).collect() == [1,2,3,4,5,6,7]

// Infinite Iterators:
assert iter(count(10)).take(5).collect() == [10,11,12,13,14]
assert iter(cycle("ABCD")).take(12).collect() == ['A','B','C','D','A','B','C','D','A','B','C','D']
assert repeat(10,3).collect() == [10,10,10]

// Iterators terminating on the shortest input sequence:
assert chain('ABC', 'DEF').collect() == ['A','B','C','D','E','F']
assert compress("ABCDEF", [1,0,1,0,1,1]) == ['A','C','E','F']
assert dropwhile({ it < 5 }, [1,4,6,4,1]).collect() == [6,4,1]
assert groupby("AAAABBBCCDAABBB").collect{ k, g -> k } == ["A","B","C","D","A","B"]
assert groupby("AAAABBBCCD").collect{ k, g -> g.collect().join() } == ["AAAA","BBB","CC","D"]
assert ifilter({ it % 2 }, 0..<10).collect() == [1,3,5,7,9]
assert ifilterfalse({ it % 2 }, 0..<10).collect() == [0,2,4,6,8]
assert islice("ABCDEFG", 2).collect() == ['A','B']
assert islice("ABCDEFG", 2, 4).collect() == ['C','D']
assert islice("ABCDEFG", 2, null).collect() == ['C','D','E','F','G']
assert islice("ABCDEFG", 0, null, 2).collect() == ['A','C','E','G']
assert imap(Math.&pow, [2,3,10], [5,2,3]).collect() == [32,9,1000]
assert starmap(Math.&pow, [[2,5], [3,2], [10,3]]).collect() == [32,9,1000]
assert takewhile({ it < 5 }, [1,4,6,4,1]).collect() == [1,4]
assert izip("ABCD", "xy").collect()*.join() == ["Ax","By"]
assert izipLongest("ABCD", "xy", fillvalue:'-').collect()*.join() == ["Ax","By","C-","D-"]

// Combinatoric generators:
assert product("ABCD", "xy").collect()*.join() == ["Ax","Ay","Bx","By","Cx","Cy","Dx","Dy"]
assert product(0..<2, repeat:3).collect()*.join() == ["000", "001", "010", "011", "100", "101", "110", "111"]
assert permutations("ABCD", 2).collect()*.join() == ["AB","AC","AD","BA","BC","BD","CA","CB","CD","DA","DB","DC"]
assert permutations(0..<3).collect()*.join() == ["012","021","102","120","201","210"]
assert combinations("ABCD", 2).collect()*.join() == ["AB","AC","AD","BC","BD","CD"]
assert combinations(0..<4, 3).collect()*.join() == ["012","013","023","123"]
assert combinationsWithReplacement("ABC", 2).collect()*.join() == ["AA","AB","AC","BB","BC","CC"]

named argument は product で気づいた。
それまでは List で渡していたので Python 風になって満足。

Cyclical Iterators

import static Itertools.*

def raymonds_hamming() {
  def output
  def deferred_output = gen{ yield ->
    for (i in output) yield(i)
  }
  def (result, p2, p3, p5) = tee(deferred_output, 4)
  def m2 = gen{ for (x in p2) it(2*x) }
  def m3 = gen{ for (x in p3) it(3*x) }
  def m5 = gen{ for (x in p5) it(5*x) }
  def merged = merge(m2, m3, m5)
  def combined = chain([1G], merged)
  output = gen{ groupby(combined).each{ k, g -> it(k) } }
  return result
}

assert iter(raymonds_hamming()).take(20).collect() == [1,2,3,4,5,6,8,9,10,12,15,16,18,20,24,25,27,30,32,36]
assert iter(islice(raymonds_hamming(), 1690, 1691)).next() == 2125764000
assert iter(raymonds_hamming())[1690] == 2125764000
// assert iter(islice(raymonds_hamming(), 999999, 1000000)).next() == 519312780448388736089589843750000000000000000000000000000000000000000000000000000000

まわった!
ただ、Python に比べると圧倒的に遅い。
そもそも Python で書かれた仕様のための実装と、最適化された実装の itertools の違いもある。
ただ、以前書いた無限リスト版に比べると速いし1000000番目以外は気にならない。

終わりに

TODO をいくつか

  • 遅い原因の一つはおそらく Thread でそれ自体は仕方が無いことだが、ThreadPool を使用すれば多少改善されると思われる
  • Iterable で返しているので2度 iterator を呼び出すことができるがそのときの振る舞いが正しいのかわからない
def c = count(10)
assert c in Iterable
assert takewhile({ it < 15 }, c).collect() == [10,11,12,13,14]
assert takewhile({ it < 15 }, c).collect() == []  // or [10,11,12,13,14] ?

count の場合 Python でもこのように動作したが、他は確認していない。

  • generator の delegate に何とかすれば yield を引数で渡す必要がなくなるが delegate を塞ぐほどのことではないと判断してやめた。


とりあえず、Python と同じように記述できるようになっただけでうれしい。

2012-04-18 更新

permutations の引数が 0 のときの動作が違ったので修正した

for (x in permutations([1,2,3], 0)) println x
// 修正前
// [1, 2, 3]
// [1, 3, 2]
// [2, 1, 3]
// [2, 3, 1]
// [3, 1, 2]
// [3, 2, 1]

// 修正後
// []

Named arguments

コンパイラさんに教えてもらった。

def aMethodHasNamedArguments(Map named=[:], Object... args) {
  println "named=$named, args=$args"
}

aMethodHasNamedArguments("A", key1:1, "B", key2:2, "C")
// named=[key1:1, key2:2], args=[A, B, C]

aMethodHasNamedArguments()
// named=[:], args=[]


Groovy 1.8.6 の AntlrParserPlugin より

        if (namedArguments) {
            if (!expressionList.isEmpty()) {
                // let's remove any non-MapEntryExpression instances
                // such as if the last expression is a ClosureExpression
                // so let's wrap the named method calls in a Map expression
                List<Expression> argumentList = new ArrayList<Expression>();
                for (Object next : expressionList) {
                    Expression expression = (Expression) next;
                    if (!(expression instanceof MapEntryExpression)) {
                        argumentList.add(expression);
                    }
                }
                if (!argumentList.isEmpty()) {
                    expressionList.removeAll(argumentList);
                    checkDuplicateNamedParams(elist, expressionList);
                    MapExpression mapExpression = new MapExpression(expressionList);
                    configureAST(mapExpression, elist);
                    argumentList.add(0, mapExpression);
                    ArgumentListExpression argumentListExpression = new ArgumentListExpression(argumentList);
                    configureAST(argumentListExpression, elist);
                    return argumentListExpression;
                }
            }
            checkDuplicateNamedParams(elist, expressionList);
            NamedArgumentListExpression namedArgumentListExpression = new NamedArgumentListExpression(expressionList);
            configureAST(namedArgumentListExpression, elist);
            return namedArgumentListExpression;
        } else {


名前付き引数は先に処理されるようだ。

Generator in Groovy

同期する Generator を java.util.concurrent パッケージを使って書いてみる。

Using BlockingQueue

はじめに http://groovy.codehaus.org/Iterator+Tricks のように非同期のものを BlockingQueue で書いてみる。

def generate(Closure generator) {
  return new Iterable() {
    @Override Iterator iterator() {
      def queue = new java.util.concurrent.LinkedBlockingQueue(1)
      def done  = false
      def yield = { queue.put(it) }
      def worker = new Thread({
        try {
          generator(yield)
        } catch (InterruptedException ignore) {
        } finally {
          done = true
        }
      })
 
      worker.daemon = true
      worker.start()
      return new Iterator() {
        @Override void finalize() {
          worker.interrupt()
        }
 
        @Override boolean hasNext() {
          return !done || !queue.empty
        }
 
        @Override def next() {
          if (hasNext()) {
            try {
              return queue.take()
            } catch (InterruptedException ignore) {}
          }
          throw new NoSuchElementException()
        }
 
        @Override void remove() {
          throw new UnsupportedOperationException()
        }
      }
    }
  }
}
def fibo(yield) {
  def a = 0
  def b = 1
  while (true) {
    yield(b)
    (a, b) = [b, a+b]
  }
}

assert generate(this.&fibo).find{ it % 20 == 0 } == 832040

フィボナッチ数を求めるような場合は問題ないが PythonJavaScript の yield と同じようには扱えない。

なぜ同じように扱えないのか?

問題点その1 iterator() を呼び出した時点で worker thread が起動する

def counter = generate { yield ->
  for (i = 0; ; i++) {
    println "put $i"
    yield(i)
  }
}.iterator()
// iterator() を呼び出した時点で put している
// put 0
// put 1


問題点その2 put が先行する

def counter = generate { yield ->
  for (i = 0; ; i++) {
    println "put $i"
    yield(i)
  }
}.iterator()
5.times {
  println "get ${counter.next()}"
}
// get の回数より put の回数が多い
// put 0
// get 0
// put 1
// put 2
// put 3
// get 1
// get 2
// get 3
// put 4
// put 5
// put 6
// get 4


問題点その3 BlockingQueue に null を渡せない

for (x in generate{ it(null) }) println x
// Exception in thread "Thread-2" java.lang.NullPointerException

Using CountDownLatch

問題点1を解決する。
CountDownLatch は合図があるまでスレッドを待機させることができるのでこれを使う。
Worker Thread をスレッド起動直後に待機させ、最初の hasNext() 呼び出しで合図を送って開始させる。

def generate(Closure generator) {
  return new Iterable() {
    @Override Iterator iterator() {
      def queue  = new java.util.concurrent.LinkedBlockingQueue(1)
      def signal = new java.util.concurrent.CountDownLatch(1)
      def start  = false
      def done   = false
      def yield  = { queue.put(it) }
      def worker = new Thread({
        try {
          signal.await()
          generator(yield)
        } catch (InterruptedException ignore) {
        } finally {
          done = true
        }
      })
  
      worker.daemon = true
      worker.start()
      return new Iterator() {
        @Override void finalize() {
          worker.interrupt()
        }
  
        @Override boolean hasNext() {
          if (!start) {
            start = true
            signal.countDown()
          }
          return !queue.empty || !done
        }
  
        @Override def next() {
          if (hasNext()) {
            try {
              return queue.take()
            } catch (InterruptedException ignore) {}
          }
          throw new NoSuchElementException()
        }
  
        @Override void remove() {
          throw new UnsupportedOperationException()
        }
      }
    }
  }
}

Using SynchronousQueue & Semaphore

問題点2を解決する。
BlockingQueue の実装クラスには SynchronousQueue という常に空のキューがある。
常に空でキューなのかということだが put したと同時に take するので常に空ということらしい。
これで解決したかと思ったのだけども LinkedBlockingQueue で1個置き場所があって最大2個ずれていたので置き場所がなくなっても1個ずれる。
結局、Semaphore を使ってスレッドが同時に2つ動作しないように管理しないといけない。
Semaphore は開始時の制御にも使えるので CountDownLatch はもう使用しない。
Worker Thread の開始直後と、put 直後は Worker Thread を一度止めて、次の hasNext が呼ばれるまで待機するようにする。

def generate(Closure generator) {
  return new Iterable() {
    @Override Iterator iterator() {
      def queue = new java.util.concurrent.SynchronousQueue()
      def lock  = new java.util.concurrent.Semaphore(0)
      def value = []
      def DONE  = new Object()
      def yield = {
        queue.put(it)
        lock.acquire()
      }
      def worker = new Thread({
        try {
          lock.acquire()
          generator(yield)
        } catch (InterruptedException ignore) {
        } finally {
          queue.put(DONE)
        }
      })
    
      worker.daemon = true
      worker.start()
      return new Iterator() {
        @Override void finalize() {
          worker.interrupt()
        }
    
        @Override boolean hasNext() {
          if (value.empty) {
            try {
              lock.release()
              value[0] = queue.take()
            } catch (InterruptedException e) {
              value[0] = DONE
            }
          }
          return value[0] != DONE
        }
  
        @Override def next() {
          if (hasNext()) return value.remove(0)
          throw new NoSuchElementException()
        }
    
        @Override void remove() {
          throw new UnsupportedOperationException()
        }
      }
    }
  }
}

Using Two Semaphores

問題点3を解決する。
そもそも BlockingQueue には null を渡すことができない。
変わりに LinkedList を使い BlockingQueue が行っていた同期処理をもうひとつの Semaphore で行う。

def generate(Closure generator) {
  return new Iterable() {
    @Override Iterator iterator() {
      def queue   = new LinkedList()  // size == 0 or 1
      def getLock = new java.util.concurrent.Semaphore(0)
      def putLock = new java.util.concurrent.Semaphore(0)
      def done    = false
      def yield = {
        queue.addLast(it)
        getLock.release()
        putLock.acquire()
      }
      def worker = new Thread({
        try {
          putLock.acquire()
          generator(yield)
        } catch (InterruptedException ignore) {
        } finally {
          done = true
          getLock.release()
        }
      })
    
      worker.daemon = true
      worker.start()
      return new Iterator() {
        @Override void finalize() {
          worker.interrupt()
        }
    
        @Override boolean hasNext() {
          if (!done && queue.empty) {
            try {
              putLock.release()
              getLock.acquire()
            } catch (InterruptedException e) {
              worker.interrupt()
              return false
            }
          }
          return !done
        }
    
        @Override def next() {
          if (hasNext()) return queue.removeFirst()
          throw new NoSuchElementException()
        }
    
        @Override void remove() {
          throw new UnsupportedOperationException()
        }
      }
    }
  }
}

同期しながら動作し、null を渡すことも可能

def counter = generate { yield ->
  for (i = 0; ; i++) {
    println "put $i"
    yield(i)
  }
}.iterator()
5.times {
  println "get ${counter.next()}"
}
// put と get は1対1で同期している
// put 0
// get 0
// put 1
// get 1
// put 2
// get 2
// put 3
// get 3
// put 4
// get 4

assert generate{ it(null) }).iterator().next() == null

使ってみる

def counter = generate { yield ->
  (1..5).each{ yield(it) }
}

for (i in counter) println i

// counter は Iterable なので再利用可能
for (i in counter) println i

// リスト内包表記のようにも書ける
println generate{ for (x in 1..3) for (y in 1..3) it(x*y) }.iterator().take(10).collect()

// この場合は iterator の方が都合がよいのでメソッドを定義する
def gen(Closure generator) {
  generate(generator).iterator()
}

println gen{ for (x in 1..3) for (y in 1..3) it(x*y) }.take(10).collect()

// ただし、これは Ruby の tap のようなものがあれば同じように書けてしまう
def tap(obj, Closure doto) {
  obj.with(doto)
  return obj
}

println tap([]){ for (x in 1..3) for (y in 1..3) it << x*y }

終わりに

Generator を実装するのと concurrent パッケージを使用するという両方の目的で書いてみた。

TiddlyWiki の設定

TiddlyWiki の Tips を調べていたら、設定(使用しているプラグラインとか)を公開されている方がいたので自分も公開してみる。

TiddlyWiki とは

  • 開発者は Jeremy Ruston 氏
  • HTML ファイル1つでできている Wiki なので tiddlywiki.com からダウンロードしてくるだけで使える

この説明がわかりやすいかも

TiddlyWiki を何に使っているのか

TiddlyWiki はいろいろな用途で使われているけれども自分の場合はプログラミング関連の本やブログを読みながらメモするのに使っている。*1

Write once, read anytime

自分の TiddlyWiki のサブタイトルに書いてあった。*2
読んでみて、コードを書いてみて、実行して、まとめてみないと理解できない体質なので、本に書かれたコードを打ち込むのだけど、それを残している。
そして、必要になった時に貼り付けて実行。


例えば、今『プログラミング Clojure』を読んでいてこんな感じ。

(def foo 10)
; => #'user/foo
 
foo
; => 10
 
(.start (Thread. (fn [] (println foo))))
; => nil
; 10
 
; Clojure 1.3 から var はデフォルトでは dynamic ではない
(binding [foo 42] foo)
; => IllegalStateException Can't dynamically bind non-dynamic var: user/foo
 
; foo を dynamic にする
; (def #^{:dynamic true} foo foo)
 
; dynamic な foo を定義する
(def ^:dynamic foo 10)
; => #'user/foo
 
(binding [foo 42] foo)
; => 42
 
; let と binding の違いを見る
(defn print-foo [] (println foo))
; => #'user/print-foo
 
; let は自分のフォームの外側には影響を及ぼさない
; ルート束縛である 10 を表示する
(let [foo "let foo"] (print-foo))
; 10
; => nil
 
; binding は束縛の影響をそのボディから呼び出されるコードすべてに及ぶ
(binding [foo "bound foo"] (print-foo))
; bound foo
; => nil

使用中のプラグイン

tiddlyspace.com
  • コードが読みやすいように
  • はてなスーパーpre記法ほど種類がないが追加できる
  • ハイライトが存在しないとエラーになる
  • ハイライトが用意できたら適用されるように記述するときにとりあえず空で言語を増やしておくか運用を考慮中
SectionLinksPlugin
  • 本を読むときは目次が重要なので
NestedSlidersPlugin
  • コードが長かったりすると閉じたり開いたりできる方が便利
http://www.math.ist.utl.pt/~psoares/MathSVG.html
  • ときには数式が必要になることもあるので
Cafe de Shrimp
  • やるべきことにこれを適用すると大変なことになるので仕掛かり中のものに適用している
  • 先程の『プログラミングClojure』の例では書籍のバージョンではなく Clojure 1.3 で実行したのでエラーになった。その時点で一度 TODO にしておき、調べてわかったらそこから再開した。


今回調べていてタブ化するプラグインが便利そうだったので入れるかもしれない。*3

タグの運用

現在、3つの軸で管理している。

  • 言語 (.clj とか拡張子)
  • ジャンル (algorithm, tool とか全部小文字)
  • タイトル (書籍やブログのタイトルなど、大文字から始める)


『プログラミング Clojure』では章ごとに管理した。
以前は1つにしていたのだが、途中に書き加えるときに不便だったのでできるだけ小分けにするようにしている。
「Programming Clojure」 という tiddler を作成して、書籍に関連するサイトなどのリンクを書いておく。*4
そして同じ名前でタグ付け。*5

[[Programming Clojure]] .clj

各章ごとには

[[Programming Clojure]]

だけタグ付けしている。

TiddlyWiki ではタグにタグを付けられる。
タグ名の tiddler を用意するとそのタグでタグ付けされている一覧が表示されるので目次代わりにもなる。

終わりに

TiddlyWiki 使い始めた当初はインストールログやブックマークのまとめだけだったので、書いていることも変わってきている。
運用してみて、自分で書いたものを自分で読んでみることで自分のスタイルに合った使い勝手になってきた。


注意点としてはプラグインごとに記法がある場合、量が増えるに従ってプラグインの変更がしにくくなる。
両方適用するか、テキストファイルなので置換するという手もあるが最初から意識しておいた方がいいかもしれない。
自分の場合はプラグインJavaScript を修正したので今度はプラグインの更新が簡単にできなくなった。


もうひとつ Wiki なのでコンソールに貼り付けないとコードは実行できないという欠点がある。
このあたりは高機能エディタとかで snippet を管理する方が優れているのかもしれない。*6


とはいえ、非常に満足しているツールなので自分の設定と運用法を書いてみた。

2012-03-26 追記

タブ化プラグインは便利なので入れた。

前回表示されなかったのはスタイルの設定が足りなかったかららしい。


Dan Ariely 氏の『予想どおりに不合理』を読んで面白かったのでもう少し追記しておく。
氏の実験と観察から得られた結果を自分なりにまとめると

  1. 人間は相対評価しかできない
  2. 目先のことで判断を誤るのは優秀な頭脳でも同じ

の2点だ。
2番目は特筆に価する。
理解すること自体が難しい問題はともかく、問題に対する判断力は頭脳で決まるわけではないということだ。
結局地道な相対評価を繰り返して価値観を高めていくしかない。

本読んでまとめるのはアンチパターンの部分もあるかもしれないけど継続できているので今の自分に合っている。
参照したときに自分で書いたものを評価することになるので成長できているか判断できる点もいいと思った。

*1:プログラミング以外の本用もあるけど

*2:昔なのではっきりしないが Java のをもじったようだ

*3:入れてみたがスタイルシートのためかタブが隠れてしまったのでまだ

*4:2nd edition がでるらしい

*5:空白があるので括弧が必要

*6:ブログとか読んでいるとそうやって管理しているんだろうなと

Groovy で Emulating callable objects その2

前回の Closure よりもっと簡単にできた。
というか Python と同じだった。

Python と同じ方法

org.codehaus.groovy.runtime.ScriptBytecodeAdapter より

    // TODO: set sender class
    public static Object invokeClosure(Object closure, Object[] arguments) throws Throwable {
        return invokeMethodN(closure.getClass(), closure, "call", arguments);
    }


つまり、call メソッドが実装されていれば呼び出せるようだ。
これなら Closure 以外のインスタンスとして扱える。

class HasCallMethod {
  def call(int i) { i+1 }
}

def obj = new HasCallMethod()
assert obj instanceof Closure == false
assert obj(1) == 2


ちなみに、Closure には doCall を呼び出す call メソッドが最初から存在するので doCall で拡張できる。
正規の方法かは不明だが、調べたら使っている人達もいるようだ。

前回の TODO: Generator が動作しなかった理由*1 *2

Iterator#next が呼ばれたから次の値を取得するのではなく、next の前に値が用意されることもある。
したがって、副作用を期待するとエラーになる。

N = 10

i = 0
N.times{ i++ }
assert N == i

i = 0
interator = new Generator({ yield ->
  while(true) {
    i++
    println(i)
    yield(i)
  }
}).iterator()

N.times{
  println "next()"
  interator.next()
}
assert i == N   // error: N <= i <= N+2

出力
pullValue() と doCall() は同期の中にログを仕込んだ。

next()
1
doCall(1)
2
pulledValue = 1
doCall(2)
3
next()
pulledValue = 2
next()
doCall(3)
4
pulledValue = 3
next()
doCall(4)
5
pulledValue = 4
next()
doCall(5)
6
pulledValue = 5
next()
doCall(6)
7
pulledValue = 6
next()
doCall(7)
8
pulledValue = 7
doCall(8)
9
next()
pulledValue = 8
next()
doCall(9)
10
pulledValue = 9
next()
doCall(10)
11
pulledValue = 10
doCall(11)
12

doCall() が呼ばれて pulledValue が呼ばれる順序は必ず守られる。
ただ pulledValue が参照されると次の doCall を呼び出してよくなり、yield でブロックされる前に評価されるので最大2ずれる。


fibonacci 数の計算では副作用がないのでうまく動作したが Tiny Prolog では env を副作用を参照するのでエラーになってしまった。

*1:エントリ書いたので前回への追記からこっちへ移動した

*2:http://groovy.codehaus.org/Iterator+Tricks の Generator のこと

Groovy で Emulating callable objects

Python では object.__call__ を定義するとオブジェクトを呼び出せるそうだ。

Scala でも apply メソッドで同じようにオブジェクトを呼び出すことができる。
Groovy で同じようにするにはどうすればよいか。

そもそもの問題

『7つの言語 7つの世界』で Prolog の章を読んで面白かったのでいろいろ探していたらOKIソフトウェアの鈴木氏の Tiny Prolog シリーズを発見した。

他に Ruby, Java, C# 版もある。
理解するために Groovy に翻訳していた。
ここで object.__call__ が使われていて、別に Groovy では object._() のように目立たないメソッド呼出しにすればいいだけなのだが悔しいので粘って見た。
むしろ問題は Generator の方なんだけどこっちは検索して見たら Java で実装されたものが見つかったのでそれを使った。
Groovy の Cookbook Examples にも Generator が載っているがこれではうまく動かなかった。*1

ログが実行毎に違ったりしたのでスレッドの競合かもしれない。
でもコードを読んでも pullValue() と doCall() は排他できていてるように見える。
何故うまく動かないのか原因がわからない。
それは TODO にしておいて Groovy で Object を呼び出すにはどうすればよいか。

Closure#setDelegate(object)

Groovy では Object を呼び出すことはできないのだけど Closure が属している Object を変更できることを思い出した。
いつもは Builder で使うときのように Closure の内側から delegate を呼び出すが、外側からも呼び出せたのだ。

class Person {
  def name
  @Override String toString() { name }
}

def robert = new Person(name:"Robert")
println robert
println()


println "// new Closure(owner /*, null*/)"
bob = new Closure(robert) {
  def doCall() { "Hi!" }
  def doCall(String s) { "My name is $name." }
//  def doCall(String s) { "My name is ${this.name}." }   Error
  @Override String toString() { "Bob" }
}
println bob
println bob()
println bob("What's your name?")
println bob.name
println()


println "// new Closure(null, thisObject)"
bob = new Closure(null, robert) {
  def doCall() { "Hi!" }
//  def doCall(String s) { "My name is $name." }  Error
  def doCall(String s) { "My name is ${this.name}." }
  @Override String toString() { "Bob" }
}
println bob
println bob()
println bob("What's your name?")
// println bob.name   Error
println()


println "// Closure#setDelegate(obj)"
bob = { "My name is ${name}." }
// bob = { "My name is ${this.name}." }  Error
bob.delegate = robert
println bob
println bob()
println bob("What's your name?")
println bob.name
println()
Robert

// new Closure(owner /*, null*/)
Bob
Hi!
My name is Robert.
Robert

// new Closure(null, thisObject)
Bob
Hi!
My name is Robert.

// Closure#setDelegate(obj)
emulating_callable_objects$_run_closure1@2f60877b
My name is Robert.
My name is Robert.
Robert

Closure の doCall をオーバーロードすれば呼び出しのパターンも増やせる。
ただし、instanceof で調べても Closure でしかないが今回は問題ない。
Person が Closure を継承するようにしてもよいが Closure は特別なクラスなので簡単には拡張できない。

Closure が static コンテキストで生成された場合

this に何が設定されているか気になったのでついでに調べて見た。

class A {
  def foo() {
    return {}
  }
  static def bar() {
    return {}
  }
}
 
def a = new A()
 
assert a.foo().owner == a
assert A.bar().owner == A
assert a.foo().thisObject == a
assert A.bar().thisObject == A
assert a.foo().delegate == a.foo().owner
assert A.bar().delegate == A.bar().owner

static コンテキストで生成された場合は、生成したクラスが設定されている。

追記 2012-02-25

環境は Groovy 1.8.6 で確認
『Groovy・イン・アクション』によると JSR になる前の Groovy では Closure 内の this は Closure 自身を参照していたらしい。
JSR エキスパートグループによって現在の形に変更されたそうだ。
Groovy 1.8.6 では Ruby の each_cons や each_slice の動作をする List#collate が追加されていた。
未だに startGroovy.bat の問題がそのままだが patch 当てれば OK

*1:掲載されているサンプルは動作する