문과 표현식의 이원화가 언어를 불필요하게 복잡하게 만든다는 논지와, 예외 처리·구문 변환·리팩터링에서 생기는 문제를 지적한다.
Abstract Heresies: 문과 표현식
===============
컴퓨터 과학과 프로그래밍에 대한 비정통적 의견.
2024년 4월 30일 화요일
리습(Lisp)이나 OCaml 같은 일부 언어에서는 모든 언어 구성 요소가 값을 돌려주는 표현식이다. 자바나 파이썬 같은 다른 언어에는 두 종류의 언어 구성 요소가 있다. 합성적으로 조합되고 반환값이 있는 표현식과, 순차적으로 조합되고 반환값이 없어 부수 효과로만 동작해야 하는 문. 언어에 문을 두는 것은 불필요하게 일을 복잡하게 만들 뿐인데, 언어 설계자들은 거기서 더 나아가 제멋대로라고밖에 볼 수 없는 복잡성까지 추가하려는 경향이 있다.
보통 표현식이 기대되는 문맥에서 문을 사용할 수는 없다. 반환값이 없기 때문이다. 반대로 문이 기대되는 문맥에서는 표현식의 반환값을 단순히 버리면 표현식을 사용할 수 있다. 이는 결국 문맥이 두 종류라는 뜻이다. 정상적인 설계자라면 문 맥과 표현식 맥 사이를 전환하는 방법을 제공할 텐데, 언어 설계자들은 대개 이를 생략한다.
바인딩이나 반복 같은 언어 구성 요소는 문이나 표현식, 또는 둘 다로 제공되어야 한다. 언어 설계자들은 어떤 것을 문으로, 어떤 것을 표현식으로 만들지 변덕이나 컴파일러 구현의 용이성에 따라 무작위로 정하는 듯하다. 필요한 구성 요소가 있는데 현재 문맥이 맞지 않으면 문맥을 전환해야 하는데, 그 방법이 없다면 코드를 다시 써야 할 수도 있다. 예를 들어, 예외를 발생시킬 수 있는 부분 표현식의 예외를 처리하고 싶다면, 그 표현식을 일련의 문으로 다시 써야 할 것이다.
제대로 된 언어 설계자라면 같은 구성 요소를 문 형태와 표현식 형태 모두에서 동일한 구문(syntax)으로 쓰게 할 텐데, 보통은 형태에 따라 구문이 크게 달라진다. C에서 순차적 문은 세미콜론으로 종료된다. 하지만 순차적 표현식은 쉼표로 구분된다. 조건문은 if/else를 쓰지만, 조건 표현식은 ?:를 쓴다. 리팩터링할 때 단순히 코드를 문맥 간에 이동시킬 수 없고, 구문까지 바꿔야 한다.
함수를 작성하면 언어에 새로운 표현식이 추가된다. 하지만 함수 본문은 표현식이 아니라 문이다. 이로 인해 호출 지점(표현식 문맥)에 함수 본문(문)을 치환할 수 없으므로, 자동으로 참조 투명성이 깨진다.
try/catch는 악몽이다. 보통 try/catch는 문으로만 제공되고 표현식이 아니어서, 부분 표현식에서 예외 처리를 쓸 수 없다. try/catch로부터 부수 효과 없이 값을 얻을 방법이 없다. 반면 Throw는 값을 던지니 표현식 문맥으로도 던질 수 있을 텐데, catch가 문이라 막혀 버린다.
언어 구성 요소가 두 종류이고, 어떤 것은 어떤 문맥에서는 쓸 수 있고 다른 문맥에서는 쓸 수 없으며, 문맥에 따라 구문까지 달라지는 상황은 프로그램 작성을 그만큼 더 어렵게 만든다. 현재 문맥이 무엇인지, 어떤 구문을 써야 하는지 계속 추적하며 프로그램을 작성해야 하고, 수시로 앞뒤를 바꿔 가며 전환해야 한다. 컴파일러가 필요한 임시 변수를 도입하고 제어 흐름을 재작성해서 모든 언어 구성 요소를 어느 문맥에서든 쓸 수 있게 만드는 것은 쉽다. 하지만 언어 설계자들은 신경 쓰지 않고, 그 수작업을 프로그래머에게 떠넘긴다.
그리고 이 모든 혼란은 모든 것을 값을 돌려주는 표현식으로 만들면 간단히 피할 수 있다.
Posted by Joe Marshall at 오전 9:18![]()
![]()
이메일로 보내기BlogThis!X에 공유Facebook에 공유Pinterest에 공유
레이블: 언어 설계

Anonymous 님이 씀... 그런데 (values)는 어떨까요? 표준에서는 약간의 회색 지대 아닌가요?
fadrian 님이 씀... 더 나쁜 점은 코드 생성을 더 어렵게 만든다는 겁니다. 문맥을 추적해야 하고 경우에 따라 서로 다른 코드를 생성할 준비를 해야 하니까요. 이는 베이스 언어로 번역기를 작성하는 일을 더 어렵게 만들 뿐 아니라, 매크로 시스템 같은 기능의 유용성도 떨어뜨립니다. 그런 도구를 써서 산출물을 작성하는 일 자체가 더 복잡하고 어렵게 되거든요.
Joe Marshall 님이 씀... (values)는 리습에서 덜 문제입니다. 값을 기대하는 문맥에 값을 전혀 반환하지 않으면 그냥 NIL이 들어오니까요.
코드 생성은 정말 골칫거리입니다. 어느 문맥으로 확장되는지 질의할 수 없으니, 값을 기대하는 문맥으로 확장하는 매크로 하나와 문 문맥으로 확장하는 매크로 하나, 이렇게 두 개를 작성해야 합니다.
연속 전달 스타일(continuation-passing style, CPS)도 문제입니다. 한쪽 문맥에는 값을 만들어 내는 연속(continuation)이 필요하고 다른 쪽 문맥에는 값을 만들지 않는 연속이 필요한데, 컴파일러는 어느 쪽이 필요한지 알려줄 방법을 제공하지 않습니다.
구독: 게시글 댓글(Atom)