왜 변수 유형 '연산자'를 가진 언어가 그렇게 적은가?


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
일반적으로 당신이하고 싶은 것은 그러한 기능을 구현하는 일반적인 방법 인 일종의 람다, 클로저 또는 익명 함수로 메타 프로그래밍의 어떤 형태를 지원하는 모든 언어로 덮여 있습니다. 메소드가 일류 시민 인 언어를 사용하면 변수와 거의 동일하거나 유사한 언어를 사용할 수 있습니다. 대부분의 그러한 언어에서는 변수에 저장된 메소드를 실제로 호출하려는 것이 분명해야하기 때문에 여기서 사용하는 간단한 구문은 아니지만 정확하게 설명해야합니다.
thorsten müller

7
@MartinMaat 함수형 언어는 이것을 많이합니다.
Thorbjørn Ravn Andersen

7
haskell에서 연산자는 다른 함수와 같은 함수입니다. 유형 (+)Num a => a -> a -> aIIRC입니다. 당신은 또한 ( a + b대신 대신 (+) a b) 쓰여질 수 있도록 함수를 정의 할 수 있습니다
sara

5
@enderland : 수정으로 질문의 목표가 완전히 바뀌 었습니다. 언어가 존재하는지 묻는 것이 아니라 왜 존재하지 않는지를 묻는 것이 었습니다. 편집 내용이 혼란스러워하는 독자가 많이 생길 것으로 생각합니다.
Bryan Oakley

5
@enderland : 사실이지만, 주제를 완전히 바꾸는 것은 혼란 스러울뿐입니다. 주제가 아닌 경우 커뮤니티는 투표를 종료합니다. (내가 이것을 쓸 당시의) 답변 중 어느 것도 그 질문에 대해 쓰여진 것처럼 의미가 없습니다.
Bryan Oakley

답변:


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과 관련하여, 가장 빠진 것은이 부서가 $괄호 (특히 여러 개의 중첩)를 제거 하는 연산자입니다. -가변 함수, 예를 들어 Python 및 Java 제외. OTOH 단항 기능 구성 을 훌륭하게 수행 할 수 있습니다 .
9000

6
예, 연산자는 특별한 구문을 가진 함수일뿐입니다. 그러나 일반적으로 함수와 함께 가지 않는 연산자와 묵시적인 계약이 있기 때문에 연산자 오버로딩의 팬이 아닙니다. 예를 들어 "+"는 운영자 우선 순위, 통신 성 등과 같은 특정 기대치를 가지고 있으며 이러한 기대에 반하는 것은 사람들을 혼란스럽게하고 버그를 생성하는 확실한 경로입니다. Javascript를 좋아하지만 추가와 연결을 위해 +를 구별하는 것을 선호했을 것입니다. 펄은 바로 거기에있었습니다.
fool4jesus

4
파이썬에는 표준 operator모듈이 있으며,이 모듈은을 작성하는 것이 아니라 action = operator.add정의하는 모든 유형에서 작동하도록합니다 . +int
dan04

3
Haskell에서는 +Num 유형 클래스에서 작동하므로 +생성하는 모든 새 데이터 유형에 대해 구현할 수 있지만 functor에 대한 fmap과 같은 다른 방법으로 수행 할 수 있습니다. Haskell이 운영자 과부하를 허용하는 것은 당연한 일입니다.
Martin Capodici

17
사람들이 왜 과부하에 고정되어 있는지 잘 모르겠습니다. 원래 질문은 과부하에 관한 것이 아닙니다. 연산자에 대해 일류 가치로 생각합니다. $operator1 = +표현식 을 작성 하고 사용하기 위해 연산자 오버로딩을 전혀 사용할 필요가 없습니다 !
Andres F.

16

어떤 종류의 메타 프로그래밍 을 허용하는 언어가 많이 있습니다 . 특히 나는 Lisp 언어 군에 대해 아무런 대답을하지 않은 것에 놀랐습니다 .

Wikipedia에서 :

메타 프로그래밍은 프로그램을 데이터로 취급 할 수있는 컴퓨터 프로그램을 작성하는 것입니다.

나중에 텍스트 :

Lisp는 아마도 역사적 우선 순위와 메타 프로그래밍의 단순성과 힘 때문에 메타 프로그래밍 기능을 갖춘 전형적인 언어 일 것이다.

리스프 언어

다음은 Lisp에 대한 간단한 소개입니다.

코드를 보는 한 가지 방법은 일련의 지침입니다. 이렇게 한 다음 수행하고 다른 작업을 수행하십시오. 이것은 목록입니다! 프로그램이 수행 할 작업 목록입니다. 물론 목록 안에 루프 등을 나타내는 목록을 만들 수도 있습니다.

우리는 리스프 함수 호출처럼 보이는 뭔가 얻을 (ABCD) : 우리는 요소 A, B, C를 포함하는 목록을 나타내는 경우,이 싶습니다 a기능이며, b, c, d인수가됩니다. 사실이라면 전형적인 "Hello World!" 프로그램은 다음과 같이 작성 될 수 있습니다.(println "Hello World!")

