可変型の「演算子」を持つ言語がそれほど多くないのはなぜですか?


47

私はそれをこのように意味します:

<?php
    $number1 = 5;   // (Type 'Int')
    $operator1 = +; // (Type non-existent 'Operator')
    $number2 = 5;   // (Type 'Int')
    $operator2 = *; // (Type non-existent 'Operator')
    $number3 = 8;   // (Type 'Int')

    $test = $number1 $operator1 $number2 $operator2 $number3; //5 + 5 * 8.

    var_dump($test);
?>

しかし、この方法でも:

<?php
    $number1 = 5;
    $number3 = 9;
    $operator1 = <;

    if ($number1 $operator1 $number3) { //5 < 9 (true)
        echo 'true';
    }
?>

どの言語もこれを持っているようには見えません-持っていない正当な理由はありますか?


28
一般的に、あなたがしたいことは、ある種のラムダ、クロージャー、またはそのような機能を実装する一般的な方法である匿名関数を伴う何らかの形のメタプログラミングをサポートするすべての言語でカバーされます。メソッドが第一級の市民である言語では、変数とほぼ同じメソッドを使用できます。そのようなほとんどの言語では、変数に格納されているメソッドを実際に呼び出したいことを明確にする必要があるため、ここで使用する正確な単純な構文ではありません。
トールステンミュラー

7
@MartinMaat関数型言語はこれをたくさん行います。
トールビョーンラヴンアンデルセン

7
haskellでは、演算子は他の関数と同様の関数です。タイプ(+)Num a => a -> a -> aIIRCです。あなたはまた、彼らはinfixed書き込むことができるような機能を定義する(できるa + bの代わり(+) a b
サラ

5
@enderland:あなたの編集は質問の目標を完全に変えました。言語が存在するかどうかを尋ねるところから、なぜそれほど存在しないのかを尋ねるところまで行きました。あなたの編集は多くの混乱した読者をもたらすと思います。
ブライアンオークリー

5
@enderland:それは事実ですが、主題を完全に変更することは混乱を招くだけです。トピックから外れている場合、コミュニティはそれを閉じるために投票します。(私がこれを書いている時点では)答えはどれも、質問が書かれているので意味がありません。
ブライアンオークリー

回答:


104

演算子は、おかしな名前の関数であり、いくつかの特別な構文があります。

C ++やPythonなどさまざまな言語では、クラスの特別なメソッドをオーバーライドすることで演算子を再定義できます。次に、標準演算子(など+)は、指定したロジックに従って動作します(文字列の連結、行列の追加など)。

このような演算子定義関数は単なるメソッドであるため、関数と同じように渡すことができます。

# python
action = int.__add__
result = action(3, 5)
assert result == 8

他の言語では、新しい演算子を関数として直接定義し、中置形式で使用できます。

-- haskell
plus a b = a + b  -- a normal function
3 `plus` 5 == 8 -- True

(+++) a b = a + b  -- a funny name made of non-letters
3 +++ 5 == 8 -- True

let action = (+)
1 `action` 3 == 4 -- True

残念ながら、PHPがそのようなものをサポートしているかどうかはわかりません。サポートするのが良いことなのかどうか。単純な関数を使用してください$foo $operator $bar


2
@tac:はい、これはいいですし、他の言語移植することもできます:) Haskellに関して、私が最も欠けているのは、この部門が$括弧(特に複数の入れ子)をなくす演算子です。 -variadic関数。したがって、PythonやJavaなどは除外されます。OTOH単項関数の合成はうまくできます
9000

6
はい、演算子は特別な構文を持つ単なる関数であるため、演算子のオーバーロードのファンではありませんでしたが、通常は関数に関係しない演算子に関係する暗黙のコントラクトがあります。たとえば、「+」には特定の期待(オペレーターの優先順位、可換性など)があり、それらの期待に反することは、人を混乱させ、バグを生成する確実なルートです。javascriptが好きなのに、加算と連結では+を区別したほうがいいと思う理由の1つです。Perlはすぐそこにありました。
fool4jesus

4
Pythonには、operatorを記述できる標準モジュールがあり、(だけでなく)action = operator.add定義する任意の型で機能することに注意してください。+int
dan04

3
Haskell +ではNumタイプクラスで動作+するため、作成した新しいデータ型に実装できますが、ファンクター用のfmapなど、他の操作と同じ方法で実装できます。Haskellには、それを許可しないために一生懸命作業しなければならないオペレーターのオーバーロードを許可するのは自然なフィットです!
マーティンカポディッチ

17
なぜ人々が過負荷に固執しているのか分かりません。元の質問は、オーバーロードに関するものではありません。私は、演算子を第一級の値として考えているようです。$operator1 = +式を記述して使用するために、演算子のオーバーロードを使用する必要はまったくありません!
アンドレスF.

16

ある種のメタプログラミングを可能にする多くの言語があります。特に、言語のLispファミリーについて話している回答がないことに驚いています。

ウィキペディアから:

メタプログラミングとは、プログラムをデータとして扱うことができるコンピュータープログラムを作成することです。

本文の後半:

