가변 변수에 대한 John Carmack의 견해

7 hours ago 2

Hacker News 의견
  • 2년간 Clojure를 사용한 후, 불변성이 주는 명료함을 다른 개발자에게 설명하기가 정말 어렵다는 걸 느꼈음
    상태 변화를 통해 효과를 일으키는 사고방식에 익숙한 사람들은 직접 경험해보기 전에는 이해하기 힘듦

    • 변수 변경은 암묵적인 순서 의존성을 만들어냄
      예를 들어 x = 7; x = x + 3; x = x / 2처럼 작성하면 순서를 바꿔도 오류는 없지만 결과가 달라짐
      반면 x1, x2처럼 새 변수를 쓰면 잘못된 순서에서 오류가 발생해 문제를 명확히 드러냄
      결국 단일 할당(single assignment)은 이런 의존성을 명시적으로 표현하는 방식임
    • 나도 Scheme에서 비슷한 경험을 했음
      함수형 언어를 써본 적 없는 동료들에게 함수 중심의 사고가 얼마나 테스트하기 쉽고 깔끔한지 설명해도 잘 와닿지 않았음
      Python은 함수형 스타일을 읽기 좋게 쓰기 어렵고, JS가 오히려 더 낫다고 느낌
      결국 호기심 많은 개발자만이 Clojure 같은 언어를 시도하게 됨
    • 불변 데이터와 순수 함수를 기본으로 하면, 함수는 완전히 블랙박스처럼 다룰 수 있음
      함수는 외부 상태를 몰라도 되고, 외부도 함수 내부를 몰라도 됨
      프로그램 전체 상태를 몰라도 특정 함수만 독립적으로 테스트하거나 디버깅할 수 있음
    • 불변성과 가변성을 단순히 대립시키는 건 복잡성을 회피하는 것임
      Haskell 커뮤니티가 결국은 타입 시스템 안에서 가변성을 재발명하려는 걸 보면 흥미로움
      핵심은 부작용을 최소한의 비용으로 통제하는 것임
    • Clojure는 내가 배운 언어 중 가장 영향력 있는 언어였음
      Haskell은 타입 시스템 때문에 진입 장벽이 높았고, F#은 너무 타협적이라 C# 문법으로 코딩하게 됨
      Clojure의 homoiconicity와 강력한 데이터 구조 덕분에 ‘값으로 일한다’는 개념이 처음으로 명확히 와닿았음
      직업적으로 쓰진 않겠지만, 함수형 언어나 Lisp 경험이 없는 사람에게는 꼭 추천하고 싶음
  • 변수는 기본적으로 불변이고 모든 것이 표현식(expression) 이었으면 좋겠음
    하지만 현실은 Clojure 개발자로서 Python의 침공에 시달리는 중임

    • 나도 Python 개발자지만 개인 프로젝트에서만 Clojure를 써봄
      이제는 TypeScript의 침공에 시달리고 있어서 공감됨
    • Rust를 배우고 나서, 언어가 순수 함수형이 아니어도 모든 것이 표현식일 수 있다는 걸 깨달았음
      이 방식은 변경 범위를 제한하는 데 정말 유용함
    • Clojure는 언제나 Python보다 빠름, 그건 위안이 됨
    • 당신은 단순히 Clojure를 사용하는 사람이지, “Clojure 프로그래머”로 자신을 규정할 필요는 없음
      언어 간의 부족 전쟁에 휘말릴 필요 없음
      생산성 향상 시대에는 경계가 무의미함
      Don’t Call Yourself a Programmer 글을 추천함
  • 변수 재할당은 최소화하려 하지만, 종종 변수 섀도잉을 사용함
    result = result.process() 같은 패턴이 간결해서 선호함

    • 추상적인 예시라 그렇겠지만, 대부분의 경우엔 각 단계마다 명확한 이름을 붙일 수 있음
    • 이런 패턴은 보안 버그를 유발할 수 있음
      예를 들어 process()가 검증 함수라면, 어떤 시점에 처리된 값인지 불분명해질 수 있음
      따라서 이름으로 상태를 명확히 구분하는 게 좋음
    • 함수형 스타일에서는 이런 중간 변수 없이 함수 체이닝으로 해결 가능함
      예: result = x |> foo |> bar |> baz 또는 (-> x foo bar baz)
    • “result.process()라니, 도대체 어떤 result고 어떤 process인가?”
      나중에 코드를 읽는 사람은 혼란스러움
    • 이미 결과(result)인데 다시 처리(process)한다는 건 논리적으로 어색함
  • “변수(variable)”라는 용어 자체가 늘 마음에 걸림
    불변인데 왜 variable이라 부르는가?

    • 변수는 실행 중에는 변하지 않지만, 호출마다 값이 달라질 수 있음
      Rust에서는 mut로 명시해야만 변경 가능함
      반면 C에서는 상수를 만들려면 전처리기를 써야 해서 혼란스러움
    • 함수의 인자 x는 호출마다 다른 값을 받으므로, 그 자체로 변하는 값
      재할당이 없어도 변수라 부를 수 있음
    • 수학에서도 변수는 특정 객체가 아닌 임의의 값을 가리키는 기호임
      프로그래밍이 이 개념을 그대로 가져온 것임
    • 결국 변수는 실행마다 값이 달라질 수 있기 때문에 variable임
      상수(constant)는 모든 실행에서 동일한 값을 가짐
      Variable (mathematics) 참고
    • ‘변수’가 시간에 따라 변한다기보다 맥락에 따라 달라진다는 의미로 쓰이는 것임
  • IDE가 변수의 변경 여부를 시각적으로 표시해주면 좋겠음
    예를 들어, 변경된 변수는 살짝 표시만 해주는 식으로

    • IntelliJ에서는 재할당된 변수에 밑줄이 생기고, 마우스를 올리면 ‘Reassigned local variable’이라는 힌트를 보여줌
      가능한 한 final을 많이 쓰면 코드가 읽기 쉽고 유지보수하기 쉬움
    • 하지만 자동 추론보다는 명시적 opt-in이 낫다고 생각함
      IDE가 경고를 주고, 정말 필요한 경우에만 변경을 허용하는 게 좋음
      Rich Hickey의 set vs list 이야기처럼, 의미를 명확히 표현하는 구조를 선택해야 함
    • Swift는 컴파일러가 변수의 변경 여부를 감지해, 불필요한 변경이면 상수로 바꾸라고 제안함
    • JetBrains IDE에는 변수의 읽기/쓰기 위치를 찾는 기능이 이미 있음
    • 이런 기능을 하는 린터(linter) 를 만든다면 이름은 “mutalator”가 어울릴 듯함
  • 예전에 스레드 안전성을 위해 불변성을 엄격히 적용한 프로젝트를 했음
    덕분에 코드가 읽기 쉬워지고, 무엇이 바뀔 수 있는지 추적하기 쉬워졌음
    이후로 불변성의 열렬한 팬이 됨

    • 그렇다면 Rust를 꼭 써보라고 권하고 싶음
  • 대규모 Haskell 코드베이스에서 일하다가 다시 C로 돌아오니, 불변성이 기본이었으면 좋겠다는 생각이 듦
    const로는 부족함

    • C에서는 사실 직접적인 변경이 아니라 값 재할당만 가능함
      변경하려면 포인터를 써야 하고, C++은 함수 호출만으로도 인자를 바꿀 수 있어서 불투명함
    • Rust의 기본값이 충분히 안전한 불변성을 제공하는지 궁금함
  • “거의 모든 변수를 const로 선언하는 게 좋다”는 말에 공감함
    Rust가 언급될 만함

  • 불변이 기본이고, 특정 블록 안에서만 mutable을 허용하는 문법을 상상해봄
    예를 들어 Python의 with 블록처럼

    with mutable(x, items): x = 3 items.append(4)

    블록을 벗어나면 다시 불변으로 돌아가는 식임

    • 이건 사실상 mutable borrow 개념임
      Rust의 borrow checker를 보면 이 개념이 얼마나 복잡한지 알 수 있음
    • borrow 체크가 없다면, 블록 밖에서도 참조가 남아 변경될 수 있음
  • State is the enemy”라는 말이 있음
    상태가 늘어날수록 테스트해야 할 조건이 기하급수적으로 늘어남
    불변성은 이런 상태 폭발을 막는 방법임

Read Entire Article