본문으로 건너뛰기

© 2026 Molayo

GeekNews헤드라인2026. 06. 15. 06:29

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

요약

데이터 구조에서 후행 구분자(Trailing Delimiter)의 유무가 코드 변경 및 유지보수에 미치는 영향을 분석합니다. JSON의 마지막 쉼표 금지 규칙이 가져오는 불편함과 이를 허용하는 Python, Go 등의 사례를 비교합니다.

핵심 포인트

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

JSON의 마지막 쉼표 문제

  • JSON 객체에서 멤버 사이 쉼표는 허용되지만, 마지막 멤버 뒤에 오는 쉼표는 문법상 허용되지 않음
{
"a": 1,
"b": 2,
"c": 3
}
  • 같은 객체에서
    "c": 3,

처럼 마지막 멤버 뒤에 쉼표를 붙이면 유효하지 않은 JSON이 됨

{
"a": 1,
"b": 2,
"c": 3,
}
  • 후행 쉼표가 허용되면
    "a"

앞에 "x"

를 추가하고 "c"

뒤에 "y"

를 추가할 때 같은 형태의 줄 추가만 필요함

{
+ "x": 0,
"a": 1,
"b": 2,
"c": 3,
+ "y": 4,
}
  • 현재 JSON 문법에서는 마지막 위치에 키를 추가할 때 기존 마지막 줄
    "c": 3

에도 쉼표를 붙여야 하므로 변경이 더 복잡해짐

{
+ "x": 0,
"a": 1,
"b": 2,
- "c": 3
+ "c": 3,
+ "y": 4
}
  • 요소를 제거할 때도 해당 줄만 지울 수 없고, 마지막 줄에 후행 쉼표가 남지 않는지 확인해야 함
  • 객체 값 자체가 여러 줄 배열이나 객체이면 “후행 쉼표 없음”으로 인한 변환이 더 복잡해짐

다른 언어의 비슷한 사례

Haskell 레코드

  • Haskell은 레코드 타입에서 쉼표를 각 행의 앞에 두는 “부분 불릿 포인트” 스타일을 사용할 수 있음
data Drone = Drone
{ xPos :: Int
, yPos :: Int
, zPos :: Int
}
  • 이 방식은 마지막 행을 바꾸기 쉽게 만들지만, 첫 번째 행을 바꾸기는 더 어렵게 만듦

TLA+

  • TLA+에서는 변수 목록과 시퀀스에서 끝 쉼표가 없는 형태는 유효함
VARIABLES a, b, c
vars == <<a, b, c>>
  • 같은 구문에서 마지막 항목 뒤에 쉼표를 붙이면 유효하지 않음
VARIABLES a, b, c,
vars == <<a, b, c,>>
  • TLA+ 명세를 작성할 때 최상위 변수를 계속 추가하게 되므로 이 제한이 불편해짐
  • PlusCal DSL에서는 같은 문제가 없고, 변수 선언을 세미콜론으로 나열할 수 있음
(*--algorithm foo {
variables a; b; c;

Prolog

  • Prolog 같은 논리 언어는 후행 구분자를 허용하지 않을 뿐 아니라 별도의 종료 기호를 사용함
foo(A, B, C) :-
A = 1, % comma
B = 2, % comma
C = 3. % period!
  • 마지막 마침표를 별도 줄에 두는 방식으로 중괄호처럼 볼 수도 있지만, 표준 구문이 아니며 후행 구분자도 얻지 못함
foo(A, B, C) :-
A = 1,
B = 2,
C = 3
.

더 나은 방식

후행 구분자를 허용하는 언어

  • Go는 맵 리터럴에서 마지막 항목 뒤 쉼표를 허용함
valid := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
  • Python도 딕셔너리에서 마지막 항목 뒤 쉼표를 허용함
valid = {
"a": 1,
"b": 2,
"c": 3,
}
  • Python과 Go의 쉼표는 뒤에 올 수 있지만 앞에는 올 수 없어 완전한 불릿 포인트 스타일은 만들 수 없음
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는 선행 쉼표와 후행 쉼표를 모두 허용함
sig Valid {
, a: 1
, b: 2
}
sig AlsoValid {
a: 1,
b: 2,
}
  • Alloy는 빈 구분자도 허용해 여러 개의 쉼표만 있는 줄도 유효하게 처리함
sig StillValid {
,, a: 1,,
,,,,,,,,,
,, b: 2,,
}

반론: 파싱 모호성

Prolog의 제어 구분자

  • 후행 구분자를 반대하는 논거 중 하나는 파싱이 모호해질 수 있다는 점임
  • Prolog에서 마침표로 규칙을 끝내면
    foo

bar

가 별도 정의임이 분명함

foo(A, B) :-
A = 1,
B = 2.
bar(c).
  • 규칙 종료 기호를 쉼표로 바꾸면
    bar(c)

foo

정의의 일부로 해석될 수도 있음

foo(A, B) :-
A = 1,
B = 2,
bar(c),
  • 이 경우
    foo

bar(c)

도 참일 때만 참인 것으로 해석될 수 있음

Ruby의 메서드 호출

  • Ruby에서는 줄바꿈 뒤에도 메서드 체인을 이어 쓸 수 있으며, 아래 코드는 5를 출력함
puts 3.
succ().
succ()
  • 메서드 호출 뒤 후행 구분자를 허용하면
    quux()

가 최상위 함수인지 foo

의 메서드인지 분명하지 않게 됨

foo.
bar().
baz().
quux()
  • Prolog와 Ruby 사례는 데이터 구분자가 아니라 제어 구분자와 관련된 모호성임

데이터 구문에서의 예외: Python 튜플

  • Python은 괄호를 표현식 그룹화와 튜플 정의에 모두 사용함
    (2+3)

은 표현식 평가로 처리되어 int

가 됨

>>> x = (2+3)
>>> type(x)
<class 'int'>

(2+3,)

은 후행 쉼표 때문에 단일 원소 튜플로 처리됨

>>> x = (2+3,)
>>> type(x)
<class 'tuple'>
  • Python의 이 사례는 후행 데이터 구분자가 표현식과 단일 원소 튜플을 구분하는 역할을 함

AI 자동 생성 콘텐츠

본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0