プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

84
プログラミング 言語処理入門 以前 Esehara shigeo

Transcript of プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

Page 1: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

プログラミング言語処理入門以前 Esehara shigeo

Page 2: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

お前誰だ

Page 3: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

esehara shigeo (32)twitter: @esehara

プログラミング言語オタク

Page 4: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

概要

Page 5: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

発表の概要

1. プログラミングについて知るためには、実際に簡単な処理系を

作ってみるのが手っとり速い

2. 一番簡単な構文はLispなので、Lisp処理系を作る

3. ただ、そのまま作ると余りにも重いので、一日でできるくらいミ

ニマムな奴を考える

4. できたのでUnLispと名づけた次第

Page 6: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

方針

Page 7: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

リストはUnlispを実装する言語の

文字列リストが

ネストした形のものであること

["do", ["def", "fib", ["fn", "n", ["if", ["=", "n", "0"], "0", ["if", ["=", "n", "1"], "1", ["+", ["fib", ["-", "n", "1"]], ["fib", ["-", "n", "2"]]]]]]], ["fib", "7"]]

Page 8: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

仕様の理由

1. カッコの対応関係を調べるのが面倒であること

2. このような実装の方針を取っている奴はいくつか

ある

3. 最低限ということを考えた場合、テキストからリス

トを作るのは余技であると判断

Page 9: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

仕様の理由

1. カッコの対応関係を調べるのが面倒であること

2. このような実装の方針を取っている奴はいくつか

ある

3. 最低限ということを考えた場合、テキストからリス

トを作るのは余技であると判断

Page 10: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

実例

Page 11: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

達人出版社から無料配布中

Page 12: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

『つくって学ぶプログラミング言語』より

Page 13: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

miniMAL ( https://github.com/kanaka/miniMAL )

Page 14: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

この方法のメリットとデメリット

1. プログラミング言語のリテラルを使うことができるので字句解

析をしないで済むことができる。だたし、字句解析の部分は言

語処理でやっていて越したことはないので、これはちょっと簡

略化しすぎている印象は否めない

2. ほとんとのビルドイン関数を、プログラミング言語に搭載したビ

ルドイン関数に丸なげできる。しかし、そうなると、実装自体

が、プログラミング言語のメタプログラミングになるので、汎用

性が無くなる

3. とはいえ、手っとりばやさはこの方法が一番強い

Page 15: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

先を踏まえた上でのUnlispでの方針

なので、Unlispでは「文字列」のリストを採用し、そこ

からトークンを作成し、それを元に構文解析を行うと

いう方針を取る。そうすることによって、とりあえず字

句解析と、構文解析という二つの方法を分割するこ

とが可能になる。

Page 16: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

なんで「実装言語のリストを利用するの?」

データはコード

Page 17: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

例: Ansible(プロビジョンツール)によるループ

Page 18: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

例: TeXによるFizzBuzz

http://hogespace.hatenablog.jp/entry/2012/02/20/133731 より

%residue(剰余)の結果

\newcount \ResidueResult

%剰余を返すマクロ

\def \residue #1 #2{

\newcount \tmp

\newcount \tmpb

\tmp=#1

\tmpb=#1

\divide \tmp by #2

\multiply \tmp by #2

\advance \tmpb by -\tmp

\ResidueResult=\tmpb

}

\def \FizzBuzzUnit#1{

\newcount \FizzFlag

\newcount \BuzzFlag

\newcount \AllFlag

\FizzFlag=0

\BuzzFlag=0

\AllFlag=0

\residue #1 3

\ifnum \ResidueResult=0

\FizzFlag=1

\fi

\residue #1 5

\ifnum \ResidueResult=0

\BuzzFlag=1

\fi

\advance \AllFlag by \FizzFlag

\advance \AllFlag by \BuzzFlag

\ifnum \FizzFlag=1

Fizz

\fi

\ifnum \BuzzFlag=1

Buzz

\fi

\ifnum \AllFlag=0

#1

\fi

}

Page 19: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

で、何が言いたいの?

このようにして考えた場合、「データが何であるか」と

いう問いに意味はない。自分の意見では、データに

意味を与えているのは、「データをどのように処理す

るか」という処理系の側である。とするならば、「実装

を最低限にはするが、プログラミング言語処理のト

ピックを拾いあげる」とする目的において、配列を使

うという方法は、それなりの合理性があると考える。

