선언 문법이나 세미콜론 같은 겉모습만으로 언어를 평가하는 태도를 비판하고, 문법과 의미론의 관계 및 문법 결정의 트레이드오프를 논한다.
URL: https://www.gingerbill.org/article/2026/02/19/choosing-a-language-based-on-syntax/
2026-02-19
사람들이 어떤 언어를 순전히 선언(declaration) 문법만으로 평가하고, 그 부분이 마음에 드는지 아닌지만으로 그 언어를 쓸지 말지 결정하는 모습을 보면 아직도 당황스럽다.
선언의 일반적인 범주는 대략 다음처럼 분류할 수 있다:
type name = value—타입 중심(type-focused)name: type = value—이름 중심(name-focused)var name type = value—수식자 중심(qualifier-focused)언어를 설계할 때 의미론(semantics)이 꽤 명확하다면, 이 선언 문법은 쉽게 바꿀 수 있고 언어의 의미론은 대부분 동일하게 유지된다(혹은 완전히 같을 수도 있다). 그런데도 사람들은 선언 문법이야말로 언어의 “성격”을 만든다고 진지하게 생각한다. 나는 이런 사고방식을 전혀 이해하지 못하겠다.
아마도 내 편향이 있긴 하다. 나는 컴파일러가 어떻게 동작하는지 알고 있기 때문이다. 하지만 예전에 내가 그런 걸 이해하지 못했을 때조차도, 나는 “보기 좋은지”가 아니라 내 필요에 따라 언어를 골랐다..
프로그래밍 언어는 단지 문법만이 아니다. 표시적 의미론(denotational semantics)나는 언어의 조작적 의미론(operational semantics)보다 표시적 의미론에 초점을 맞추는 것이 더 중요하다고 늘 느꼈다. (적어도 나에게는) 표시적 의미론이 결정되면 조작적 의미론은 “자명해지기” 때문이다., 조작적 의미론(operational semantics), 대수적 의미론(algebraic semantics) 같은 의미론은 실제로 존재한다.
문제는 많은 초보 프로그래머들이 이런 정신적 구분을 갖고 있지 않아서, 모든 언어가 결국 “문법”만 다를 뿐 거의 같다고 생각한다는 점이다. 함수형 언어나 데이터베이스 언어를 접해보면, 혹은 스프레드시트도 하나의 언어라는 사실을 알게 되면 생각이 달라질 것이다.
나는 2018년에 선언 문법의 서로 다른 계열들을 다룬 글인 선언 문법의 미학에 대하여(On the Aesthetics of the Syntax of Declarations)를 쓴 적이 있다. 하지만 사람들이 언어를 사용할지 말지 결정할 때 사물을 바라보는 방식은 여전히 내게 이상하게 느껴진다.
내 언어 Odin을 예로 들어보자. 선언 문법이 다음처럼 달라지면 사람들이 이게 의미론을 크게 바꾼다고 생각할까?
// 실제 Odin
x: i32 = 123
y := 123 // 타입 추론
FOO :: "some constant"
bar :: proc() -> i32 {
return 123
}
이걸 이렇게 바꾼다고 하자:
// 수식자 중심
var x i32 = 123
var y = 123 // 타입 추론
const FOO = "some constant"
proc bar() -> i32 {
return 123
}
기껏해야 var와 const 때문에 타이핑이 조금 더 늘어나는 정도일 것이다. 결국 이는 인체공학(ergonomics)이나 “타이핑 최적화”의 문제인데(그리고 타이핑은 결코 병목이 아니다), 그게 전부다.
나는 후자의 방식에서도 컴파일러의 대부분은 사실상 동일하리라고 본다. 왜냐하면 컴파일러는 어차피 여러 종류의 선언을 구분(disambiguate)해야 하기 때문이다 내부적으로 컴파일러에서는 이를 엔티티(entities) 라고 부른다. 어떤 컴파일러는 심볼(symbol) 이라는 용어를 쓰기도 하고, 다른 용어를 쓰기도 한다.. 다만 각 문법 계열은 각기 트레이드오프가 있고, 그로 인해 어떤 가능성은 허용되고 어떤 가능성은 막힌다.
문법은 어떤 의미론이 가능한지에 대한 가능성을 제한한다.
여러 언어에서 여러 번 봐온 또 다른 비슷한 이야기:
<올해>에 세미콜론이요? 아직도 그걸 쓰나요? 이제 필요 없다는 걸 아직도 모르나요?
내가 보기로 “현대적” 언어들이 여전히 세미콜론을 쓰는 이유를 이해하지 못한다는 이런 정서는 대체로 다음 중 하나에서 온다:
내가 Odin을 처음 만들었을 때는 세미콜론이 대부분 필수였고 많은 위치에서 추론되기도 했지만, 결국 나는 세미콜론을 문장 종결자로서 완전히 선택 사항(optional)으로 만들었다. 그렇게 만든 이유는 두 가지였다:
두 번째 이유는 우스워 보일 수 있지만 실제로 그랬다. 세미콜론이 있다는 이유만으로 Odin을 시도조차 하지 않으려는 사람들이 있었다. 그래도 핵심 이유는 첫 번째였다. 특히 직장에서도 내 동료들 중 다수가 C/C++을 수십 년 해온 습관 때문에 코드베이스에서 세미콜론을 여전히 쓰고 있다 우리는 결국 정리할 것이다. 필요 없는 세미콜론을 코드베이스에서 지우는 일이 odin strip-semicolon을 호출하는 것만큼 간단하더라도 말이다..
하지만 언어에서 세미콜론을 선택 사항으로 만드는 것은 몇 가지 타협을 수반할 수 있다.
한 가지 방법은 문법을 설계해서 세미콜론이 어디에 있어야 하는지 “자명하게” 추론되도록 하는 것이다. Lua가 이런 언어의 예이고, 세미콜론이 필요한 경우는 그것이 호출(call)로 오해될 수 있을 때다:
(function() print("Test1") end)(); -- 저 세미콜론은 필요하다
(function() print("Test2") end)()
다른 방법은 특정 규칙에 기반한 자동 세미콜론 삽입(ASI: automatic semicolon insertion)을 사용하는 것이다. 불행히도 많은 사람들이 이 방식의 첫 경험을 JavaScript로 하고, JavaScript의 구현은 정말 형편없다. 그래서 보통 사람들은 잠재적 실수를 없애기 위해 그냥 세미콜론을 늘 적어버린다. 하지만 Go, Python, Odin처럼 비교적 멀쩡한 ASI 접근을 가진 언어들도 있다.
Go의 접근은 순수하게 어휘(lexical) 규칙이다. 이는 여러 줄에 걸친 리스트에서는 강제로 후행 쉼표(trailing comma)를 써야 한다는 뜻이기도 하다. 하지만 이는 단순함을 위해서만이 아니라 코드 스타일을 강제하기 위한 목적도 있을 것이다.
Python과 Odin의 접근은 어휘 규칙 + 구문(syntactical) 규칙이다. Odin의 어휘 규칙은 Go와 매우 비슷하지만, 추가 구문 규칙 덕분에 훨씬 덜 짜증나고 더 다양한 코드 스타일 옵션을 허용한다. Odin의 규칙은 Python과 매우 비슷한데, 괄호 안(( ) 및 [ ], 그리고 표현식이나 레코드 블록으로 사용될 때의 { })에서는 줄바꿈 기반 “세미콜론”을 무시한다.
Allman 스타일의 중괄호를 허용하기 위해 Odin은 문법의 많은 지점에서 추가로 단일 개행을 허용하지만, 오직 한 줄만 추가로 허용한다. 이는 프로시저 타입 선언과 프로시저 리터럴 사이의 특정 모호성을 피하기 위함이다:
a_type :: proc()
a_procedure_declaration :: proc() {
}
another_procedure_declaration :: proc()
{
}
another_type :: proc() // 시그니처와 `{` 사이에 추가 개행이 있음에 주목
{ // 이것은 그냥 블록이다
}
사람들이 (표시적 혹은 조작적) 의미론이 아니라, 가장 사소한 문법 문제만으로 정말로 언어를 고른다는 게 어떻게 가능한가? 아니면 대부분의 사람들은 실제로 “프로그래밍”을 하는 게 아니라, 문법을 “패턴 매칭”으로 대충 엮어놓고 되길 바라는 걸까?
내가 이렇게 냉소적일 필요는 없고, 답은 훨씬 단순할지도 모른다: 첫 노출 편향(first exposure bias). 이는 어떤 선택지가 많더라도 합리적 판단으로 고르는 게 아니라, 먼저 익숙해진 것 때문에 단순히 선호 가 생기는 경향이다. 사람들은 익숙한 것에 머무르는데, 그 자체는 합리적일 수 있다. 하지만 시도조차 해보지 않고 싫어한다고 말하는 건 다소 비합리적이다.
그렇지만 문법이 싫어서 그 언어를 쓰지 않는 데에는 합리적인 이유도 있다고 생각한다. 때로는 그 문법이 언어의 의미론과 맞지 않게 너무 두서없거나 일관성이 없을 수도 있다. 때로는 너무 빽빽하고 시길(sigil)로 가득 차 있어서 Perl은 코드를 훑어보거나 읽을 때 정말로 두통이 온다. 과장이 아니다. 코드를 스캔 하기 어렵다 여기서 나는 스캔(scanning) 과 읽기(reading) 를 구분하고 있다. 하지만 이 구분은 다른 글의 주제다. 그리고 코드 안에서 패턴을 찾기가 힘들다. 또 때로는 너무 이질적이라서, 배우는 데 걸리는 시간이 다른 대안보다 훨씬 길 수도 있다.
나는 C의 선언이 사용과 일치한다(declarations match usage)는 점에 대해 써온 적이 있다. 내 주장으로는, 사람들이 C 파서/컴파일러를 만들지 않았다면 C 표준을 읽지 않았다면. 대부분 이 사실을 깨닫지 못한다. 대부분은 C의 선언 문법을 타입 우선이라 생각하거나, 혹은 무슨 신비한 난독화 문법이라서 대충 찍어 맞춘다고 생각한다.
Odin에서는 읽기, 파싱, 이해를 개선하기 위해 타입 문법을 좀 더 파스칼 스타일로 설계했다:
x: [3]^int // int에 대한 포인터의 길이 3 배열
y: ^[3]int // 길이 3 int 배열에 대한 포인터
C에서 불행히도 이에 대응하는 표기는:
int *x[3]; // int에 대한 포인터의 길이 3 배열
int (*y)[3]; // 길이 3 int 배열에 대한 포인터
C의 “선언이 사용과 일치” 접근을 따르는 대신, Odin의 접근은 “왼쪽에는 타입, 오른쪽에는 사용”이다:
x: [3]int // LHS에 타입
x[1] = 123 // RHS에 사용
y: ^int = ...
y^ = 123
z: [6]^int = ...
z[3]^ = 123
Odin의 매우 강하고 직교적인(orthogonal) 타입 시스템과 결합되면, 것들은 예상대로 그냥 동작한다 TM. 나 같은 평범한 인간에게도 이해하기 쉽다.
나는 Odin에서 포인터에 캐럿 ^를 쓰는 것에 대한 비판을 많이 봤다 대개 언어를 한 번도 써보지 않은 사람들이 가상의 문제만 떠올리며 하는 얘기다. 그리고 그런 키보드에서 {}나 [] 같은 기호도 타이핑하기 어렵기는 마찬가지일 텐데, 같은 사람들이 그런 것에 대해서는 거의(혹은 전혀) 불평하지 않는다. 이는 많은 키보드 레이아웃에서 ^가 데드 키(dead key)이기 때문인데, Odin은 :=를 통한 타입 추론, 자동 역참조(x^.y가 x.y가 됨), 훨씬 나은 타입 시스템(예: 진짜 배열), 그리고 전반적인 의미론 덕분에 C에서보다 포인터 타입을 명시적으로 타이핑해야 할 필요가 훨씬 적다. 하지만 사람들이 언어를 직접 써보지도 않고, 그런 결정의 실제 결과를 끝까지 생각해보지도 않은 채 언어 간 문법만 가지고 불평할 때 생기는 문제가 바로 이것이다.
나는 Odin의 문법이 가능한 한 일관되도록 많은 시간을 쏟았다 그리고 절대적으로 필요할 때만 “불일관”하게 만들었다. 또한 언어의 의미론에 비추어 인체공학적으로 매우 편하게 만들려고 했다. 하지만 Odin의 선언 문법이나 선택적 세미콜론이 언어의 성패를 가른다고 생각하진 않는다. 내게 Odin의 본질은 C처럼 보이게 하는 것이 아니라, 현대적인 C 대안처럼 느껴지게 하면서 프로그래밍의 즐거움을 주는 것이다.
C의 타입 중심 선언 문법에 꽤 가깝게 유지하는 언어들, 예를 들어 C3 같은 언어들도 다른 기능과 구문에서는 C의 문법에서 많이 벗어난다. 이는 C의 문법에 반복하면 안 될 문제가 많이 있기 때문이며, 특히 선언 문법이 그렇다.
때로는 작은 문법 결정이 마찰을 만들고, 그 마찰은 누적된다. Odin을 설계할 때의 접근 중 하나는 직접적인 마찰을 주기보다는, 보통 더 나은 방식으로 사람들을 살짝 유도(nudge)하는 것이었다. 하지만 마찰이 필요하다면, 필요한 것은 보통 사포(sandpaper)가 아니라 벽돌벽(brick wall)에 가깝다. 그리고 이 경우 문법은 언어 자체의 의미론을 반영하는 것이며, 많은 사람들이 그 점을 오해한다. 문법이 전부가 아니다. 의미론이 언어의 실제 기반이다.
동료 언어 설계자라면, 솔직히 말해서: 이런 사람들은 무시하라. 누구나 의견을 가질 수 있지만, 그 의견은 누구에게도 가치가 없을 수 있다. 심지어 그 의견을 가진本人에게조차.
누군가가 (구체적인 세부가 아니라) 당신의 언어에서 어떤 문법 결정의 일반 범주에 대해 불평한다면—예컨대 선언 문법, 세미콜론을 쓰느냐 마느냐, 코어/표준 라이브러리가 프로시저 이름에 snake_case를 쓰느냐 camelCase를 쓰느냐 같은 것, 혹은 그 밖의 어처구니없는 주장이라면—그냥 무시하라.
사람들이 이런 “사소한” 문법 결정으로 언어를 평가하는 이유의 상당수는, 아마도 다른 프로그래밍 언어 경험이 많지 않기 때문일 것이다. 나는 사람들이 프로그래밍과 다른 언어들에 더 익숙해질수록, 이런 감정은 완전히 사라지고 결국 프로그래밍 자체에만 집중하게 된다는 것을 봤다. 문법은 “감상”하기 위한 것이 아니라, 읽기 위한 것이다.
인터넷의 아무 랜덤한 사람이 아니라, 당신이 가치 있다고 보고 신뢰하는 사람들의 의견을 찾아라.
문법만 보고 언어를 고르지 말라. 시간이 지나며 당신에게 가장 큰 영향을 미치는 것은 언어의 실제 의미론이니, 그것을 고려하라.