読者です 読者をやめる 読者になる 読者になる

Line 1: Error: Invalid Blog('by Esehara' )

または私は如何にして心配するのを止めてバグを愛するようになったか

>> Zanmemo

あと何かあれは 「esehara あっと じーめーる」 か @esehara まで

イケてないRubyのコードのリファクタリングって奴をSmallTalkでやってみる

本日の料理

f:id:nisemono_san:20160722160944j:plain

コードも豚肉も煮こむに限る

はじめに

Rubyのリファクタリングでイケてないコードを美しいオブジェクト指向設計のコードへ改良するための方法 - その1 - Ruby on Railsのビシバシはぁはぁ日記というブログの記事がどうもバズっていて、色んな方面からつっこみが入っていたりする。また、別にこれはオーバーエンジニアリングだから、この程度のコードでもいいのではないか、という指摘も挙がっている。

コードを書く人によって「これが良いアプローチだ」というのは千差万別であることは間違いない。元の記事を読む限り、これはどうもRubyの特殊事情なのではないかという疑問が拭えない。そこで、あえて本家オブジェクト指向環境(言語とは言わないマン)であるところのPharo(SmallTalk環境)を利用して、SmallTalkだと、どういう風にリファクタリングできるかを考えてみたい。

「ダメなコード」と言われるものを移植する

まず、元の記事になっているダメなコードを移植する。コードの意図によれば、商品のオーダを、範囲によって絞りこみ、それを追加するという方針を取っている。

ちなみに、一般的な言語のinitialize的なメソッドは、System Browserの中ほどにあるClass sideというものを使って切りかえ、それによってインスタンス生成用のメソッドを用意してあげるのが普通のようだ。画像にすると、下の部分をトグルする。

f:id:nisemono_san:20160723180030p:plain

そして、下のようなメソッドを生成する。

amount: money year: year month: month day: day
    |newOrder|
    newOrder := self new.
    newOrder amount: money;
            place_at: (Date year: year month: month day: day).
    ^newOrder

こうすることによって:

orders := { 
    Order amount: 3000 year: 2016 month: 7 day: 23.
    Order amount: 4000 year: 2016 month: 7 day: 21.
    Order amount: 5000 year: 2016 month: 7 day: 19.
    Order amount: 6000 year: 2016 month: 7 day: 10.
}.

のような、自然な書き方ができるのだが、本体はそっちではなく、このオーダーリストの中から、「7月10日から7月22日」のような間でおこなわれた金額を算出したいということである。ちなみに、元ブログでのイケてないコードは下のようになる:

class OrdersReport
  def initialize(orders, start_date, end_date)
    @orders = orders
    @start_date = start_date
    @end_date = end_date
  end

  def total_sales_within_date_range
    orders_within_range = []
    @orders.each do |order|
      if order.placed_at >= @start_date && order.placed_at <= @end_date
        orders_within_range << order
      end
    end

    sum = 0
    orders_within_range.each do |order|
      sum += order.amount
    end
    sum
  end
end

これを愚直に移植すると次のようになる。

orders: orders start_at: start_date end_at: end_date
    |amountSum targetOrders|

    (start_date class = Date) ifFalse: [ self signalInvalidDate ].
    (end_date class   = Date) ifFalse: [ self signalInvalidDate ].
    
    targetOrders := OrderedCollection new.
    orders do: [ :order | 
        ((order place_at > start_date) and: (order place_at < end_date)) 
        ifTrue: [targetOrders add: order]].
    
    amountSum := 0.
    targetOrders do: [ :order | amountSum := (order amount) + amountSum].
    
    ^ amountSum

まず、このコードの何処が駄目だろうか。

前提として、SmallTalkのリテラルでの配列はイミュータブルである。つまり、追加したり、変更したりすることが不可能である。そのため、別途にOrderedCollectionというクラスを使う必要があるのだが、ただこれが出てきたときには、まず「何か変なことをしているんじゃないんだろうか」と疑ったほうがいい可能性がある。

実際に、あとのコードで、条件に当てはまるものを追加するようなコードとなっていて、それをさらに集計しているわけだから二度手間であることは間違いない。

orders: orders start_at: start_date end_at: end_date
    |amountSum targetOrder|

    (start_date class = Date) ifFalse: [ self signalInvalidDate ].
    (end_date class   = Date) ifFalse: [ self signalInvalidDate ].
    
     targetOrder := orders select: [ :order | 
        ((order place_at > start_date) and: (order place_at < end_date))].
    
    amountSum := (targetOrder inject: 0 into: [:sum :order | sum + (order amount)]).    
    ^ amountSum

さて、ここで気になるところが一つある。それは判定式がやたらと長いことだろう。これをもうすこし直感的にするために、メソッドを定義したほうがよいだろう。

元の記事を読むと、このあたりの責務を、OrdersReportの責務にしているわけなんだけれども、これにはちょっと違和感がある。なぜなら、ある時間と時間の範囲に、そのオーダーが属しているかどうかの判断は、オーダーによって判定するほうが筋がいいのではないだろうか。そうすれば、オーダーと、それを集計するクラスとの結合が少なくなる。従って、Orderクラスには次のようなメソッドをはやし:

inRange: start_date end_at: end_date
    ^ ((self place_at > start_date) and: (self place_at < end_date))