Page 20: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

処理

Page 21: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

『How To Create Your Own Freaking Awesome Programming Language』より

Page 22: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

方法

Page 23: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

とりあえずどんな方法があるか見てみる(ANTLR)

Page 24: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

とりあえずどんな方法があるか見てみる(RAGEL)

Page 25: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

とりあえずどんな方法があるか見てみる(Treetop)grammar Arithmetic rule additive multitive ( '+' multitive )* end

rule multitive primary ( [*/%] primary )* end

rule primary '(' additive ')' / number end

rule number '-'? [1-9] [0-9]* endend

Page 26: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

ライブラリ使うのか問題

今回はもうちょっと軽量化したい

ため、これらのツール(ライブラ

リ)を使うのは一回避ける

Page 27: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

とりあえずどんな方法があるか見てみる(正規表現)func patterns() []Pattern {

return []Pattern{{whitespaceToken, regexp.MustCompile(`^\s+`)},{commentToken, regexp.MustCompile(`^;.*`)},{stringToken, regexp.MustCompile(`^("(\\.|[^"])*")`)},{numberToken, regexp.MustCompile(`^((([0-9]+)?\.)?[0-9]+)`)},{openToken, regexp.MustCompile(`^(\()`)},{closeToken, regexp.MustCompile(`^(\))`)},{symbolToken, regexp.MustCompile(`^('|[^\s();]+)`)},

}}go-lisp( https://github.com/janne/go-lisp/blob/master/lisp/tokens.go )より

Page 28: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

Unlispの字句解析の方針について

いろいろな方法はあるけれども、

とりあえずは「正規表現」で良さ

そう(とりあえずtoken化を最低限

できればよい)

Page 29: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

こんな感じ

module Unlisp module Lexer def tokenize str return Token.new(Token::LIST, str) if str.is_a? Array case str when /\d+/ Token.new(Token::INTEGER, str.to_i) when /(\D.*)/ Token.new(Token::ATOM, $1) else Token.new(Token::ERROR, str) end end

def list_analyzer lst parse_result = [] while !lst.empty? token = tokenize(lst.shift) if token.list? token.value = list_analyzer(token.value) end parse_result << token end parse_result end end

module Lexer extend self endend

Page 30: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

トークンの中身はいたってシンプル

要するに、トークン

の種類と、そのトー

クンの値さえあれ

ば、なんとかなる。

module Unlisp class Token attr_accessor :type, :value, :env

# Token types INTEGER = 1 # STRING = 2 ATOM = 3 LIST = 4 FUNCTION = 5 ERROR = 6 # ... endend

Page 31: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

あれ、String型はサポートしないの

面倒だからですちゃんとした理由としては、型が増えれば、それに対する操作も当然に増える。StringがあればStringの操作も必要になる。そうすると実装する範囲も増えるので、これはよろし

くはない。だたし、Hello, Worldできない言語は言語じゃない(重要)なので、そのあたり

のサポート関数はあったほうが良い。

Page 32: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

あれ、Boolean型はサポートしないの

面倒だからですちゃんとした理由としては、BooleanはIntegerで代用が可能であるから。(例:C言語、

Python)

Page 33: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

Booleanは結局Integerで代用できる例(Python)

def true_or_false(x): if x: print("This is True.") else: print("This is False.") true_or_fales(0)true_or_false(1)

Page 34: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

怠惰なのでヘルパーメソッドは生やす(美徳)

def self.false Token.new(INTEGER, 0) end

def self.true Token.new(INTEGER, 1) end

def false? integer? && value == 0 end

def true? integer? && value != 0 end

Page 35: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

構文

Page 36: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

演算子の優先順位問題

(* OCamlの場合 *)sqrt 10.0 +. sqrt 10.0;;(* - : float = 6.32455532033675905 *)

# Rubyの場合

Math.sqrt 10 + Math.sqrt 10# SyntaxError: unexpected tINTEGER, # expecting end-of-input

Page 37: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

で、これって何がおきてるの?

Rubyの場合、Math.sqrt(10 + Math.sqrt) 10といった

ような、オペレータの結合優先度が高いため、エラーとな

る。実際に、Math.sqrt 10 + 10だとエラーは起きない。

だが、このときMath.sqrt(10 + 10)なのか、それとも

Math.sqrt(10) + 10 なのかは判断が付かない。(ただ

し、このコードは極端な例。実際に書くと『リーダブルコー

ド』で殴られる可能性はある)

Page 38: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

演算子の優先順位問題(再び)

3 * 4 + 5 * 6# In Ruby => 42

3 * 4 + 5 * 6. "In Smalltalk => 102"

Page 39: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

Smalltalkのクラスの中身

Page 40: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

演算子の優先順位解説

Smalltalkの場合、Integerのメソッドとして演算子は定義されて

いるので、容赦なく左に結合していく。(わかりやすく言うと(((3 * 4) + 5) * 6)に結合していく)

「人間がこのように規則を適用するのが自然である」ということを

実装するのは、結構問題がある。例として考えるならば、計算式

の順序としては「式 = { (式), *, / , +, - } 」という順序が作れる。計

算式ならば、それほど難しくはないが、このような構文の適用順番

というのは常に考えないといけなくなる。

Page 41: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

面倒

Page 42: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

なんで最初に「Lispっぽいもの」を実装するといいのか

Lispの構文の場合、先頭に関数、後者に引数ということ

のみ考えればいいので、上記の問題は発生しない(そう

いう意味では、Smalltalkの場合も同様のことが言えるか

もしれない。ただ、Smalltalkの場合、オブジェクト指向と

いう別の問題が出てくる)

例: (hoge 1 (+ 1 2 ) 3)

Page 43: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

蛇足

Page 44: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

個人的な意見

優先順を決めることにおいてすら、そのプログラミング言

語自体の設計思想において演繹される。確かに、「3 * 4 + 5 * 6」を、いわば「(3 * 4) + (5 * 6)」、あるいは「(+ (* 3 4) (* 5 6))」とするのは、人間の慣習というよりは、機械

への歩みよりということができるが、逆にそれは機械に

とっても、人間にとってもほどよく優しいことがある。

Page 45: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

個人的な意見

「明示的に計算順位を記述すること」と、「暗黙的に処理

系が計算順位を決定して欲しい」とする問題は、一種の

トレードオフと言えるわけで、後者の場合においては、人

間が「その処理系の気持ちになって考える」という側面

が出てきてしまう。ただ、通常使う場合においては、後者

のほうが支持される傾向にありそうである。

Page 46: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

個人的な意見(例えばPHP)

例えば、"1"というString型を、わざわざInt型にするのは

まどろっこしいという怠惰がある場合、PHP(またはPerl)では"1" + "1"で、2として計算できる。これは、人間にとっ

て「1」というのは、文字列でもあり、数字でもあるというと

ころから考えれば、解らなくもない理屈である。ただ、こ

のような闇雲なキャストを行うということは、逆に機械側

の負担を大きくし、バグを引きよせる原因となる。

Page 47: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

問題

Page 48: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

引数は関数に渡される前に評価されるのが通常

def my_if(predicate, true_case, false_case): if predicate: return true_case else: return false_case

def counter(n): return my_if(n == 10, n, counter(n + 1))

Page 49: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

無限ループを避ける(無名関数でラップする)

def my_if(predicate, true_case, false_case): if predicate: return true_case else: return false_case()

def counter(n): return my_if(n == 10, n, lambda: counter(n + 1))

Page 50: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

分岐(if)の実装方法について

普通、ifを実装する場合において、何らかの

形で、ある分岐を採用しない場合は、その

部分の式、あるいは文を評価することを遅

らせなければならない

Page 51: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

Smalltalkの場合を見てみよう(Fizzbuzz)((1 to: 100) collect: [ :i | (i % 15 == 0) ifTrue:'FizzBuzz' ifFalse: [(i % 5 == 0) ifTrue: 'Buzz' ifFalse: [(i % 3 == 0) ifTrue: 'Fizz' ifFalse: i asString ]]]) do: [:i | Transcript show: i asString; cr].

Page 52: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

SmalltalkのifTrueというメソッドの定義ってどうなってる

ifFalse: falseAlternativeBlock ifTrue: trueAlternativeBlock ^falseAlternativeBlock value

※これはFalseの場合。要するに、ifTrueのブロックを破棄して、

ifFalseを採用しているだけだということがわかる

Page 53: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

Smalltalkの場合、メソッドなので当然評価される

|i|(1 = 1) ifTrue: (i := 10) ifFalse: (i := 20).Transcript show: i.

※このとき、iに代入されている値は20

Page 54: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

なので、評価を一回遅らせるためブロックにする

|i|(1 = 1) ifTrue: [i := 10] ifFalse: [i := 20].Transcript show: i.

※このとき、iに代入されている値は10

Page 55: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

Rubyに移植してみよう

class TrueClass def ifTrue yield end def ifFalse self endend

class FalseClass def ifTrue self end def ifFalse yield endend

class Object def ifTrue self end def ifFalse self endend

Page 56: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

RubyでSmalltalkを真似てみよう(Fizzbuzz)1.upto(100).each do |n| (n % 15 == 0) .ifTrue { puts "FizzBuzz" } .ifFalse { (n % 3 == 0 ) .ifTrue { puts "Fizz"} .ifFalse { (n % 5 == 0) .ifTrue { puts "Buzz" } .ifFalse { puts n} } } end

Page 57: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

元々評価が遅延する場合(Haskell)

if' :: Bool -> a -> a -> aif' True x _ = xif' False _ y = y

※実は、この定義はラムダ計算におけるTrueとFalseと同様なのだが、詳細は省く。詳し

くは( https://wiki.haskell.org/If-then-else ) を参考にするとわかりやすい

Page 58: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

概要

Unlispの実装コード自体はそれほど面白くないから、ここでは省く

けれども、要は["if", ["<", "3", "2"], ["+", "x", "x"], ["+", "2", "2"]]というコードが入ってきた場合、評価自体は2番目のformが

定まるまで保留し、trueであった場合は3番目を評価メソッドで評

価し、falseである場合は、4番目を評価するといった流れになって

いる。このように、分岐する場合においては、処理を保留しなくて

はならない。

Page 59: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

複数の条件分岐に関してはifの入れ子として表現する

["do", ["def", "fib", ["fn", "n", ["if", ["=", "n", "0"], "0", ["if", ["=", "n", "1"], "1", ["+", ["fib", ["-", "n", "1"]], ["fib", ["-", "n", "2"]]]]]]], ["fib", "7"]]

Page 60: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

複数の条件分岐に関してはifの入れ子として表現する

["do", ["def", "fib", ["fn", "n", ["if", ["=", "n", "0"], "0", ["if", ["=", "n", "1"], "1", ["+", ["fib", ["-", "n", "1"]], ["fib", ["-", "n", "2"]]]]]]], ["fib", "7"]]

Page 61: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

複数の条件分岐を表現する構文

def fizzbuzz(x) puts (if x % 15 == 0 "FizzBuzz" elsif x % 3 == 0 "Fizz" elsif x % 5 == 0 "Buzz" else x ) endend

(define (fizzbuzz x) (println (cond [(= (modulo x 15) 0) "FizzBuzz"] [(= (modulo x 3) 0) "Fizz"] [(= (modulo x 5) 0) "Buzz"])))

def fizzbuzz(x): if x % 15 == 0: print("FizzBuzz") elif x % 3 == 0: print("Fizz") elif x % 5 == 0: print("Buzz") else: print(x)

Page 62: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

複数の条件分岐を実装しないのはなぜか

先ほど述べたように、条件分岐を行う場合、評価順

を考慮しなければならない。複数の条件分岐をその

まま実装するのは、そのような評価順を考慮する

ルールが増えるのでよろしくない。例えば、SICPとい

う書籍においては、Schemeにおけるcondをifに展

開するようにしている。これが示すのは、複数の条件

分岐はたかだかifの重ね合わせであるということだ。

Page 63: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

関数

Page 64: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

関数を定義しないとおもちゃとして面白くない

というわけで、構文のことを長々と話したわけだけれども、実際の

ところ、関数が定義できないと、おもちゃとして面白くない。

従って、関数を定義できるようにするわけだけれども、Unlispにお

いては、変数を定義する defと、無名関数を定義するfnしか用意

していない。またfnに関しても、一引数しか使えないようになって

いる。

Page 65: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

直接「名前付き関数」が定義できないのはどうしてか

そもそも「名前付き関数」自体

が、変数に無名関数を代入した

ものの糖衣構文だと考えること

ができる。

Page 66: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

Schemeでの実例

;; 名前つき関数

(define (square x) (* x x))

;; ラムダで定義する

(define square (lambda (x) (* x x)))

Page 67: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

OCamlでの実例

(* 名前つき関数 *)let square x = x * x

(* ラムダで定義する *)let square = fun x -> x * x

Page 68: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

JavaScriptの事例

// 名前付き関数

function foo(x) { return x * x ; }

// 無名関数

var foo = function(x) { return x * x ; }

Page 69: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

直接「名前付き変数」が定義できないのはどうしてか

さて、このように考えた場合、関数定義を直接行うという方針につ

いては、実際のところ、なんらかの変数に対して、無名関数を束縛

するという考え方で代用することが可能である。

もっと言ってしまうならば、変数に対して無名関数をオブジェクトと

して代入(あるいは束縛する)ということこそが、関数宣言である

と、乱暴にまとめてしまうことが可能である。

Page 70: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

無名関数が引数を実質一つしか取れないのは何故?

引数関数は、実際のところ、あるn引数関数

を1引数関数として表現することをCurry化と呼ぶ。

Page 71: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

無名関数が引数を実質一つしか取れないのは何故?

Curry化を持ちいると、2引数関数、3引数関数……n引数関数は、1引数関数の入れ子構造になる。『論

理と計算のしくみ』によれば(p192)、「関数の計算に

関して純粋な理論を展開するには、関数はすべて1引数であると仮定してよいように思われる」としてい

る。

Page 72: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

実例(JavaScript)// 通常の関数

var noncurry = function(x, y) { return x + y }noncurry(1, 2);// カリー化された関数

var curry = function(x) { return function(y){ return x + y } }curry(1)(2);

Page 73: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

実例(Python)

def no_curry(x, y): return x + y

no_curry(1, 2)

def curry(x): def inner_curry(y): return x + y return inner_curry

curry(1)(2)

Page 74: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

実例(Haskell)

curry :: Int -> Int -> Intcurry x y = x + y

plus_one x = curry 1

※Haskellの場合は、関数はcurry化されているので、こういう風に宣言することが可能

Page 75: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

実例(Ruby)

curried = ->(x, y){ x + y }curried[1, 2]

# RubyにはCurry化のメソッドがある

curried.curry[1][2]

Page 76: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

環境

Page 77: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

大域環境と小域環境

簡単に言ってしまえば、大域環境はグ

ローバル、つまり何処からでも参照でき

る環境である。例えば、普通に関数を定

義した場合、小域環境に、その関数の

情報が作られる。

その一方で、関数に対して入ってくる引

数を一時的に格納する変数が存在して

いる。

# echoは大域環境

def echo(x): # xは小域環境に入る

print(x)

# 3がxが入る

echo(3)

Page 78: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

小域環境とは何か

このxの値は、関数を抜けた場合には無効である必

要がある。なぜならば、このxは関数の内部におい

て、利用するために一時的に付けられている名前に

過ぎないからである。とすると、大域環境とは別に、

このような一時的に利用される関数の環境も用意し

なければならない。

Page 79: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

Unlispを使って考えてみる

[["fn", "x", ["+", "x", "1"]], "6"]

このとき、引数に6が渡されているわけだが、このとき、"6"という数

字をxに適用する。すると、この段階で、環境として、["+", "x", "1"]を評価するさいに、小域環境として、x = 6という、ローカルな

環境が新しく作られ、それに従って評価される。

Page 80: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

実際の実装について

実際の環境の実装は、変数名と値(あるいは関数宣言

のコード)を一緒に渡すかたちになっている。

上の関数の場合は、[["x", 6]]という形で、環境に対し

て、。このとき、この式が評価される場合には、`"x"`が入ってきたときには、環境に保存されたペアを検索し、そ

の値を起きかえる。

Page 81: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

わかりやすい図(SICPより)

Page 82: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

わかりやすい図(SICPより)

Page 83: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

実際の実装

関数に入ったときに新しい環境を作成し、離脱するときにさし戻すようにする。

def call lst, env # ... head = lst[0] head.env = head.env.next [head.value[0], next_val(lst)] if head.value[1].list? result_lst, _ = list_eval(head.value[1], head.env) elsif head.value[1].atom? result_lst, _ = apply_atom(head.value[1], head.value, head.env) end return result_lst, env end

Page 84: プログラミング言語処理入門 (YAP(achimon)C::Asia Hachioji 2016 没スライド)

クロージャ

乱暴に言えば、関数が定義された小域環境を、関数が定義されたときに保持する。

Unlispの場合:

[[["fn", "x", ["fn", "y", ["+", "x", "y"]]], "2"], "3"]