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++은 함수 호출만으로도 인자를 바꿀 수 있어서 불투명함
Hacker News 의견
2년간 Clojure를 사용한 후, 불변성이 주는 명료함을 다른 개발자에게 설명하기가 정말 어렵다는 걸 느꼈음
상태 변화를 통해 효과를 일으키는 사고방식에 익숙한 사람들은 직접 경험해보기 전에는 이해하기 힘듦
예를 들어
x = 7; x = x + 3; x = x / 2처럼 작성하면 순서를 바꿔도 오류는 없지만 결과가 달라짐반면
x1,x2처럼 새 변수를 쓰면 잘못된 순서에서 오류가 발생해 문제를 명확히 드러냄결국 단일 할당(single assignment)은 이런 의존성을 명시적으로 표현하는 방식임
함수형 언어를 써본 적 없는 동료들에게 함수 중심의 사고가 얼마나 테스트하기 쉽고 깔끔한지 설명해도 잘 와닿지 않았음
Python은 함수형 스타일을 읽기 좋게 쓰기 어렵고, JS가 오히려 더 낫다고 느낌
결국 호기심 많은 개발자만이 Clojure 같은 언어를 시도하게 됨
함수는 외부 상태를 몰라도 되고, 외부도 함수 내부를 몰라도 됨
프로그램 전체 상태를 몰라도 특정 함수만 독립적으로 테스트하거나 디버깅할 수 있음
Haskell 커뮤니티가 결국은 타입 시스템 안에서 가변성을 재발명하려는 걸 보면 흥미로움
핵심은 부작용을 최소한의 비용으로 통제하는 것임
Haskell은 타입 시스템 때문에 진입 장벽이 높았고, F#은 너무 타협적이라 C# 문법으로 코딩하게 됨
Clojure의 homoiconicity와 강력한 데이터 구조 덕분에 ‘값으로 일한다’는 개념이 처음으로 명확히 와닿았음
직업적으로 쓰진 않겠지만, 함수형 언어나 Lisp 경험이 없는 사람에게는 꼭 추천하고 싶음
변수는 기본적으로 불변이고 모든 것이 표현식(expression) 이었으면 좋겠음
하지만 현실은 Clojure 개발자로서 Python의 침공에 시달리는 중임
이제는 TypeScript의 침공에 시달리고 있어서 공감됨
이 방식은 변경 범위를 제한하는 데 정말 유용함
언어 간의 부족 전쟁에 휘말릴 필요 없음
생산성 향상 시대에는 경계가 무의미함
Don’t Call Yourself a Programmer 글을 추천함
변수 재할당은 최소화하려 하지만, 종종 변수 섀도잉을 사용함
result = result.process()같은 패턴이 간결해서 선호함예를 들어
process()가 검증 함수라면, 어떤 시점에 처리된 값인지 불분명해질 수 있음따라서 이름으로 상태를 명확히 구분하는 게 좋음
예:
result = x |> foo |> bar |> baz또는(-> x foo bar baz)result.process()라니, 도대체 어떤 result고 어떤 process인가?”나중에 코드를 읽는 사람은 혼란스러움
“변수(variable)”라는 용어 자체가 늘 마음에 걸림
불변인데 왜 variable이라 부르는가?
Rust에서는
mut로 명시해야만 변경 가능함반면 C에서는 상수를 만들려면 전처리기를 써야 해서 혼란스러움
x는 호출마다 다른 값을 받으므로, 그 자체로 변하는 값임재할당이 없어도 변수라 부를 수 있음
프로그래밍이 이 개념을 그대로 가져온 것임
상수(constant)는 모든 실행에서 동일한 값을 가짐
Variable (mathematics) 참고
IDE가 변수의 변경 여부를 시각적으로 표시해주면 좋겠음
예를 들어, 변경된 변수는 살짝 표시만 해주는 식으로
가능한 한
final을 많이 쓰면 코드가 읽기 쉽고 유지보수하기 쉬움IDE가 경고를 주고, 정말 필요한 경우에만 변경을 허용하는 게 좋음
Rich Hickey의 set vs list 이야기처럼, 의미를 명확히 표현하는 구조를 선택해야 함
예전에 스레드 안전성을 위해 불변성을 엄격히 적용한 프로젝트를 했음
덕분에 코드가 읽기 쉬워지고, 무엇이 바뀔 수 있는지 추적하기 쉬워졌음
이후로 불변성의 열렬한 팬이 됨
대규모 Haskell 코드베이스에서 일하다가 다시 C로 돌아오니, 불변성이 기본이었으면 좋겠다는 생각이 듦
const로는 부족함변경하려면 포인터를 써야 하고, C++은 함수 호출만으로도 인자를 바꿀 수 있어서 불투명함
“거의 모든 변수를 const로 선언하는 게 좋다”는 말에 공감함
Rust가 언급될 만함
불변이 기본이고, 특정 블록 안에서만 mutable을 허용하는 문법을 상상해봄
예를 들어 Python의
with블록처럼블록을 벗어나면 다시 불변으로 돌아가는 식임
Rust의 borrow checker를 보면 이 개념이 얼마나 복잡한지 알 수 있음
“State is the enemy”라는 말이 있음
상태가 늘어날수록 테스트해야 할 조건이 기하급수적으로 늘어남
불변성은 이런 상태 폭발을 막는 방법임
Separation of Church and State