OrderCalclatorのメソッドは次のように変更する。

orders: orders start_at: start_date end_at: end_date
    |amountSum targetOrder|

    (start_date class = Date) ifFalse: [ self signalInvalidDate ].
    (end_date class   = Date) ifFalse: [ self signalInvalidDate ].
    
    targetOrder := orders select: [ :order | order inRange: start_date end_at: end_date ].
    
    amountSum := orders inject: 0 into: [:sum :order | sum + (order amount)]).
    ^ amountSum

だいぶ、見通しが良くなってきた。さらに、amountSumの部分に関しても、メソッド化して、簡潔な記述にしておこう。

orders: orders start_at: start_date end_at: end_date
    | targetOrder|

    (start_date class = Date) ifFalse: [ self signalInvalidDate ].
    (end_date class   = Date) ifFalse: [ self signalInvalidDate ].
    
    targetOrder := orders select: [ :order | order inRange: start_date end_at: end_date ].
    ^ self sumOrder: targetOrder

さらに、別に代入する必要もなくなったので

orders: orders start_at: start_date end_at: end_date
    (start_date class = Date) ifFalse: [ self signalInvalidDate ].
    (end_date class   = Date) ifFalse: [ self signalInvalidDate ].
    ^ self sumOrder: (orders select: [ :order | order inRange: start_date end_at: end_date ]).

というわけで、三行になったわけである。めでたしめでたし。

ちょっと違和感のあったところ

さて、元記事ではさらなる提案として、start_atend_atの引数が必要であるのにも関わらず明示されていないとして、DateRangeというクラスを提唱しているが、自分の目からすればオーバーであるように感じられる。

「これをある程度の規模のプロジェクトと想定し、再利用性」という側面から見ているのだけれども、しかしこの部分だけみればYAGNI原則に引っかかっている印象に見える。また、クラスを挟むことによって、同時に明晰性が失われているように感じる。例えばこのコードの部分を見てみよう:

class OrdersReport
  def initialize(orders, date_range)
    @orders = orders
    @date_range = date_range
  end

  # ...
end

ここに出てくるdate_rangeという変数とは一体なんなのか、全く明晰ではない。これはRubyであるから仕方ないのかもしれない。SmallTalkの場合、次のように書けるので、ある程度、何を目的としたものなのかが、キーワードから推測できるようになっている:

|start_at end_at orders|
orders := { 
    Order amount: 3000 year: 2016 month: 7 day: 22.
    Order amount: 4000 year: 2016 month: 7 day: 21.
    Order amount: 5000 year: 2016 month: 7 day: 19.
    Order amount: 6000 year: 2016 month: 7 day: 10.
}.

start_at := Date year: 2016 month: 7 day: 10.
end_at   := Date year: 2016 month: 7 day: 22.
OrderCalculater orders: orders start_at: start_at  end_at: end_at. 

確かに、Ruby的には綺麗のように感じる(だから可読性が良いとも言っているのだろけれども)。しかし、明晰性はどうなのかというと悩みどころがある。プログラミングには可読性のもっと狭い概念として、明晰性があるように思える。

とはいえ、自分もオブジェクト指向に詳しいわけではない。機会があれば、紹介されている本を読んで理解を深めたいと思う。

Practical Object-Oriented Design in Ruby: An Agile Primer (Addison-Wesley Professional Ruby)

Practical Object-Oriented Design in Ruby: An Agile Primer (Addison-Wesley Professional Ruby)

Elixirのマクロを使って、FizzBuzzを書く

今日の料理

f:id:nisemono_san:20160722065057j:plain

餅とチーズが入ったお好み焼きを食べると、結構お腹が膨れることが判明した。

はじめに

何処かの誰かさんがやたらと「Elixirはいいぞ」と煽っているので(リンクに悪意はない)、来年頃にはElixirが来るのではないかと言われて久しい(今年としないのは、もうちょっと時間が必要だと思うため)。とはいえ、ただスルーするのも勿体なくはあり、しかし同時に何かしらの触る機会というものが存在しなかった。

で、最近になって『On Lisp』という、Common Lispの解説書みたいな本を読んでいて、マクロおじさんが「マクロはいいぞ」と言っていたので、「そういえば、Elixirもマクロが使えるんだよな」と思ったので、『On Lisp』が使えるかどうかの予行練習として、マクロを使ってFizzBuzzをしてみることにした。

なぜマクロを使うのか

しかし、ただここで一つの疑問が生まれるかもしれない。そもそも、どうしてマクロを使うのだろうか。実際、マクロ自体に関しては賛否両論で、それを意図的に避けている言語もある。とりあえず、そのあたりについては放っておくとして、なぜマクロを使うのかというのがある。

まず、プログラミング言語の構文として、関数やメソッド定義では実現できないような機能がある。それは、関数自体の定義だったり、あるいはメソッド自体の定義である。あるいは、ifなど、それ自体の評価が特殊な事例においての時である。もちろん、Rubyではdefine_methodみたいに、メソッドを動的に定義するためのメソッドが用意されているのだが。

上記の説明では不十分ではあるが、その言語の動作に対して手を加えたい場合、マクロは有効である。

ごくごく普通のFizzBuzz

