# 후행이 아닌 구분자는 즐겁지 않다

> Clean Markdown view of GeekNews topic #30384. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=30384](https://news.hada.io/topic?id=30384)
- GeekNews Markdown: [https://news.hada.io/topic/30384.md](https://news.hada.io/topic/30384.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-06-11T10:01:53+09:00
- Updated: 2026-06-11T10:01:53+09:00
- Original source: [buttondown.com](https://buttondown.com/hillelwayne/archive/nontrailing-separators-do-not-spark-joy/)
- Points: 1
- Comments: 1

## Topic Body

- 데이터 구조에서 항목을 쉼표로 나눌 때 **후행 구분자**를 허용하면 항목 추가·삭제·재배치가 같은 방식의 텍스트 변경으로 처리됨
- JSON은 마지막 멤버 뒤 쉼표를 금지해 맨 끝에 키를 추가하거나 삭제할 때 기존 줄까지 수정해야 하는 **특수 사례**가 생김
- Haskell 레코드, TLA+ 변수 선언, Prolog 규칙도 구분자 위치나 종료 기호 때문에 첫 줄·마지막 줄 변경이 서로 다르게 처리됨
- Python과 Go는 후행 쉼표를 허용하지만 선행 쉼표는 허용하지 않으며, Alloy는 **선행·후행 쉼표**를 모두 허용함
- 후행 구분자는 제어 구문에서는 파싱 모호성을 만들 수 있고, Python의 단일 원소 튜플처럼 데이터 구문에서도 의미 구분에 쓰임

---

### JSON의 마지막 쉼표 문제
- JSON 객체에서 멤버 사이 쉼표는 허용되지만, 마지막 멤버 뒤에 오는 쉼표는 문법상 허용되지 않음

```json
{
    "a": 1,
    "b": 2,
    "c": 3
}
```

- 같은 객체에서 `"c": 3,`처럼 마지막 멤버 뒤에 쉼표를 붙이면 유효하지 않은 JSON이 됨

```json
{
    "a": 1,
    "b": 2,
    "c": 3,
}
```

- 후행 쉼표가 허용되면 `"a"` 앞에 `"x"`를 추가하고 `"c"` 뒤에 `"y"`를 추가할 때 같은 형태의 줄 추가만 필요함

```diff
{
+   "x": 0,
    "a": 1,
    "b": 2,
    "c": 3,
+   "y": 4,
}
```

- 현재 JSON 문법에서는 마지막 위치에 키를 추가할 때 기존 마지막 줄 `"c": 3`에도 쉼표를 붙여야 하므로 변경이 더 복잡해짐

```diff
{
+   "x": 0,
    "a": 1,
    "b": 2,
-   "c": 3
+   "c": 3,
+   "y": 4
}
```

- 요소를 제거할 때도 해당 줄만 지울 수 없고, 마지막 줄에 후행 쉼표가 남지 않는지 확인해야 함
- 객체 값 자체가 여러 줄 배열이나 객체이면 “후행 쉼표 없음”으로 인한 변환이 더 복잡해짐

### 다른 언어의 비슷한 사례
- ## Haskell 레코드
  - Haskell은 레코드 타입에서 쉼표를 각 행의 앞에 두는 “부분 불릿 포인트” 스타일을 사용할 수 있음
  ```haskell
  data Drone = Drone
    { xPos :: Int
    , yPos :: Int
    , zPos :: Int
    }
  ```
  - 이 방식은 마지막 행을 바꾸기 쉽게 만들지만, 첫 번째 행을 바꾸기는 더 어렵게 만듦
- ## TLA+
  - TLA+에서는 변수 목록과 시퀀스에서 끝 쉼표가 없는 형태는 유효함
  ```tla
  VARIABLES a, b, c
  vars == <<a, b, c>>
  ```
  - 같은 구문에서 마지막 항목 뒤에 쉼표를 붙이면 유효하지 않음
  ```tla
  VARIABLES a, b, c,
  vars == <<a, b, c,>>
  ```
  - TLA+ 명세를 작성할 때 최상위 변수를 계속 추가하게 되므로 이 제한이 불편해짐
  - PlusCal DSL에서는 같은 문제가 없고, 변수 선언을 세미콜론으로 나열할 수 있음
  ```tla
  (*--algorithm foo {
  variables a; b; c;
  ```
- ## Prolog
  - Prolog 같은 논리 언어는 후행 구분자를 허용하지 않을 뿐 아니라 별도의 종료 기호를 사용함
  ```prolog
  foo(A, B, C) :-
      A = 1, % comma
      B = 2, % comma
      C = 3. % period!
  ```
  - 마지막 마침표를 별도 줄에 두는 방식으로 중괄호처럼 볼 수도 있지만, 표준 구문이 아니며 후행 구분자도 얻지 못함
  ```prolog
  foo(A, B, C) :-
      A = 1,
      B = 2,
      C = 3
  .
  ```

### 더 나은 방식
- ## 후행 구분자를 허용하는 언어
  - Go는 맵 리터럴에서 마지막 항목 뒤 쉼표를 허용함
  ```go
  valid := map[string]int{
          "a": 1,
          "b": 2,
          "c": 3,
      }
  ```
  - Python도 딕셔너리에서 마지막 항목 뒤 쉼표를 허용함
  ```python
  valid = {
    "a": 1,
    "b": 2,
    "c": 3,
  }
  ```
  - Python과 Go의 쉼표는 뒤에 올 수 있지만 앞에는 올 수 없어 완전한 불릿 포인트 스타일은 만들 수 없음
  ```python
  invalid = {
      , "a": 1
      , "b": 2
      , "c": 3
  }
  ```
- ## 선행 구분자와 Alloy
  - TLA+는 선행 결합과 선행 논리합 연산을 허용하지만, `(a &&)`처럼 뒤에 붙이는 방식은 허용하지 않음
  ```
  // Not TLA+ but the same semantics
  || && a == 1
     && b == 2
  
  || && a == 3
     && b == 4
  ```
  - [Alloy](https://alloytools.org/)는 선행 쉼표와 후행 쉼표를 모두 허용함
  ```alloy
  sig Valid {
      , a: 1
      , b: 2
  }
  
  sig AlsoValid {
      a: 1,
      b: 2,
  }
  ```
  - Alloy는 빈 구분자도 허용해 여러 개의 쉼표만 있는 줄도 유효하게 처리함
  ```alloy
  sig StillValid {
      ,, a: 1,,
      ,,,,,,,,,
      ,, b: 2,,
  }
  ```
  - 이런 형태는 일부 사람들에게 “[stuttering](https://www.reddit.com/r/ProgrammingLanguages/comments/1amsalm/does_your_language_support_trailing_commas/)”이라고 불림

### 반론: 파싱 모호성
- ## Prolog의 제어 구분자
  - 후행 구분자를 반대하는 논거 중 하나는 파싱이 모호해질 수 있다는 점임
  - Prolog에서 마침표로 규칙을 끝내면 `foo`와 `bar`가 별도 정의임이 분명함
  ```prolog
  foo(A, B) :-
      A = 1,
      B = 2.
  
  bar(c).
  ```
  - 규칙 종료 기호를 쉼표로 바꾸면 `bar(c)`가 `foo` 정의의 일부로 해석될 수도 있음
  ```prolog
  foo(A, B) :-
      A = 1,
      B = 2,
  
  bar(c),
  ```
  - 이 경우 `foo`는 `bar(c)`도 참일 때만 참인 것으로 해석될 수 있음
- ## Ruby의 메서드 호출
  - Ruby에서는 줄바꿈 뒤에도 메서드 체인을 이어 쓸 수 있으며, 아래 코드는 5를 출력함
  ```ruby
  puts 3.
       succ().
       succ()
  ```
  - 메서드 호출 뒤 후행 구분자를 허용하면 `quux()`가 최상위 함수인지 `foo`의 메서드인지 분명하지 않게 됨
  ```ruby
  foo.
    bar().
    baz().
  
  quux()
  ```
  - Prolog와 Ruby 사례는 데이터 구분자가 아니라 제어 구분자와 관련된 모호성임

### 데이터 구문에서의 예외: Python 튜플
- Python은 괄호를 표현식 그룹화와 튜플 정의에 모두 사용함
- `(2+3)`은 표현식 평가로 처리되어 `int`가 됨

```python
>>> x = (2+3)
>>> type(x)
&lt;class 'int'&gt;
```

- `(2+3,)`은 후행 쉼표 때문에 단일 원소 튜플로 처리됨

```python
>>> x = (2+3,)
>>> type(x)
&lt;class 'tuple'&gt;
```

- Python의 이 사례는 후행 데이터 구분자가 표현식과 단일 원소 튜플을 구분하는 역할을 함

## Comments



### Comment 59396

- Author: neo
- Created: 2026-06-11T10:01:54+09:00
- Points: 1

###### [Lobste.rs 의견들](https://lobste.rs/s/05t4zb/nontrailing_separators_do_not_spark_joy) 
- JSON 문법은 객체의 두 멤버 사이에 쉼표를 둘 수 있지만 멤버 뒤에 **후행 쉼표**를 둘 수 없다고 되어 있음. 이걸 “설계 실수”라고 부를 수는 없다고 봄. 선택지가 아니었기 때문임  
  JSON은 2000~2001년쯤 ECMAScript 3의 부분집합으로 만들어졌고, 정보성 RFC 4627은 2006년에 작성됨. JavaScript의 부분집합이라 브라우저에서 `eval`로 바로 동작한다는 점이 JSON의 목적이자 성공의 핵심이었고, 브라우저의 네이티브 JSON API는 2009년에야 추가됨  
  **ES5**에서 후행 쉼표가 명세화된 것도 2009년 12월이라, 후행 쉼표가 있는 JSON은 애초에 목적에 맞지 않아 존재할 수 없었음
  - 적극적인 행동만 실수라고 보는 듯하지만, **아무것도 하지 않는 것**도 선택이며 따라서 실수라고 부르는 것도 타당하다고 봄

- Prolog에서 가장 자주 겪는 불편 중 하나가 이거임. 술어를 작업할 때 마지막에 `,true.`를 붙여 두면, 위쪽 줄을 재정렬하거나 주석 처리할 때 마지막 마침표를 신경 쓰지 않아도 돼서 편함
  - 비슷하게 SQL에서도 우리 팀은 `where true / and ... / and ...` 형태로 **WHERE 절**을 씀. 여기서 슬래시는 줄바꿈을 뜻함  
    이렇게 하면 어떤 조건이든 특별 취급 없이 쉽게 편집할 수 있음

- Zig는 **후행 쉼표**를 허용하고, 이걸 설정 불가능한 포매터를 제어하는 데 쓸 수 있음  
  `.{1, 2, 3,}`는 다음처럼 바뀜  
  ```zig  
  .{  
     1,  
     2,  
     3,  
  }  
  ```  
  그리고 세로로 포맷된 리터럴에서 후행 쉼표를 빼면 가로 정렬을 요청하는 의미가 됨: `.{ 1,  2, 3 }`  
  다음에도 동작함: 컨테이너 타입 정의 `struct { a: u32, b: u32, }`, 함수 시그니처 `fn foo(a: u32, b: u32,) void {}`, 함수 호출 `foo(1, 2,);`  
  이런 모든 경우에 후행 쉼표로 자동 포맷을 제어할 수 있음  
  이 기능이 너무 마음에 들어서 내 HTML 언어 서버/자동 포매터에도 추가했음  
  Before:  
  ```html  
  &lt;div foo="bar" style="very-long-string" &gt;Foo&lt;/div&gt;  
  ```  
  After:  
  ```html  
  <div foo="bar"  
       style="very-long-string"  
  >Foo&lt;/div&gt;  
  ```  
  https://github.com/kristoff-it/superhtml

- 100% 동의함. **후행 구분자**가 없는 새 언어는 개인적으로 약간 감점함. 그 언어의 문법을 싫어할 정도는 아니지만 작은 상처 같은 불편함임
  - 함수 호출에서도 그런가? `foo(1,2,3,4,)`? `bar(,1,2,3,4)`? `foo()`가 가변 개수 매개변수를 허용한다면 마지막 인자는 `nil`인가? `bar()`의 첫 번째 매개변수는 `nil`인가?

- Clojure와 EDN에서는 **쉼표가 공백**임. 보통 같은 줄의 맵 리터럴에서 키-값 쌍 사이에 관례적으로 쓰지만 완전히 선택 사항임  
  ```clojure  
  {:a 1 :b 2}  
  ;=> {:a 1, :b 2}  
  {:a,1,,,,,,,,,,:b,2,} ; if you must  
  ;=> {:a 1, :b 2}  
  ```

- 여기서 긴장이 생기는 큰 이유는 같은 줄에 여러 절이 있을 때 어떤 식으로든 **구분자**가 필요하기 때문이라고 봄  
  ```  
  function(1, 2, 3, 4)  
  ```  
  이런 경우 줄 단위 차이로 인자를 추가하거나 제거하면 늘 어색해 보임. 인자 하나만 주석 처리할 때도 약간의 주의와 노력이 필요함. 대신 한 줄에 여러 항목을 넣는 간결함을 받아들이는 것임  
  하지만 한 줄에 하나의 절만 두기 시작하면, 이상적으로는 더 깔끔한 차이와 단순한 줄 주석으로 쉽게 비활성화하는 방법을 원하게 됨  
  명백한 답은 줄바꿈을 표준 구분자로 다루는 것임  
  ```  
  function(  
    1  
    2  
    3  
  )  
  ```  
  많은 언어가 `;`에 대해 이렇게 함. 한 줄에 여러 문장을 넣지 않도록 권장하는 스타일이면 `;`를 거의 보지 않게 됨. 쉼표에도 똑같이 하는 언어는 잘 모르지만, 취미 언어에서 시도해 보고 싶었던 적은 있음  
  물론 Lisp는 하위 표현식과 하위 절이 항상 완전히 구분되어 있으므로 **구분자가 아예 없어** 이 문제를 우회함

- Lisp, 정확히는 **S-표현식**을 좋아하는 이유 중 하나가 이거임. 생각할 세부사항이 하나 줄어듦
