SQL의 단점 중 하나는 쿼리문이 FROM이 아닌 SELECT로 시작한다는 점임, 그래서 어떤 엔티티(테이블)를 다루는지 바로 파악하기 어렵고, 스마트 에디터에서 쿼리 작성을 더 효율적으로 도와주는 데도 방해가 됨, FROM -> SELECT -> WHERE 순서로 가는 게 더 자연스러움, 특히 SELECT절에서 컬럼 이름을 정하고 WHERE에서 이를 참조하기 때문에 더 그렇다고 생각함, 사실 SELECT * FROM table 대신 FROM table만 써도 SELECT절은 생략 가능할 것이라 봄, 이런 불만 때문에 잔소리쟁이 노인처럼 들릴지 몰라도 그냥 내 개인적 그리움임
PSQL과 PRQL은 실제로 FROM이 먼저 오는 쿼리 순서를 사용함, BigQuery에도 파이프/화살표 문법이 최근 추가되었고, DuckDB 커뮤니티 익스텐션도 있으니 추천함 DuckDB - PSQL, DuckDB - PRQL
SQL이 이렇게 쓰이는 이유는 관계형 대수 기초에서 투영(Projection)을 항상 먼저 적기 때문임, 그래서 표준에 따르면 WHERE에서 컬럼 별칭을 쓸 수 없음, 왜냐하면 selection(WHERE)이 projection(SELECT) 전에 일어나기 때문임, 참고로 MySQL 8에서는 TABLE <table> 이란 문법도 있으니 참고할 만함
실제로 대부분의 SQL 엔진 내부 처리 순서는 FROM -> WHERE -> SELECT 순서임, 그래서 SELECT에서 정의한 컬럼 별칭이 GROUP BY, HAVING, ORDER BY에는 쓰이지만 WHERE에서는 쓸 수 없음
C#에서는 SQL로 컴파일되는 DSL(LINQ-to-SQL)도 FROM이 먼저 오는 구조임, 그리고 IDE에서 다른 절을 작성할 때 자동 완성 기능 덕분에 필드 제안을 바로 받을 수 있어서 이런 구조가 좋다고 생각함
Kusto라는 Azure의 데이터 분석 쿼리 언어도 파이프를 쓰는 유사한 형태임 Kusto 쿼리 소개, .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에 대한 설명
한편, 파이썬의 "from some_library import child_module" 문법은 매우 직관적임, JS에서는 "import { asYetUnknownModule } from SomeLibrary"과 같은 구조라서 훨씬 덜 직관적으로 느껴짐
JS에서는 namespace import로 다음과 같이 쓰면
import * as someLibrary from "some-library"
someLibrary.someFunction()
ReScript는 바로 이 이유 때문에 API를 data-last에서 data-first로 바꿈, 훌륭한 타입 추론 덕분에 거의 항상 정확하고 타입에 맞는 자동 완성이 제공돼서 개발 경험이 매우 좋음, 물론 참조 없이 함수를 선언하면(타입을 알 수 없으니) 여전히 문제가 있지만, 타입을 추가하거나 먼저 호출하면 해결됨, 관련 블로그 글도 추천함 Data-first와 data-last 비교
이런 시각을 계속 주장해왔는데, 실제로 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}
)
}
Hacker News 의견
SELECT * FROM table대신FROM table만 써도 SELECT절은 생략 가능할 것이라 봄, 이런 불만 때문에 잔소리쟁이 노인처럼 들릴지 몰라도 그냥 내 개인적 그리움임TABLE <table>이란 문법도 있으니 참고할 만함{3} for {2} in {1}같은 순서도 가능함, 이런 도구들은 "읽기 좋은 문법"과 "타이핑하기 쉬운 문법" 사이의 타협점을 제공함, 나는 툴링을 활용해서라도 읽기 좋은 문법을 우선하는 쪽에 손을 들어줌, 꼭 "for-in" 구조만 고집할 이유는 없다고 생각함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)로 파이프 쓰면 훨씬 쉽고 보기 좋음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를 활용하는 편이 메모리 상에 리스트를 새로 만들 필요 없고, 줄 전체를 한번에 다루기에도 훨씬 좋음, 예를 들어 이쪽이 훨씬 "왼쪽에서 오른쪽" 잘 반영됨