とはいえ、いきなり「じゃあマクロを利用したFizzBuzzを作るぞ」となったとしても、そもそも自分はElixir自体を触るのははじめてなので、まず雛形を用意してやる必要がある。普通に書くとするならば、たぶん下のようになるだろう:

defmodule OldFizzBuzz do
  def to_s(x) do
    cond do
      rem(x, 15) == 0 -> "FizzBuzz"
      rem(x, 3)  == 0 -> "Fizz"
      rem(x, 5)  == 0 -> "Buzz"
      true -> to_string(x)
    end
  end

  def forprint(x) do
    for i <- 1..x do
      IO.puts to_s(i)
    end
  end
end

まず、特徴的なのは、condという条件分岐の式が用意されていることである。これは、Lispにおけるcondと若干様子が似ている。で、いわゆる余りを求める関数に関してはremを使う。これはよく見るFizzBuzzの形である。

しかし、これは冗長である。なぜなら、15は3と5の倍数なので、実際は「3の倍数であるかどうか」と「5の倍数であるかどうか」で合成ができるからである。従って、次のように書きなおすことができる。

変更版

defmodule DefultFizzBuzz do
  def to_s(x) do
    result = fizz(x) <> buzz(x)
    if result == "", do: to_string(x), else: result
  end

  def fizz(x) do
    if rem(x, 3) == 0, do: "Fizz", else: ""
  end

  def buzz(x) do
    if rem(x, 5) == 0, do: "Buzz", else: ""
  end

  def forprint(x) do
    for i <- 1..x do
      IO.puts to_s(i)
    end
  end
end

というわけで書きなおしてみた。気になるところとしては、ifを利用する場合、SmallTalkのようなメッセージパッシングみたいな文法を使っていること以外、とくに目新しいものはない。ちなみに<>に関しては、文字列の結合のオペレーターになっている。

ここで、面倒だなと思うことがある。見ればわかるように、fizzbuzzの関数は、見た目は基本的に一緒なのである。このような見た目が一緒な関数をわざわざ機械的に宣言することは、人間がやるべきことではない。せいぜいタイプの数が多少減る「だけ」だとしても、そのようにコードを整理することには、多分意味がある。

マクロを利用する

というわけで、実際にマクロを利用してみよう。しかし、少し書いてみたのだけれど、マクロの宣言自体も当然のことながら先に行なっておく必要がある。そこで、Elixirではmoduleで、その宣言をまとめる必要がある(いいかえるならば、トップで宣言することはできない)ので、FizzBuzzHelperというモジュールを用意し、定義する。

また、別途どのような関数を宣言したいのかをタプルによって宣言しておく。

defmodule FizzBuzzDefine do
  def define_taple do
    [{"Fizz", 3}, {"Buzz", 5}]
  end
end


defmodule FizzBuzzHelper do
  import FizzBuzzDefine

  defmacro mod_zero_string do
    for {name, x} <- define_taple do
      quote do
        def unquote(:"#{name}")(x) do
          if rem(x, unquote(x)) == 0 do
            unquote(name)
          else
            ""
          end
        end
      end
    end
  end
end

まず、タプル({})は、同じ要素数を持つタプルの変数に対し、その値を束縛することができる。また、文字列をシンボルにしたい場合については:"hoge"という形にする。関数をapplyで呼びだしたい場合であったり、あるいはこの場合の関数定義に利用したい場合なんかに使うとよい。

さて、気になることが一つある。それはquoteである。Lispを触っている人間ならば、「ああ、評価を遅らせるやつね」とわかるやつだ。これは、いわゆるマクロの中で実行される式と、マクロとして展開するための式を区別するためである。

とはいえ、quoteブロックの中でも、マクロ定義の中で束縛した変数の値を使いたい場合があると思う。これについてはunquoteで、一回quoteを取り外し、評価することができるようになる。

さて、せっかくなので、ifの中身もどうせなので、マクロにしておこう。もし余りが0であるならば、指定された文字列を、そうでなければ空の文字列を返すマクロだ。

defmodule FizzBuzzIf do
  defmacro if_rem_0(i, x, s) do
    quote do
      if rem(unquote(i), unquote(x)) == 0, do: unquote(s) , else: ""
    end
  end
end


defmodule FizzBuzzHelper do
  import FizzBuzzDefine
  import FizzBuzzIf

  defmacro mod_zero_string do
    for {name, x} <- define_taple do
      quote do
        def unquote(:"#{name}")(x) do
          if_rem_0(x, unquote(x), unquote(name))
        end
      end
    end
  end
end

ほぼunquoteになってしまったので、恐らくマクロを利用しなくても関数で良いだろうと思う。

さて、関数の定義は終わったわけだが、まだ判定の部分、つまり3の倍数でも5の倍数でもなければ数字を返すというロジックの部分に関しては完成していない。しかし、この部分に関しては、このマクロで定義された数だけ、判定する必要があるだろう。ここではapplyの関数を使う。

defmodule FizzBuzzHelper do

  # ...

  defmacro define_to_s do
    functions = Enum.map(define_taple, &(elem(&1, 0)))
    quote do
      def to_s(i) do
        function_strings = Enum.map_join(unquote(functions), &(apply(FizzBuzz, :"#{&1}", [i])))
        if function_strings == "", do: to_string(i), else: function_strings
      end
    end
  end
end

しかし、この部分に関しては、殆どマクロを利用する必要がない。なので、これは関数に移動させる。

