# 왼쪽에서 오른쪽으로 프로그래밍하기

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

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=22610](https://news.hada.io/topic?id=22610)
- GeekNews Markdown: [https://news.hada.io/topic/22610.md](https://news.hada.io/topic/22610.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-08-20T01:34:58+09:00
- Updated: 2025-08-20T01:34:58+09:00
- Original source: [graic.net](https://graic.net/p/left-to-right-programming)
- Points: 4
- Comments: 1

## Topic Body

- **왼쪽에서 오른쪽으로 프로그래밍하는 방식**은 코드를 입력하는 즉시 프로그램이 유효한 상태를 유지하며, 이로 인해 에디터의 자동완성 등 **도구 지원**이 극대화됨
- Python의 **리스트 내포**는 선언되지 않은 변수와 타입 추론의 부재로 자동완성 기능을 방해함
- Rust나 JavaScript는 **프로그램을 왼쪽에서 오른쪽으로 자연스럽게 구성할 수 있어** 변수 사용과 메서드 탐색이 더 직관적임
- C와 Python의 함수형 스타일은 **함수명이나 구조의 비발견성**으로 인해 효율적인 코딩 경험을 저해함
- **복잡도가 높은 로직**에서는 왼쪽에서 오른쪽으로 전개되는 코드가 더 읽기 쉽고, 유지보수 및 확장성이 우수함

---

### 왼쪽에서 오른쪽으로 프로그래밍하기

##### 코드는 입력하는 즉시 유효해야 함

---

### Python 리스트 내포의 한계

- Python의 리스트 내포 구문 `words_on_lines = [line.split() for line in text.splitlines()]`은 선언되지 않은 변수(`line`)에 접근해야 하므로, 에디터가 **자동완성**이나 타입 추론을 제대로 제공하지 못하는 문제 발생
- 코드를 부분적으로 입력하는 과정에서
  - `words_on_lines = [line.sp`처럼 입력하면 에디터는 `line`의 타입을 알지 못해 메서드를 추천할 수 없음
  - 변수명 오타(`lime` 등)와 같은 잠재적 오류 탐지도 어렵게 됨
- 올바른 추천을 받으려면 미완성 코드를 작성해야 하며, 그 과정이 **비직관적이고 불편함**을 초래함

### Rust에서의 왼쪽에서 오른쪽 구성

- Rust 예제(`let words_on_lines = text.lines().map(|line| line.split_whitespace());`)는
  - 익명 함수의 선언과 함께 변수(`line`)가 처음 등장하는 순간 선언으로 간주되어, 즉시 **자동완성 및 메서드 추천**이 가능해짐
  - 실제로 `split_whitespace`라는 메서드도 자동추천된 덕분에 쉽게 찾을 수 있었음
- 이 방식은 프로그램이 항상 **부분적으로라도 유효한 상태**를 유지하므로, IDE나 에디터가 실시간으로 코딩을 지원할 수 있음

### 점진적 공개(Progressive Disclosure)와 API 사용성

- **점진적 공개(Progressive Disclosure)** 는 사용자가 필요한 만큼만 복잡도를 경험하는 설계 원리로, 프로그래밍에도 적용 가능함
  - 예: 이미지를 추가할 때만 관련 옵션이 나타나는 워드프로세서의 UX와 유사함
- C 언어는 이런 지원이 부족함
  - `FILE *file`에 관련된 모든 함수가 `file.`로 탐색되지 않으므로, 함수명의 패턴(`fread`, `fclose` 등)을 외워야 하고 기능을 발견하기 어려움
  - 반면 이상적인 언어라면 `file.`을 통한 메서드 추천으로 관련 기능을 쉽게 **점진적으로 발견**할 수 있음

### 함수 및 메서드 발견성의 차이

- Python의 `map(len, text.split())`과 JavaScript의 `text.split(" ").map(word => word.length)` 예시 비교
  - Python에서 `len`, `length`, `size` 등 함수명이 예상되지 않아 여러 시도를 해야 실제 동작을 알 수 있음
  - JavaScript에서는 `word.` 뒤에 `.l` 만 입력해도 에디터가 `length` 등 메서드를 제시하여 **발견성이 높음**
  - `map` 같은 고차 함수도 실제 반환값과 데이터 타입이 즉각적으로 명확하게 드러남

### 복잡한 로직일수록 구조적 작성의 장점

- 복잡도가 높은 로직(`filter`, `lambda`가 중첩된 긴 Python 코드)의 경우
  - 코드의 시작과 끝을 반복적으로 확인해야 하며, 조건식이나 괄호 매칭 등에서 **가독성 저하**와 이해의 어려움이 발생함
- 동일한 로직의 JavaScript 버전에서는 코드를 위에서 아래로, 왼쪽에서 오른쪽으로 **순차적으로 읽고 이해**할 수 있음

### 핵심 원칙

#### 코드는 입력하는 순간마다 유효해야 함

- `text` 단독 입력에도 프로그램이 유효한 상태 유지
- `text.split(" ")`까지 작성해도, 이후 `.map(word => word.length)`까지 이어서 입력할 때도, 전체적으로 **항상 중간 상태가 유효함**
- 이러한 코딩 패턴은 에디터의 **실시간 지원** 가능성을 높이고, REPL 환경에서는 즉시 결과를 확인할 수도 있음

### 결론

- API와 언어 디자인은 코드를 왼쪽에서 오른쪽으로 자연스럽게 입력하며 중간 단계마다 **유효한 프로그램**을 만들 수 있도록 지원해야 함
- 좋은 API 설계가 이러한 코딩 경험 개선의 핵심임

## Comments



### Comment 42670

- Author: neo
- Created: 2025-08-20T01:34:59+09:00
- Points: 1

###### [Hacker News 의견](https://news.ycombinator.com/item?id=44942936) 
- SQL의 단점 중 하나는 쿼리문이 FROM이 아닌 SELECT로 시작한다는 점임, 그래서 어떤 엔티티(테이블)를 다루는지 바로 파악하기 어렵고, 스마트 에디터에서 쿼리 작성을 더 효율적으로 도와주는 데도 방해가 됨, FROM -> SELECT -> WHERE 순서로 가는 게 더 자연스러움, 특히 SELECT절에서 컬럼 이름을 정하고 WHERE에서 이를 참조하기 때문에 더 그렇다고 생각함, 사실 `SELECT * FROM table` 대신 `FROM table`만 써도 SELECT절은 생략 가능할 것이라 봄, 이런 불만 때문에 잔소리쟁이 노인처럼 들릴지 몰라도 그냥 내 개인적 그리움임
  - PSQL과 PRQL은 실제로 FROM이 먼저 오는 쿼리 순서를 사용함, BigQuery에도 파이프/화살표 문법이 최근 추가되었고, DuckDB 커뮤니티 익스텐션도 있으니 추천함 [DuckDB - PSQL](https://duckdb.org/community_extensions/extensions/psql.html), [DuckDB - PRQL](https://duckdb.org/community_extensions/extensions/prql.html)
  - SQL이 이렇게 쓰이는 이유는 관계형 대수 기초에서 투영(Projection)을 항상 먼저 적기 때문임, 그래서 표준에 따르면 WHERE에서 컬럼 별칭을 쓸 수 없음, 왜냐하면 selection(WHERE)이 projection(SELECT) 전에 일어나기 때문임, 참고로 MySQL 8에서는 `TABLE &lt;table&gt;` 이란 문법도 있으니 참고할 만함
  - 실제로 대부분의 SQL 엔진 내부 처리 순서는 FROM -> WHERE -> SELECT 순서임, 그래서 SELECT에서 정의한 컬럼 별칭이 GROUP BY, HAVING, ORDER BY에는 쓰이지만 WHERE에서는 쓸 수 없음
  - C#에서는 SQL로 컴파일되는 DSL(LINQ-to-SQL)도 FROM이 먼저 오는 구조임, 그리고 IDE에서 다른 절을 작성할 때 자동 완성 기능 덕분에 필드 제안을 바로 받을 수 있어서 이런 구조가 좋다고 생각함
  - Kusto라는 Azure의 데이터 분석 쿼리 언어도 파이프를 쓰는 유사한 형태임 [Kusto 쿼리 소개](https://learn.microsoft.com/en-us/kusto/query/?view=microsoft-fabric), .NET의 LINQ 스타일도 마찬가지임, 솔직히 SQL에도 FROM으로 시작하는 변형이 좀 더 적극적으로 도입되어야 하고, 그게 어려운 일도 아니라 생각함, 사용성 향상을 위한 시도가 부족하다고 봄
- 파이썬이 왜 이렇게 사랑받는지 이해하기 어렵다고 생각함, 두 사람 이상이 작업하면 언어가 한없이 고통스러워짐, 글쓴이가 지적한 부분은 빙산의 일각일 뿐임
  - 사람들이 Lisp류 언어에 몰리지 않는 이유와 비슷하다고 생각함, 수학적 엄밀함이 곧 가독성을 의미하지는 않음, 파이썬의 list/dict/set comprehensions는 타입이 정해지는 for 루프와 마찬가지임, 모두가 파이썬의 타입이 헐렁하다고 걱정하지만 리턴 타입을 명확하게 정해주는 유일한 구문(리스트 컴프리헨션)이 표적이 되는 건 이상함, Rust를 포함한 대부분의 다른 언어들에서도 "from iter as var" 순서는 아님, 또 각 언어들의 함수 호출 문법을 비교하는 것도 재미있음(파이썬에서도 functools.map이 있듯이)
  - 무언가를 이해하지 못한다고 해서 그게 미덕이 되는 것은 아니라고 봄, 파이썬이 사랑받는 이유엔 분명 뭔가가 있음, 물론 단점도 분명하지만, 그것만으로는 의미가 없음, 장단점을 전체적으로 비교해봐야 하고, 다른 언어와도 그렇게 비교해야 함
  - 나도 파이썬을 좋아함(단, 소규모 팀, 짧고 수명이 짧은 프로그램 기준), 정적 타입이 없어서 구현이 빠르지만 강한 타입 시스템 덕분에 완전히 망가지진 않음, 이 점 때문에 데이터 과학에서 인기가 많다고 봄, 탐색을 할 때 매우 편함, 반면 장기적으로 여러 팀이 유지보수 하거나 대규모 프로그램엔 분명 단점이 있음, 결국 만능 언어는 없고 적어도 "시도하고 빠르게 갈 수 있는 언어(Soft)"와 "오랜 기간 관리하기 좋은 언어(Hard)" 두 가지는 필요함
  - 나도 예전엔 위 의견에 완전히 동감했지만, 타입 주석과 타입 검사 덕분에 다른 사람들이 쓴 파이썬 코드와 협업이 훨씬 수월해졌음, 여전히 대규모 프로젝트까지는 아니라고 생각하지만, 타입이 붙으면서 파이썬은 내가 가장 좋아하는 스크립트 언어가 되었음
  - 나도 공유 코드베이스에선 리스트 컴프리헨션 같은 걸 피하고 아주 단순한 파이썬 스타일을 지향함, "한 가지 방식만 존재"해야 한다고 알려진 언어지만 실제로는 너무 많은 방식이 공존함, 리스트 컴프리헨션은 개인적으로 재밌고 만족스럽지만, 모두가 한 길로 가야 한다면 이 문법은 없어야 한다고 봄
- "프로그램은 타이핑하는 즉시 유효해야 한다"는 주장에 공감은 하지만, 현실에서는 코드를 항상 왼쪽에서 오른쪽, 한 줄씩 순차적으로 작성하지 않음, 중간에 다른 부분을 먼저 작성하거나 변수 선언을 나중에 하는 경우도 많음, 예를 들어 변수를 사용해놓고 한참 뒤에 선언할 때도 있음
  - 코드는 한 번 작성되고 수십, 수백 번 읽힌다는 점에서, 순차적으로 읽을 수 있는 코드가 점프를 요하는 코드보다 훨씬 읽기 편하다고 생각함
  - 사실 이 논의는 글의 핵심에서 살짝 벗어나긴 하지만, 흥미로운 시각임
  - 완전히 동감함, 새 파일 만들 때만 코드를 처음부터 순서대로 씀, 필드 추가할 땐 굳이 클래스 정의부터 가기보다 바로 그 필드를 쓰는 코드부터 만듦, 조건문 개선할 때도 잠시 동안은 유효하지 않은(에러 나는) 상태가 되는 경우가 많음
  - 이 의견에도 동의하지만, 이것과 연결된 중요한 원칙은 "너 아직 코딩 다 안 끝냈으니 컴파일 자체를 못 하게 막아버리는" 구조는 너무 과함, 에러는 비차단적이어야 하는데, 일부 언어는 미완료 코드를 아예 막아버림(예: 미사용 변수, 누락된 return 등)
  - IDE가 내가 실제로 코드를 작성하는 순서를 잘 모른다고 느낄 때가 종종 있어서 약간 불편함
- 일부 IDE에서는 코드 템플릿 기능으로 약어를 입력하면 코드 구조로 확장하고, 탭으로 각각의 플레이스홀더를 채워나가는 기능이 있음, 이때 탭 이동 순서를 반드시 왼쪽에서 오른쪽이어야 할 필요는 없어서 예를 들면 `{3} for {2} in {1}` 같은 순서도 가능함, 이런 도구들은 "읽기 좋은 문법"과 "타이핑하기 쉬운 문법" 사이의 타협점을 제공함, 나는 툴링을 활용해서라도 읽기 좋은 문법을 우선하는 쪽에 손을 들어줌, 꼭 "for-in" 구조만 고집할 이유는 없다고 생각함
- 요즘 Hacker News에서 합의된 분위기는 파이썬이 pipe 연산자를 빠뜨렸다는 것임, 나는 Mathematica에서 R로 넘어오면서 파이프의 가치를 빨리 깨달았음, 데이터 사이언스에서 단계별 데이터 변환 코드를 쓸 때 정말 직관적이고 읽기 쉬움, 파이썬이 여러 분야에서 쓰이지만 데이터 분석 외 다른 문맥에서도 파이프가 이점이 있을지 궁금함, 왜 파이썬이 파이프를 도입하지 않았는지 이해하려고 함
  - 파이프 연산자에서 한 걸음 더 나아가면, reverse assignment도 해볼 만하다고 생각함, 'let foo = ...'처럼 결과를 변수에 할당하는 대신 '... =: foo' 같은 형식도 써보고 싶음
  - R(특히 tidyverse R)의 파이프 연산자는 나에게 가장 중요한 "킬러 앱"임, 데이터 작업이 이렇게 쉽고 즐거운 언어 다시 없다고 생각함, 예를 들어 쿠키 레시피를 `bake(divide(add(knead(mix(flour, water, sugar, butter)),eggs),12),450,12)` 이런 식으로 중첩해가는 대신, `mix(flour, water, sugar, butter) %>% knead() %>% add(eggs) %>% divide(12) %>% bake(temp=450, minutes=12)` 로 파이프 쓰면 훨씬 쉽고 보기 좋음
  - 파이썬 pandas에서 pipe 문법을 쓰면 아래처럼 되고
    ```
    result = (df
     .pipe(fun1, arg1=1)
     .pipe(fun2, arg2=2)
    )
    ```
    R에서는
    ```
    result <- df |>
     fun1(., arg1=1) |>
     fun2(., arg2=2)
    ```
    둘 다 읽기 괜찮은데, R은 데이터프레임 밖에서도 파이프가 더 잘 동작하는 게 장점임
- 이 논쟁은 FP(함수형) vs OOP(객체지향) 언어 논쟁, vim과 emacs 논쟁처럼 거의 종교 전쟁에 가까움, vim은 연산자가 먼저, emacs는 선택순서가 먼저임, 영어처럼 "영어식으로 읽히는" 언어는 대개 동사가 앞에 오는 구조임(Lisp/Scheme이 그렇고), 반면 독일어, 타밀어처럼 동사가 맨 뒤에 오는 언어는 OOP 스타일(명사 먼저)과 어울림, 예를 들어 타밀어로는 "water drink" 순이고 영어는 "drink water"임, 그래서 vim을 더 편하게 느끼는 사람이 있을 수 있음, 각각의 스타일이 더 낫다기보다는 도구/사람 성향에 맞게 만들어지기도 하고, 요즘엔 언어 모델로 웬만한 건 다 가능하다고 봄
  - "영어처럼 읽히게 설계한다면 동사 먼저냐"에 대해, 명령형 언어면 맞겠지만, 선언형 언어에서 영어처럼 읽히게 하면 주어 먼저임
  - "독일어는 동사가 항상 뒤에 오냐"에 대해, 사실 간단한 문장은 동사가 두 번째에 옴("I drink water" → "Ich trinke Wasser"), 완전히 문장 끝에 오는 것도 아님
  - vim에서 연산자가 먼저라는 얘기에 대해, 사실 Kakoune은 그 반대로 동작하고, 이 방식이 훨씬 더 논리적이라고 생각함 [Kakoune에 대한 설명](https://kakoune.org/why-kakoune/why-kakoune.html)
- 한편, 파이썬의 "from some_library import child_module" 문법은 매우 직관적임, JS에서는 "import { asYetUnknownModule } from SomeLibrary"과 같은 구조라서 훨씬 덜 직관적으로 느껴짐
  - JS에서는 namespace import로 다음과 같이 쓰면
    ```
    import * as someLibrary from "some-library"
    someLibrary.someFunction()
    ```
    실제로 IDE 오토컴플리션이 잘 동작해서 좋다고 생각함 [MDN namespace import 설명](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#namespace_import)
  - "from" 키워드에 집착하는 이유를 의문스럽게 생각함, 그냥
    ```
    import SomeLibrary {
      asYetUnknownModule
    }
    ```
    이런 식으로 하면 된다고 봄
- ReScript는 바로 이 이유 때문에 API를 data-last에서 data-first로 바꿈, 훌륭한 타입 추론 덕분에 거의 항상 정확하고 타입에 맞는 자동 완성이 제공돼서 개발 경험이 매우 좋음, 물론 참조 없이 함수를 선언하면(타입을 알 수 없으니) 여전히 문제가 있지만, 타입을 추가하거나 먼저 호출하면 해결됨, 관련 블로그 글도 추천함 [Data-first와 data-last 비교](https://www.javierchavarri.com/data-first-and-data-last-a-comparison/)
- 이런 시각을 계속 주장해왔는데, 실제로 Ruby가 나에게 훨씬 쉽게 느껴진 이유와도 연결됨, 특히 나는 파이썬도 루비도 프로덕션 수준에서 깊게 써본 적 없지만, 파이썬이 왜 그리 널리 설치되고 쓰이기 시작했는지 이해가 잘 안 됨, 루비도 단점이 없진 않지만, 스크립트 작성에서 파이썬이 가진 복잡한 변화까지 직면하는 사람 별로 없는 듯, 적어도 루비는 지난 10년간 큰 버전 충돌 사건은 없었음
- 전체적으로 기사에서 지적한 부분에 전적으로 동의함, 맥락이 앞에 오고 왼쪽에서 오른쪽으로 읽히는 구조가 LLM이나 오토컴플리션에 더 잘 맞을 거라 생각함, 다만 예시 코드는 `len(list(filter(lambda line: all([abs(x) >= 1 and abs(x) <= 3 for x in line]) and (all([x > 0 for x in line]) or all([x < 0 for x in line])), diffs)))` 처럼 쓰는 것보다는, NumPy array를 활용하는 편이 메모리 상에 리스트를 새로 만들 필요 없고, 줄 전체를 한번에 다루기에도 훨씬 좋음, 예를 들어
    ```
    sum(1 for line in diffs
      if ((np.abs(line) >= 1) & (np.abs(line) <= 3)).all()
        and ((line > 0).all() or (line < 0).all()))
    ```
    이쪽이 훨씬 "왼쪽에서 오른쪽" 잘 반영됨
  - numpy 버전도 여전히 다소 암호 같긴 한데("line > 0"은 괜찮지만 브로드캐스팅 규칙이 복잡해질 수 있음), 저자가 든 자바스크립트 예시나 C#, Java, Scala처럼 타입 엄격한 언어의 컬렉션 API가 더 깔끔함, 내 취향은 코틀린인데 아래처럼 쓸 수 있어서 좋아함
    ```
    diffs.countIf { line -> 
      line.all { abs(it) in 1..3 } and ( 
        line.all { it > 0} or
        line.all { it < 0}
      )
    }
    ```