물론 또는 무언가로 평가되는 목록 b일 수도 있습니다. 다음 : ""추가 할 수 있습니다 : 4 "인쇄합니다.cd(println "I can add :" (+ 1 3) )

따라서 프로그램은 중첩 된 목록의 시리즈이며 첫 번째 요소는 함수입니다. 좋은 소식은리스트를 조작 할 수 있다는 것입니다! 따라서 프로그래밍 언어를 조작 할 수 있습니다.

Lisp의 장점

리스프는 프로그래밍 언어를 만들기위한 툴킷만큼 프로그래밍 언어가 아닙니다. 프로그래밍 가능한 프로그래밍 언어.

이것은 Lisps에서 새로운 연산자를 만드는 것이 훨씬 쉬울뿐만 아니라 함수에 전달 될 때 인수가 평가되기 때문에 다른 언어로 일부 연산자를 작성하는 것도 거의 불가능합니다.

예를 들어 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위와 같이 일부 연산자를 작성할 수 있어야합니다 .

실제 언어

여기서 정확히 범위를 벗어난 정도를 보여 주지만, 한 Lisp에서 프로그래밍을 시도하여 자세히 알아볼 것을 권장합니다. 예를 들어 다음을 살펴볼 수 있습니다.

  • 구성표 , 작은 코어를 가진 오래되고, "순수한"리스프
  • 공통 Lisp, 잘 통합 된 객체 시스템을 갖춘 더 큰 Lisp 및 많은 구현 (ANSI 표준 임)
  • 입력 된 리스프 라켓
  • Clojure가 가장 좋아하는 위의 예제는 Clojure 코드였습니다. JVM에서 실행중인 최신 Lisp SO 에도 Clojure 매크로의 예가 있습니다 (그러나 이것이 시작하기에 적합한 장소는 아닙니다. 4clojure , braveclojure 또는 clojure koans 를 먼저 살펴볼 것입니다 ).

그런데 Lisp는 LISt Processing을 의미합니다.

당신의 예에 대하여

아래 Clojure를 사용하여 예를 들어 보겠습니다.

addClojure (defn add [a b] ...your-implementation-here... )에서 함수를 작성할 수 있다면 +그렇게 이름을 지정할 수 있습니다 (defn + [a b] ...your-implementation-here... ). 이것은 실제로 실제 구현 에서 수행되는 것입니다 (함수 본문은 조금 더 복잡하지만 정의는 본질적으로 위에서 쓴 것과 동일합니다).

접두사 표기법은 어떻습니까? Well Clojure는 prefix(또는 폴란드어) 표기법을 사용하므로 infix-to-prefix접두사가 붙은 코드를 Clojure 코드로 바꾸는 매크로를 만들 수 있습니다. 실제로 놀랍도록 쉽습니다 (실제로 clojure koans 의 매크로 연습 중 하나입니다 )! 야생에서도 볼 수 있습니다 (예 : Incanter $=macro 참조) .

koans의 가장 간단한 버전은 다음과 같습니다.