defmodule FizzBuzz do
  import FizzBuzzHelper
  mod_zero_string

  def to_s(i) do
    functions = Enum.map(FizzBuzzDefine.define_taple, &(elem(&1, 0)))
    function_strings = Enum.map_join(functions, &(apply(FizzBuzz, :"#{&1}", [i])))
    if function_strings == "", do: to_string(i), else: function_strings
  end

  def forprint(x) do
    for i <- 1..x do
      IO.puts to_s(i)
    end
  end
end

これで終了です。おつかれさまでした。

雑感

まず書いてみて思ったのは、案外マクロを利用する機会というか、必然性は少ないということだ。だからといって、あると便利であることは変わりはない。特に、関数では実装できないような、いわば関数自体を定義することであったり、あるいは、マクロを抽象化して、さらなるマクロを定義するのに便利であることには違いない。その一方で、なんでもマクロにしてしまうのは、明らかに暴力であるので、関数として実現できないことが出てきてマクロを使うのがベターであるというように感じる。ただ、マクロの練習として、まず関数をマクロとして書き直すのは面白いと思う。

ElixirはよくRubyと比較されがちであるけれども、Rubyが以前にJavaと比較されていたような感じで、個人的には似て似つかぬもののように感じた(Nim langがPythonのようでPythonではないのと同じである)。しかし、同時になかなか魅力的な言語であることは間違いないので、なにかあれば、また触ろうと思う。

On Lisp

On Lisp

型をさらに拡張するーーRefinement Typesについて

本日の料理

f:id:nisemono_san:20160625124745j:plain

パン粉でカツ丼の味になるレシピをためしたところだが、世界はそれほど甘くないことを実感させられた。

本文

「型」といえば普通何を考えるだろうか。大抵の場合、「型」というものは、Integerであったり、あるいはStringといったようなものを想像するだろう。例えば、Typed Racketの場合、引数で受けとったIntegerをそのまま返すとした場合、次のような定義ができる。

(: simple-integer : (-> Integer Integer))
(define (simple-integer x) x)

さて、このように型付けされた場合、型チェックのシステムとしては、引数として入ってくるxIntegerかどうかにしか興味がない。その値が具体的にどのような形を取るか、ということに関しては、関数の内部においての責務ということになっている。

しかし、ここでちょっと立ちどまってみよう。型というのは、なんらかの要素がIntegerに属しているかどうかをチェックするものである。もうすこし考えるならば、何らかの型が「そこに属しているかどうか」をさらに絞りこむ形の型があってもいいのではないか。

Dive to Typed Racket

実際、RacketにはPositive-Integerというものが用意されている。実際に、そのコードを見てみると次のようになる。このコードは、著者環境のUbuntuだと/usr/racket/share/pkgs/typed-racket-lib/types/numeric-tower.rktに収録されている:

(define/decl -PosIntNotFixnum
  (make-Base 'Positive-Integer-Not-Fixnum
             #'(and/c exact-integer? positive? (not/c fixnum?))
             (lambda (x) (and (exact-integer? x)
                              (positive? x)
                              (not (portable-fixnum? x))))))

なんとなく、どういう風に型を付けているのかに関してはわかると思う。このように、型というものを「ある値xに対して、それがその型に属しているかどうか」というものを表現するためのものであるとするならば、Positive-Integerのような型はあっていい筈である。

ちょっと調べた感じだと、このような型を新しく定義する方法を見つけられなかったため、今回については割愛したいと思うが、余裕が出てきたら、このあたりについても調べたいと思う。

型を拡張する

さて、このようなアプローチは論文で提出されている。具体的に読みたければ、Occurrence Typing Modulo Theoriesという論文が2015年に公開されている。この論文には導出規則なども掲載されているが、著者の力では読みとくことができなかったため、理解できたところだけを、自分なりの理解で説明しようと思う。

前にも述べたように、「型」がある値がその型に属しているかどうかをチェックする門番みたいな役割であるとするならば、(乱暴に言ってしまうと)表面的な「数字」とか、あるいは「文字列」であるといったような表現だけでは不十分である。

先程も述べたように、例えばPositive-Integerであるならば、負の整数に関しては排除されるべきだろう。そのように考えるとするならば、次のような表現だってできるはずだ(以下の例は論文によるものである)

(: max : [x : Int ] [y : Int] ~>
  (Refine [z : Int ]
     (and (>= z x) (>= z y))))
(define (max x y) (if (> x y) x y))

このように、型に属するであろう任意の要素を取ってきて、それに当てはまるかどうかをチェックすることによって、より厳密な型によるチェックが行える。この場合であるならば、「Int型に属するx、及びyは、必ずzというより上位の数を所持している」ということになる(考えてみればわかるように、もし、xやyに、上位のzの数が存在しないと仮定すると、関数定義において、(> x y)という比較は成りたたない)

だた、一方で私見ではあるが、このような型は、型による値のチェックロジックが、型に寄っているのではないか、という点で、少々やりすぎなのではないかという気もしなくはない。

Liquid Haskell

とはいえ、このようなアイデア自体はどうも珍しいものではなく、先行研究として、Microsoft Researchが研究していた、F*(F starと読む)であったり、あるいはLiquid Haskellといったように、型に対して、そこに属する要素 を取りあつかえるようにするという試みは行なわれている。