Lispはおそらく、歴史的な優先順位と、メタプログラミングの単純さと力のために、メタプログラミング機能を備えた典型的な言語です。

Lisp言語

Lispの簡単な紹介を次に示します。

コードを表示する1つの方法は、一連の指示としてです。これを実行し、それを実行し、次に他のことを実行します...これはリストです!プログラムが行うことのリスト。そしてもちろん、ループなどを表すためにリスト内にリストを持つことができます。

我々は、B、C、D、このような要素を含むリストを表現している場合:(ABCD)私たちは何かを得ることLispの関数呼び出しのようなルックスaの機能である、とbcd引数があります。実際、典型的な「Hello World!」プログラムは次のように書くことができます:(println "Hello World!")

もちろんbcまたはd同様に評価するリストにすることもできます。次のように(println "I can add :" (+ 1 3) )すると、「 "I can add:4"」と出力されます。

したがって、プログラムは一連のネストされたリストであり、最初の要素は関数です。良いニュースは、リストを操作できることです!したがって、プログラミング言語を操作できます。

Lispの利点

Lispはプログラミング言語ではなく、プログラミング言語を作成するためのツールキットです。プログラム可能なプログラミング言語。

これは、Lispで新しい演算子を作成するのがはるかに簡単であるだけでなく、関数に渡されるときに引数が評価されるため、他の言語でいくつかの演算子を書くこともほぼ不可能です。

たとえば、Cのような言語ではif、次のような演算子を自分で記述したいとしましょう。

my-if(condition, if-true, if-false)

my-if(false, print("I should not be printed"), print("I should be printed"))

この場合、引数の評価の順序に依存する順序で、両方の引数が評価および出力されます。

Lispsでは、演算子(マクロと呼びます)の記述と関数の記述はほぼ同じことであり、同じ方法で使用されます。主な違いは、マクロへのパラメーターがマクロへの引数として渡される前に評価されないことです。これは、if上記のようないくつかの演算子を書くことができるために不可欠です。

現実の言語

ここでは正確に示すことは少し範囲外ですが、詳細については1つのLispでプログラミングしてみることをお勧めします。たとえば、あなたは見てみることができます:

  • Scheme、小さなコアを備えた古い、かなり「純粋な」Lisp
  • Common Lisp、適切に統合されたオブジェクトシステムを備えたより大きなLisp、および多くの実装(ANSI標準化済み)
  • 型付きLispをラケット化する
  • 私のお気に入りのClojure、上の例はClojureコードでした。JVM上で実行される最新のLisp。SOにもClojureマクロの例がいくつかあります(ただし、これは開始するのに適切な場所ではありません。最初に4clojurebraveclojureまたはclojure koansを見ていきます)。

ああ、ところで、LispはLISt Processingを意味します。

あなたの例について

以下にClojureを使用した例を示します。

addClojure (defn add [a b] ...your-implementation-here... )で関数を作成できる場合は、その+ように名前を付けることができます(defn + [a b] ...your-implementation-here... )。実際、これは実際の実装で行われていることです(関数の本体はもう少し複雑ですが、定義は上で書いたものと本質的に同じです)。

中置記法はどうですか?Clojureはprefix(またはポーランド語)表記を使用しているため、infix-to-prefixプレフィックス付きのコードをClojureコードに変換するマクロを作成できます。これは実際には驚くほど簡単です(実際、clojure koansのマクロ演習の 1つです)!野生でも見ることができます。たとえば、Incanter $=マクロを参照してください。

ここに説明されている公案からの最も簡単なバージョンがあります:

(defmacro infix [form]
  (list (second form) (first form) (nth form 2)))

;; takes a form (ie. some code) as parameter
;; and returns a list (ie. some other code)
;; where the first element is the second element from the original form
;; and the second element is the first element from the original form
;; and the third element is the third element from the original form (indexes start at 0)
;; example :
;; (infix (9 + 1))
;; will become (+ 9 1) which is valid Clojure code and will be executed to give 10 as a result

さらにポイントを進めるために、いくつかのLispの引用

「Lispを際立たせるものの一部は、進化するように設計されていることです。Lispを使用して、新しいLisp演算子を定義できます。新しい抽象化が一般的になると(オブジェクト指向プログラミングなど)、常にLispで簡単に実装できるようになります。DNAのように、そのような言語はスタイルを失いません。」

—ポールグラハム、ANSI Common Lisp

「Lispでのプログラミングは、宇宙の原始的な力で遊ぶようなものです。指先の間を稲妻のように感じます。他の言語は親近感さえありません。」

— Glenn Ehrlich、Lispへの道


1
興味深いことですが、OPが求めていることをサポートするために、メタプログラミングは必要ないことに注意してください。一流の機能をサポートする言語であれば十分です。
アンドレスF.