(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 연산자를 정의 할 수 있습니다. 새로운 추상화가 대중화됨에 따라 (예를 들어 객체 지향 프로그래밍) 항상 Lisp에서 쉽게 구현할 수 있습니다. DNA처럼, 그런 언어는 스타일에서 벗어나지 않습니다.”

— Paul Graham, ANSI 공통 리스프

“Lisp에서의 프로그래밍은 우주의 원초적 힘을 가지고 노는 것과 같습니다. 손가락 끝이 번개처럼 느껴집니다. 다른 언어조차 친밀감이 없습니다.”

— Glenn Ehrlich, Lisp로가는 길


1
OP가 요구하는 것을 지원하기 위해 메타 프로그래밍은 흥미롭지 만 필요하지는 않습니다. 일급 기능을 지원하는 모든 언어로 충분합니다.
Andres F.

1
OP의 질문에 대한 예 : (let ((opp # '+)) (print (app opp'(1 2))))
Kasper van den Berg

1
Common Lisp에 대한 언급이 없습니까?
coredump

3
언어에 대한 패널 토론에서 Ken 은 APL의 우선 순위에 대해 이야기하고 "나는 거의 괄호를 사용하지 않습니다!"라고 결론을 내 렸습니다. 관객 중 누군가가 소리 쳤다. " 딕이 모두 다 사용 했기 때문이다 !"
JDługosz

2
C ++ 스타일 언어에서는을 다시 구현할 수 if있지만 thenelse인수를 람다 로 감싸 야합니다 . PHP와 JavaScript에는 function(), C ++에는 람다 가 있으며 C에는 람다가 포함 된 Apple 확장 이 있습니다 .
Damian Yerrick 18시 51 분

9

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

대부분의 언어 구현에는 파서가 코드를 분석하고 코드를 작성하는 단계가 있습니다. 예를 들어 표현식 5 + 5 * 8은 다음과 같이 파싱됩니다.

  +
 / \
5   *
   / \
  8   8

우선 순위에 대한 컴파일러의 지식 덕분입니다. 연산자 대신 변수를 제공하면 코드를 실행하기 전에 올바른 순서로 작업을 알 수 없습니다. 대부분의 구현에서는 심각한 문제가 될 수 있으므로 대부분의 언어는이를 허용하지 않습니다.

물론 파서는 위의 구문을 일련의 식과 연산자로 구문 분석하여 런타임에 정렬하고 평가하는 언어를 생각할 수 있습니다. 아마도 이것에 대한 응용 프로그램은 많지 않을 것입니다.

많은 스크립팅 언어를 사용하면 expr런타임 에 임의의 표현식 (또는 경우와 같이 적어도 임의의 산술 표현식)을 평가할 수 있습니다. 거기에서 숫자와 연산자를 하나의 표현으로 결합하고 언어가 그것을 평가하게 할 수 있습니다. PHP (및 기타 여러 가지)에서이 함수를라고 eval합니다.

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

컴파일 타임에 코드를 생성 할 수있는 언어도 있습니다. D믹스 인 표현은 내 마음에옵니다.

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

여기 operator1operator2컴파일 타임에 알려진 문자열 상수, 예를 들어 템플릿 매개 변수가 될해야합니다. number1, number2number3정상 실행 변수로 남았다.

다른 답변은 언어에 따라 연산자와 함수가 어떻게 동일한 지 다양한 방법에 대해 이미 논의했습니다. 그러나 일반적으로 내장 된 내장 연산자 기호와 +명명 된 호출 가능 기호 사이에는 구문상의 차이가 operator1있습니다. 다른 답변에 세부 사항을 남겨 두겠습니다.


+1 당신은 " eval()언어 구문 으로 PHP에서 가능하다"로 시작해야한다 ... 기술적으로 그것은 질문에서 원하는 결과를 정확하게 제공한다.
Armfoot

@Armfoot : 질문의 초점이 어디에 있는지 말하기는 어렵습니다. 제목은“가변 연산자 유형”측면을 강조하며 연산자는 단지 문자열 eval이기 때문에 그 측면에는 대답하지 않습니다 eval. 따라서 대안을 논의하기 전에 연산자 유형 변수가 문제를 일으키는 이유에 대한 설명으로 시작했습니다.
MvG

나는 당신의 요점을 이해하지만 모든 것을 문자열로 채우면 기본적으로 변수 유형이 더 이상 관련이 없다는 것을 암시한다고 생각합니다 (PHP가 예시에 사용 되었기 때문에 이것이 질문의 초점 인 것 같습니다). 동일한 결과를 얻는 동안 해당 변수 중 일부가 "유형 연산자"인 것처럼 같은 방식으로 배치 할 수 있습니다. 그래서 귀하의 제안이 질문에 대한 가장 정확한 답변을 제공한다고 생각합니다.
Armfoot

2

Algol 68은 바로 그 기능을 가지고있었습니다. Algol 68의 예는 다음과 같습니다.

int number1 = 5;                              ¢ (Type 'Int') ¢
op operator1 = int ( int a , b ) a + b ; ¢ (존재하지 않는 '연산자'타입) ¢
prio operator1 = 4;
INT 숫자 2 = 5;                              ¢ (Type 'Int') ¢
op operator2 = int ( int a , b ) a * b ;  ¢(존재하지 않는 '연산자'유형) ¢
prio operator2 = 5;
INT 번호 3 = 8;                              ¢ (유형 'Int') ¢

INT의 테스트 = 번호 1 는 operator1 숫자 2 operator2 번호 3 ; ¢ 5 + 5 * 8. ¢

var_dump ( 테스트 );

두 번째 예는 다음과 같습니다.

정수 4 = 9;
op 연산자 3 = bool ( int a , b ) a < b ;
프리 오 연산자 3 = 3;
경우 번호 1 $의 operator3의 number4 다음 ¢ 5 <9 (참) ¢
인쇄 ( 사실 )
Fi를

연산자 기호가 정의되고 원하는 조작을 포함하는 메소드 본문이 지정됩니다. 연산자와 피연산자는 모두 유형이 지정되며 연산자에 우선 순위를 지정할 수 있으므로 평가가 올바른 순서로 수행됩니다. 또한 연산자 기호와 변수 기호 사이에 글꼴에 약간의 차이가 있음을 알 수 있습니다.

실제로 언어는 글꼴을 사용하여 작성되었지만 오늘날의 기계는 글꼴 (종이 테이프 및 천공 카드)을 처리 할 수 ​​없었으며 스트로 핑 이 사용되었습니다. 이 프로그램은 아마도 다음과 같이 입력 될 것입니다 :

'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] 1971 년 North Holland의 CHLindsey와 SG van der Meulen의 Algol 68 에 대한 비공식적 인 소개 .

[2] Algol 68 Phrases , 1976 년 영국 Norwich의 이스트 앵글리아 대학교에서 Algol 68의 응용에 관한 국제 회의에서 BC Tompsett의 Algol 68에서 컴파일러 작성을 지원하는 도구 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.