実際、Haskellの上においても、このようなRefinement Typesというのを提案している。詳しくは、このサイトおよび、Refinement Types For Haskellの論文を参考にしてもらいたい。Introductionを読めば、どのような型付けをしたいのかがわかるはずだ。

type Pos = {v:Int | v > 0 }
type Nat = {v:Int | v <= 0 }

今までの議論は、入力におけるチェックに中心を置いてきた。然し、型システムに関しては、もう一つの役割がある。それは、出力において、確かにそのような出力が保証されているかどうか、ということである。例えば、単純に数字を増やすような関数を定義することを考えてみよう。

inc :: z:Int -> {v:Int | v > z }
inc z = z + 1

若干、論文のコードを変更させてもらった。ポイントは、入力されたIntの数が、その値を返すさいに、任意のInt型よりも上になっていることを保証するということである。

まとめ

型というと、どうしても例のIntegerやStringといったような、ある入力が属している型のことを想像しがちなのである。

型というものを、「ある要素xが属する集合」として捉えた場合、その「集合」だけを取りあつかうのは、確かに考えてみれば、不十分にも思える。「何らかの集合」が存在する場合、空集合を除くならば、「それに属する要素」というのも同様に存在している筈である。とすると、型において、その要素を検証するという方法は、それほど不自然ではない筈だ。

今回は、そのあたりについて、ちょっと面白い論文を見つけたので、概要をメモしてみた。もちろん、これが正確な説明であるという自信は全くない。もっと詳しい人がいれば、説明して頂けることを祈っている。

FizzBuzzによるTyped Racket入門 ーー静的に型を付けるLispに向けて

今日の看板

f:id:nisemono_san:20160707102555j:plain

概要

Lispにも静的型付けをおこなおうとする試みはされてきた。現在、その静的型付けLispを触るのにうってつけの環境は、恐らくTyped Racketだろう。そこで、FizzBuzzを通じて、Typed Racketがどういう型付け戦略を取っているのかを一通り試してみることにする。

はじめに

最近では、動的型付けを採用しているプログラミング言語でも、その言語を使った開発規模が大きくなるにつれて、なんらかの型チェックを行ないたいという需要が出てきている。

いくつかの言語、例えばPHPPython、既にType Hintingと呼ばれる、何らかの関数やメソッドに対して、引数の型と返り値の型を指定できるようになっている。これらが喜ばしいのは、その関数を利用するためのインターフェイスに関するドキュメントを、実行不可能なコメントによって注意を喚起するのではなく、型を利用して、実行可能な形でチェックすることができることがポイントとなっている。

さて、動的型付け言語と言えば、当然のことながら、Lispもその中に入ってくる。とはいえ、Lispに対して静的な型付けをおこなおうとする試みは無かったわけではない。例えば、Typed Schemeや、Shen、直近だとTyped Clojureなどがそれに当る。ただ、これらの環境は手軽に試せるものではない。そこで、手軽に試せる環境として、今回、Typed Racketに焦点を当てる。

今回語らないこと

静的型付け言語といっても、その型付け戦略に関しては様々な方法がある。Typed Racketの場合、Occurence Typingという方法を採用している。ただ、これに関しては、まだ理解が浅いことがあるので、今回は觝れないが、しかし論文自体はそこそこ見つかるので、興味ある方は参照するといいかもしれない。また、これらに関しても、理解を深めたのちに、このブログで書ければと思っている。

Typed RacketでFizzBuzz

大抵、新しいことをするときには、FizzBuzzを実装するようにしている。例えば、Typed Racketで普通にFizzBuzzを実装すると、次のようになる:

(: integer->fizzbuzz-helper : (-> Integer String))
;; これは Integer -> String でも良い
(define (integer->fizzbuzz-helper x)
  (cond [(= (modulo x 15) 0) "FizzBuzz"]
        [(= (modulo x 3) 0) "Fizz"]
        [(= (modulo x 5) 0) "Buzz"]
        [else ""]))

例えば、Haskellなんかを知っている人ならばわかりやすい記法かもしれない。最後がその関数の返り値となり、それ以外の最初の部分は引数の型となる。FizzBuzzのルールに関しては、慧眼な読者であるならば、既に知っているものであるし、他のブログでも散々語られていることなので、そのルールについては省略する。FizzBuzzは整数を取るため、型としてIntegerを指定することにする。そして、IntegerStringにするように指定する。

さて、これで困ったことがある。このようなFizzBuzzは型として冗長になる傾向にある。例えば上の関数を利用して、FizzBuzzをおこなう際、次のような感じになるだろう(なので、integer->fizzbuzz-helperという名前にした)。

(: integer->fizzbuzz : (-> Integer Void))
(define (integer->fizzbuzz x)
  (for ([i (range 0 x)])
     (let ([result (integer->fizzbuzz-helper (+ 1 i))])
        (display (if (string=? result "") x result))
        (display "\n"))))

さて、ここで一つ気になることがある。まず最初に、displayという関数は、基本的にAnyの型を取る。

> display
- : (->* (Any) (Output-Port) Void)

とするならば、関数の返り値は必ずしもStringに限定することではない。この実装の無理がある点は、Stringに限定しているため、FizzBuzzのロジックがinteger->fizzbuzz-helperから漏れ出てしまっている点にある。なので、StringIntegerも取りうるような型として、FizzBuzzという型を定義する。