1
OPの質問の例:(let((opp# '+))(print(apply opp'(1 2))))
Kasper van den Berg

1
Common Lispの言及はありませんか?
コアダンプ

3
言語に関するパネルディスカッションで、ケンはAPLの優先順位について話していて、「かっこはほとんど使用していません!」と結論付けました。そして、聴衆から誰かが叫んだ、「それはディックがそれらをすべて使い切ったからだ!」
JDługosz

2
C ++スタイルの言語では、を再実装できますが、引数をラムダでifラップする必要があります。PHPとJavaScriptには、C ++にはラムダがあり、ラムダを使用したCのApple拡張機能があります。thenelsefunction()
ダミアンジェリック

9

$test = $number1 $operator1 $number2 $operator2 $number3;

ほとんどの言語実装には、パーサーがコードを分析してそこからツリーを構築するステップがあります。たとえば、式5 + 5 * 8は次のように解析されます

  +
 / \
5   *
   / \
  8   8

優先順位に関するコンパイラーの知識のおかげ。演算子の代わりに変数を渡した場合、コードを実行する前に適切な操作の順序がわかりません。深刻な問題となるほとんどの実装では、ほとんどの言語はそれを許可しません。

もちろん、パーサーが上記を一連の式と演算子として解析し、実行時にソートおよび評価される言語を考えることもできます。おそらくこれにはあまり適用されません。

多くのスクリプト言語では、expr実行時に任意の式(またはの場合のように少なくとも任意の算術式)を評価できます。そこで、数字と演算子を1つの式に組み合わせて、言語にそれを評価させることができます。PHP(および他の多く)では、その関数が呼び出されevalます。

$test = eval("$number1 $operator1 $number2 $operator2 $number3");

コンパイル時にコードを生成できる言語もあります。Dmixin表現が思い浮かびます。

test = mixin("number1 " + operator1 + " number2 " + operator2 + "number3");

ここoperator1operator2は、コンパイル時に既知の文字列定数、たとえばテンプレートパラメータである必要があります。number1number2およびnumber3通常のランタイム変数として残されました。

他の回答では、言語に応じて、演算子と関数がほぼ同じものになるさまざまな方法について既に説明しました。ただし、通常、組み込みの中置演算子記号とのような+名前付き呼び出し可能オブジェクトには構文上の違いがありoperator1ます。詳細はそれらの他の回答にお任せします。


+1「eval()言語コンストラクトを使用してPHPで実行可能」で開始する必要があります...技術的には、質問で求められた望ましい結果を正確に提供します。
アームフット

@Armfoot:質問の焦点がどこにあるかを伝えるのは難しいです。タイトルは「型演算子の変数」の側面を強調しており、演算子は単なる文字列であるevalためeval、その側面には答えていません。したがって、代替手段について説明する前に、演算子型変数が問題を引き起こす理由の説明から始めました。
MvG

私はあなたのポイントを理解していますが、すべてを文字列に詰め込むことによって、基本的に変数型がもはや関連しないことを意味していることを考慮しています(PHPは例示に使用されているため、これが質問の焦点のようです)、そして最終的には、これらの変数の一部が「タイプ演算子」である場合と同じ方法でそれらを配置できますが、同じ結果が得られます...だから私はあなたの提案が質問に対する最も正確な答えを提供すると信じています。
アームフット

2

Algol 68にはまさにその機能がありました。Algol 68の例は次のようになります。

int number1 = 5;                              ¢(タイプ 'Int')¢
op operator1 = intint aba + b ; ¢(存在しない「演算子」を入力)¢
prio operator1 = 4;
int number2 = 5;                              ¢(タイプ 'Int')¢
op operator2 = intint aba * b ;  ¢(存在しない「演算子」と入力)¢
prio operator2 = 5;
int number3 = 8;                              ¢(タイプ 'Int')¢

int型の テスト = 数値1 operator1に number2の operator2に number3¢ 5 + 5 * 8. ¢

var_dumptest);

2番目の例は次のようになります。

int number4 = 9;
op operator3 = boolint aba < b ;
prio operator3 = 3;
もし 番号1 $ operator3 number4 その後、 ¢ 5 <9(真)¢
プリント(
Fiの

演算子記号が定義され、目的の操作を含むメソッド本体が割り当てられていることに注意してください。演算子とそのオペランドはすべて型指定されており、演算子に優先順位を割り当てることができるため、評価は正しい順序で行われます。また、演算子記号と変数記号の間でフォントに若干の違いがあることに気付くかもしれません。

言語はfontingを使用して書かれているが、実際に、その日のマシンは、フォント(紙テープとパンチカード)を扱うことができませんでした、とstroppingを使用しました。プログラムはおそらく次のように入力されます。

'INT' NUMBER4 = 9;
'OP' 'OPERATOR3' = 'BOOL' ('INT' A,B) A < B;
'PRIO' 'OPERATOR3' = 3;
'IF' NUMBER1 'OPERATOR3' NUMBER4 'THEN' 'C' 5 < 9 'C'
PRINT('TRUE')
'FI'

また、何年も前に私がかつて悪用した演算子用の独自のシンボルを定義できる場合、この言語で面白いゲームをプレイすることもできます... [2]。


参照:

[1] CHLindseyとSG van der MeulenによるAlgol 68の非公式の紹介、北ホラント、1971年

[2] Algol 68フレーズ、Algol 68、BC Tompsettでのコンパイラ作成を支援するツール、Algol 68のアプリケーションに関する国際会議、1976年英国ノーリッチのイーストアングリア大学にて

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.