関数(ECMAScript)
必要なのは、関数定義と関数呼び出しだけです。分岐、条件、演算子、組み込み関数は必要ありません。ECMAScriptを使用した実装を示します。
まずは、という2つの関数を定義してみましょうtrue
とfalse
。自由に定義できますが、完全に任意ですが、後で見るようにいくつかの利点がある非常に特別な方法で定義します。
const tru = (thn, _ ) => thn,
fls = (_ , els) => els;
tru
は、2番目の引数を単に無視し、最初の引数を返す2つのパラメーターを持つ関数です。fls
また、最初の引数を単に無視して2番目の引数を返す2つのパラメーターを持つ関数です。
なぜこのようにエンコードtru
したのfls
ですか?さて、このように、2つの関数はtrue
and の2つの概念を表すだけfalse
でなく、「選択」の概念も表す、つまりif
/ then
/ else
式でもあります。if
条件を評価し、then
ブロックとelse
ブロックを引数として渡します。条件がに評価されるtru
と、then
ブロック評価される場合fls
、else
ブロックを返します。以下に例を示します。
tru(23, 42);
// => 23
これは戻ります 23
、:
fls(23, 42);
// => 42
42
期待どおりに戻ります。
しかし、しわがあります:
tru(console.log("then branch"), console.log("else branch"));
// then branch
// else branch
これは両方を印刷します then branch
をしelse branch
ます!どうして?
まあ、それは返す最初の引数の戻り値を、それが評価される ECMAScriptのは厳密で、常に関数を呼び出す前に、関数のすべての引数を評価するので、両方の引数を。IOW:最初の引数であるを評価します。これはconsole.log("then branch")
単に返され、コンソールに出力するというundefined
副作用があります。then branch
それが二番目の引数を評価し、またその戻りundefined
副作用としてコンソールへとプリント。次に、最初のを返しますundefined
。
このエンコーディングが発明されたλ計算では、それは問題ではありません。λ計算は純粋です。つまり、副作用がありません。したがって、2番目の引数も評価されることに気付かないでしょう。さらに、λ計算は遅延(または、少なくとも通常の順序で評価されることが多い)、つまり、必要のない引数を実際には評価しません。したがって、IOW:λ計算では、2番目の引数は評価されず、評価されても気付かないでしょう。
ただし、ECMAScriptはstrictです。つまり、常にすべての引数を評価します。まあ、実際には、常にではありません:たとえば、if
/ then
/ は、条件がある場合にのみブランチを評価し、条件がある場合にのみブランチを評価しますelse
then
true
else
false
ます。そして、この振る舞いをiff
。ありがたいことに、ECMAScriptは怠zyではありませんが、他のほとんどすべての言語と同じように、コードの評価を遅らせる方法があります:関数でラップし、その関数を呼び出さないとコードは決して実行されません。
したがって、両方のブロックを関数でラップし、最後に返される関数を呼び出します。
tru(() => console.log("then branch"), () => console.log("else branch"))();
// then branch
プリントthen branch
と
fls(() => console.log("then branch"), () => console.log("else branch"))();
// else branch
プリント else branch
ます。
従来のif
/ then
/をelse
次のように実装できます。
const iff = (cnd, thn, els) => cnd(thn, els);
iff(tru, 23, 42);
// => 23
iff(fls, 23, 42);
// => 42
繰り返しますが、関数を呼び出すときに追加の関数のラッピングが必要であり、上記と同じ理由iff
でiff
、追加の関数はの定義で括弧を呼び出します。
const iff = (cnd, thn, els) => cnd(thn, els)();
iff(tru, () => console.log("then branch"), () => console.log("else branch"));
// then branch
iff(fls, () => console.log("then branch"), () => console.log("else branch"));
// else branch
これらの2つの定義ができたので、実装できますor
。最初に、真理値表を見てください。or
第1オペランドが真理値の場合、式の結果は第1オペランドと同じです。それ以外の場合、式の結果は第2オペランドの結果です。つまり、第1オペランドがの場合、第1オペランドtrue
を返します。それ以外の場合、第2オペランドを返します。
const orr = (a, b) => iff(a, () => a, () => b);
動作することを確認しましょう:
orr(tru,tru);
// => tru(thn, _) {}
orr(tru,fls);
// => tru(thn, _) {}
orr(fls,tru);
// => tru(thn, _) {}
orr(fls,fls);
// => fls(_, els) {}
すばらしいです!しかし、その定義は少しいように見えます。覚えておいてください、tru
そしてfls
、すでに自分で条件付きのすべてのように行動するので、実際には必要ありませんiff
ので、すべてのすべてで、その関数のラッピングの:
const orr = (a, b) => a(a, b);
そこにあなたはそれを持っています:(or
そして他のブール演算子)ほんの数行の関数定義と関数呼び出しだけで定義されています:
const tru = (thn, _ ) => thn,
fls = (_ , els) => els,
orr = (a , b ) => a(a, b),
nnd = (a , b ) => a(b, a),
ntt = a => a(fls, tru),
xor = (a , b ) => a(ntt(b), b),
iff = (cnd, thn, els) => cnd(thn, els)();
返すECMAScriptのには関数や演算子がない:残念ながら、この実装はかなり無用であるtru
かfls
、それらはすべて返却true
またはfalse
私達は私達の機能でそれらを使用することはできませんので、。しかし、私たちにできることはまだたくさんあります。たとえば、これは片方向リンクリストの実装です。
const cons = (hd, tl) => which => which(hd, tl),
car = l => l(tru),
cdr = l => l(fls);
オブジェクト(Scala)
奇妙なことに気づいたかもしれません:tru
そしてfls
、二重の役割を果たし、データ値true
との両方として機能しますfalse
が、同時に条件式としても機能します。それらはデータと振る舞いであり、1つにまとめられています…ええと…「もの」…または(あえて言う)オブジェクト!
確かに、tru
そしてfls
オブジェクトです。また、Smalltalk、Self、Newspeak、またはその他のオブジェクト指向言語を使用したことがある場合は、それらがまったく同じ方法でブール値を実装していることに気付くでしょう。このような実装をScalaで実演します。
sealed abstract trait Buul {
def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): T
def &&&(other: ⇒ Buul): Buul
def |||(other: ⇒ Buul): Buul
def ntt: Buul
}
case object Tru extends Buul {
override def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): U = thn
override def &&&(other: ⇒ Buul) = other
override def |||(other: ⇒ Buul): this.type = this
override def ntt = Fls
}
case object Fls extends Buul {
override def apply[T, U <: T, V <: T](thn: ⇒ U)(els: ⇒ V): V = els
override def &&&(other: ⇒ Buul): this.type = this
override def |||(other: ⇒ Buul) = other
override def ntt = Tru
}
object BuulExtension {
import scala.language.implicitConversions
implicit def boolean2Buul(b: ⇒ Boolean) = if (b) Tru else Fls
}
import BuulExtension._
(2 < 3) { println("2 is less than 3") } { println("2 is greater than 3") }
// 2 is less than 3
BTWは、条件付きポリモーフィズムリファクタリングとの置換が常に機能する理由です。先ほど示したように、ポリモーフィックメッセージディスパッチは単に実装するだけで条件を置き換えることができるため、プログラム内のすべての条件を常に多態的なメッセージディスパッチに置き換えることができます。Smalltalk、Self、Newspeakなどの言語は、これらの言語には条件さえないため、その存在を証明しています。(また、ループ、BTW、または仮想メソッド呼び出しとも呼ばれるポリモーフィックメッセージディスパッチを除き、実際にはあらゆる種類の言語組み込み制御構造はありません。)
パターンマッチング(Haskell)
また、or
パターンマッチング、またはHaskellの部分関数定義のようなものを使用して定義することもできます。
True ||| _ = True
_ ||| b = b
もちろん、パターンマッチングは条件付き実行の一種ですが、オブジェクト指向のメッセージディスパッチも同様です。