(define-type FizzBuzz (U Integer String))

(: integer->fizzbuzz-helper : (-> Integer FizzBuzz))
(define (integer->fizzbuzz-helper x)
  (cond [(= (modulo x 15) 0) "FizzBuzz"]
        [(= (modulo x 3) 0) "Fizz"]
        [(= (modulo x 5) 0) "Buzz"]
        [else x]))

(: integer->fizzbuzz : (-> Integer Void))
(define (integer->fizzbuzz x)
  (for ([i (range 0 x)])
    (display (integer->fizzbuzz-helper (+ i 1)))
    (display "\n")))

define-typeの先頭に付いているUだけれども、これはUnion Typesと呼ばれていて、型同士の和を取るようにする。これ自体はさほど珍しいことではない。とりあえず、これでStringIntegerの両方の返り値を取れるようになったので、返り値に対して、StringおよびIntegerを返すことができるようになった。

しかし、わざわざこの程度のプログラムに対して、型を宣言することは冗長ではないだろうか。言ってしまえば、無名の型が指定できれば便利ではないだろうか。そこで、Racketの場合、直接Union型を指定することができる。

(: integer->fizzbuzz-helper : (-> Integer (U String Integer)))
(define (integer->fizzbuzz-helper x)
  (cond [(= (modulo x 15) 0) "FizzBuzz"]
        [(= (modulo x 3) 0) "Fizz"]
        [(= (modulo x 5) 0) "Buzz"]
        [else x]))

もっと考える

さて、FizzBuzzに関して無視していた事実が一つある。それは、FizzBuzzは正の整数に対して行なう対象であるということだ。なので、型で正の整数のみを対象とすることが保証できれば、そちらのほうが望ましいだろう。実際に、RacketにはPositive-Integerという型が用意されている。なので、これを利用する。

(: integer->fizzbuzz-helper : (-> Positive-Integer (U String Integer)))
(define (integer->fizzbuzz-helper x)
  (cond [(= (modulo x 15) 0) "FizzBuzz"]
        [(= (modulo x 3) 0) "Fizz"]
        [(= (modulo x 5) 0) "Buzz"]
        [else x]))

(: integer->fizzbuzz : (-> Positive-Integer Void))
(define (integer->fizzbuzz x)
  (for ([i (range 0 x)])
    (display (integer->fizzbuzz-helper (+ i 1)))
    (display "\n")))

しかし、これはエラーが起きる。具体的には下の通りだ。

Type Checker: type mismatch
  expected: Positive-Integer
  given: Integer in: (+ i 1)

これはコードを見る限り、とてもマイナスになりそうにもないのだが、しかしこの段階では、また(+ i 1)の型はIntegerなのだ。同様のことは、Racketのドキュメントにも書いてある:

> (: a : Positive-Integer)
> (define a 10)
> (: b : Positive-Integer)
> (define b 20)
> (: c : Positive-Integer)
> (define c (- b a))
. Type Checker: type mismatch
  expected: Positive-Integer
  given: Integer in: (- b a)

推論がどのように行なわれているか、現状ではちょっとわからないところがあるけれども、自分の仮説としてはこう。

型からすれば、Positive-IntegerPositive-Integerを引算した結果として、必ずしもその結果はPositive-Integerにならない可能性は十分にある。この例は直ぐに挙げることができる( 10 - 20ならマイナスになる)。つまり、型は、その型を組み合わせたときに何になるのか、にしか興味がなく、「実際にそれがどのような値になった結果、どの型になるのか」ということに関しては興味がないし、またそのような型としてはまだ表せていない。

とはいえ、何らかの型の変換方法が無ければ、Positive-Integerを指定した意味というのが無い。そこで、次のように指定する。

(define c (let ([result (- b a)])
              (if (positive? result) result
                  (error "Type Check Missing."))))

このように、positive?という判定を行なうことによって、必ずPositive-Integerが保証されるようになり、ちゃんとcに対して代入することが可能になるようだ。これを短縮した関数(というか、中身はマクロなのだが)としてassertが用意されている。上の式は、下のように書き直すことが可能だ。

(define c (assert (- b a) positive?))

assertは2番目の引数でチェックしたのちに、もしtrueであるならば、値を返し、falseならばエラーが起きるマクロである。このようにして、Racketでは、ある関数に入ってくる型がそうであるということを保証することになる。

さて、概要が分かったところで、早速FizzBuzzに対して手を入れよう:

(: integer->fizzbuzz-helper : (-> Positive-Integer (U String Integer)))
(define (integer->fizzbuzz-helper x)
  (cond [(= (modulo x 15) 0) "FizzBuzz"]
        [(= (modulo x 3) 0) "Fizz"]
        [(= (modulo x 5) 0) "Buzz"]
        [else x]))

(: integer->fizzbuzz : (-> Positive-Integer Void))
(define (integer->fizzbuzz x)
  (for ([i (range 0 x)])
    (display (integer->fizzbuzz-helper (assert (+ i 1) positive?)))
    (display "\n")))

というわけで、これで無事FizzBuzzが型付きでできるようになる。

雑感

知人と、この型について話をしたところ、このRacketが採用しているoccurence typingという方法が特殊なのではないかという示唆を貰った。確かに、関数の引数を渡す段階で型をチェックし、もしそのチェックが成功すれば、それはまたある型である、とする方法はちょっと不思議な感じもしなくはない。このあたりに関しては、論文が出ているので、それらを参考にもうすこし理解を深めようと思ったりもした。

Jupyter Notebook + RISE でスライド作ってトークしてきたというお話

今日の料理

f:id:nisemono_san:20160702185859j:plain

Cookdo麻婆豆腐と手作り麻婆豆腐の自炊戦争が行なわれてから十数年たつが、正直Cookdoの麻婆豆腐の味は忘れてしまった。とはいえ、あらためてCookdoで作る気にはならんなー、ということで、丸善屋の麻婆豆腐の素を手にいれて作った。

メリットとしては、素には挽肉も入っているので、挽肉を炒めるという工程、同様に量にあわせて豆板醤を混ぜあわせるといった行為をしなくてもいいという部分にある。しかし、辛口を買ってきたつもりが、意外と辛くなく、がっかりするということもある。辛めの麻婆豆腐が好きであるならば、自炊したほうがいいのかもしれないな、というのが現状の結論である。

概要

Jupyter Notebookという、ブラウザ上で使えるREPL環境がある。これを使うと、RubyやPythonのコード結果を書き残しながら操作できるという利点がある。またこのJupyter Notebookにはスライドモードというのが搭載されており、REPLの操作結果や、途中のMarkdownで書いたメモをスライドショーにすることも可能である。

しかし、このスライドモードの場合、履歴をスライドにしてくれるだけで、ライブコーディング的なプレゼンをしたい場合には向いていない。そこで、ライブコーディング的なプレゼンをしたい場合には、RISEというプラグインを追加する。

本題: YAP(achimon)Cで喋る内容をスライドにする話

ということで、YAP(achimon)Cで喋ってきた。

最近はラムダ計算とかそういうことをやりがちだったので、足が宙に受きがちなことばかり喋ることが多くなり、とある勉強会でやらかしてしまったので、ちゃんと知人にトークに付きあってもらって、フィードバックを受けとったのだけれども、「やはりちゃんと動くものを見せたほうが、トークに説得力が出るんじゃない?」という意見を受けとった。

検討した結果、「そういえばJupyter Notebookっていうものがあったよな」という風に思い至って、それを使ってみたらいい塩梅だったので、そのまま採用した。

Jupyter Notebookとは

Jupyter Notebookは、元の名前をIPython Notebookという。

元々、Pythonには、Rubyで言うところの「pry」みたいな、標準よりも拡張されたipythonというREPLが独自に開発されている。こいつにブラウザ上での実行環境を乗せたのがipython notebookというものである。このREPLは独自のファイルで保存されており、実行履歴が残り、再現性が高いため、何かしらの試験的なコードを書くのには非常に便利だ。画像にすると下のような感じだ。このブログを見ると、Gistにも乗っけられるっぽいので便利っぽい。

f:id:nisemono_san:20160704072406p:plain

ipython notebookからjupyter notebookになった正確な経緯は知らないが、このようなREPL環境を提供することについて、Pythonだけで留めておくのは勿体無いという判断からだろう。現在ではJupyter Notebookという名前で提供されている。RubyやらHaskellなんやらを使うこともできる

さて、このような便利なJupyter Notebookなのだが、さらに嬉しいことに、各行(Cell)を一ページ単位としたスライドを作成することができる。下のようなメニューから、slidemodeというのを選ぶことによって、各セルをどのようなスライドにするか指定することができる。

f:id:nisemono_san:20160704072437p:plain

普通、このスライドモードで設定されたものを元にスライドを生成する形となる。

jupyter nbconvert mynotebook.ipynb --to slides 

当然、生成されたスライドに関しては、コードは埋めこまれてしまっているために変更不可能だ。

コードの説明をプレゼンされるときに、おもむろにエディタを立ちあげて例を作るのはカッコイイな、と思ってたし、発表を聞いている側も、そういう風に挙動の違いを確認しながら見ていくと理解しやすいように思ったので、ライブでコードが変更できるものはないかなー、と探していたらRISEというのがあって、これを使うとライブなプレゼンモードになる。

元々Pythonのエコシステムによるものなので、そのあたりがわからないと、ちょっと戸惑う点はあるかもしれないけれど、使いこなせると、かなりカッコイイ。

ただ、こいつの弱点は一つだけあって、実行コードを表示するさいの文字はどうしても小さくなりがちであることと、大きな出力に関しては、スライドに収まらず、ポコーッとなってしまうので、そのあたりはちゃんとプレゼンをするときに、視聴者との距離を考えたほうがいいかもしれない。

まとめ

とにかくスライドのコードを書きかえられて、なおかつその実行結果が表示されると、プレゼンにも説得力が出るという端的な事実と「おお、スライドが動いているぞ」という圧力によってカッコよく見えるというのはある。プログラマにとって、キーボードを叩いている時間が、一番輝いているとするならば、その輝いている姿をトークやスライドに持ちこむことによって、どんどん輝いていって欲しいなあと思います。

蛇足

あ、今回発表した内容のレジュメはここに置いておきますので、御自由に閲覧ください

謝辞

トークを聞いて頂けた人、ならびに打ちあげにてご一緒した方、本当にありがとうございました。酒の勢いで、あること無いこと喋っていたような気がして、ちょっと頭がズキズキするのですが、拙いトークで楽しんで頂ければ、これ以上の幸せはないでしょう。それでは、どこかで御会いすることがあれば、またよろしくお願いします。

また、この機会を作ってくれたUzullaさんには大変感謝です。次の開催を期待しています。

YAPC8oji用に80枚まで作ったけど、没になったスライド 「プログラミング言語処理入門以前」 を公開します

今日の料理

f:id:nisemono_san:20160618153046j:plain

はじめました。

没になったスライド

理由

YAPC8ojiトークをすることになったので、登壇初心者のみなさんへというページを読んでいたら、「同僚や友人やぬいぐるみなど相手に練習するのは良いことです。」という、いいことが書かれていた。なので、友人にスライドを発表した結果、そのフィードバックとして

  1. 話題がとっちらかりすぎていて、話についていけない
  2. 今、何が主題になっているのかよくわからない
  3. ただいろんな言語を知っていることを自慢したいだけでは
  4. 30分で90枚は、お前は時間というのをナメている

という、極めて理知的なフィードバックを頂いた。で、今回はちゃんとした成果物があるわけだから、それが動く様子をきちんと紹介したほうがいいのでは? ということになり、80枚作ったスライドは一回没にすることにした。没にした理由としては明確で、上記の条件を満たす、よりよいツールを発見しから。とはいえ、せっかく80枚作ったし、どういうトークをしようとしているのか、という紹介には十分だと思ったので、途中までだけど、掲載してみることにする。

やはり、自分で作っていると、自分の語りたいことを語りたがる傾向にあるので、ちゃんと誰かに聞いてもらうことはいいことだし、そういう友人は最高なので、最高のプレゼンができるように、素晴しいスライドを準備しようと思ってます。待ってろYAP(achimon)C!!

今日のポエム: 「内側」から攻めるか、「外側」から攻めるか

今日の料理

f:id:nisemono_san:20160621135441j:plain

100円の餃子は著しくコストパフォーマンスが高い。

Re: 一つの言語に集中できない

phaendal.hatenablog.com

を読んだ感想です。

多分、多言語を食いちらかしているような人間からすると、同じような悩みを持っていると思う。自分も、最近はRubyとRacketに落ちつきつつあるけれども、OCamlに興味が出てきたらOCamlに突っこんでいくし、Smalltalkに興味が出てきたら、Smalltalkに突っこんでいくということを繰り返していたりする。

自分の強迫観念というか、それこそ自責することの一つに、「こんなお金にできない言語をやっている暇があるんだったら、SQLの一つくらい覚えたほうがよほど金になる」というのがある。そういう現実主義的な自分がいてクラクラしそうになる。

簡単に言ってしまえば「気質の問題」というか「飽きっぽさ」という単純な解答はあるんだけど、でもこの解答はあまりにもポジティヴではないだろう。もうすこしポジティヴに考えるとすると、下のような解答になると思う。

二つの理解

プログラム言語には、二つの理解方法があると思っている。一つには、そのプログラム言語の挙動を掘りさげていくことによって、一つのプログラムについての洞察を理解しようとすること。もう一つは様々なプログラミング言語を照しあわせて、その差分から理解しようとすること。

実際、この両者について、理解の深化についてはあまりかわらないと思う。実際のプログラミング言語の挙動については、前者のほうが詳しくなる可能性が高いだろう。後者の場合はどちらかというと「プログラミングとは何ぞや」という抽象的な問いになる可能性はあるとは思う(だからこそ、ラムダ計算なんかに興味を持ちはじめたりする)。

で、この理解の方法を「内側から理解するか」、あるいは「外側から理解するか」という風に呼ぶ。外側というのは、つまりプログラミング言語の差異によって理解するということだ。

「差異」というのはバカにはならなくて、『精神と自然』を書いたベイトソンなんかは、情報とは結局のところ「差異」なのではないか、みたいなことを述べている。とすると、この手の差異を作るためには、違う言語をやるのがてっとり早い。英語の慣用句が、日本人の慣用句と違うことに驚いて、よりなんらかの現象への見方を転換させるように、違う言語をやるということは、言語の見方を転換させることになるだろう。

体系化

で、いろんな言語をやっていると、いつしか「あれ、この考え方はこの言語にも転用できるのではないか」ということがわかってくる。そうするとしめたもので、よりプログラミングに対する知識というものが生まれてくる。そのとき、違うもの同士だったプログラミング言語が、いつのまにか手を取りあっていたりする。その瞬間に引かれるのではないんじゃないんだろうか。

だから、自分なんかが、色んな言語を触りたくなるのは、このような「そうだったのか!」という「アハ体験」をしたいが故のことなのかもしれないな、と思ったりした。

まとめ

なんていうか、酔っぱらいの戯言みたいなエントリだけれど、自分の立場としてはこんな感じ。たぶん、「違うことはどこかで同じことになる」みたいな態度で接していくといいのかなあ、なんて普段は思っていたりする

参考文献

精神と自然―生きた世界の認識論

精神と自然―生きた世界の認識論

精神現象学 (上) (平凡社ライブラリー (200))

精神現象学 (上) (平凡社ライブラリー (200))