TigerBeetle의 'Push Ifs Up, Fors Down' 원칙, 데이터베이스 최적화와 함수형 프로그래밍으로 확장되다

Push Ifs Up and Fors Down: The Idiom, Its Algebra, and Its Limits

TigerBeetle의 Tiger Style 문서에서 비롯된 'push ifs up and fors down' 원칙을 수학적으로 분석한다. 조건문은 호출자로 올리고 반복문은 배치 처리로 내리는 이 기법이 데이터베이스 쿼리 최적화(조기 프로젝션, 조인 지연, 벡터화 실행)와 함수형 프로그래밍의 filter/map 법칙, 카테고리 이론의 부분객체 및 자연변환과 어떻게 연결되는지 설명한다. 각 변환이 유효하기 위한 제약 조건도 함께 짚는다.

filter p . map f == map f . filter (p . f)
  1. socializer

    LLM이 사소한 아이디어를 불필요한 비유를 곁들인 길고 난해한 블로그 글로 바꿔버리는 능력에는 계속 감탄하게 된다.

  2. hatthew

    우리가 이걸 CS(알고리즘 최적화) 관점에서 말하는 건가, 아니면 SE(코드 설계) 관점에서 말하는 건가?

    SE 관점에서는 Collection<Optional<Walrus>>를 명시적으로 처리하는 flatmap 함수를 만들면 된다. 구현은 중요하지 않다. 만약 당신의 언어/프레임워크에 이미 호환되는 flatmap 함수가 있다면, flatmap(frobnicate)가 그것들을 버리는 데 필요한 값을 반환하는 단일 frobnicate(Optional<Walrus>) 함수를 만들면 된다.

    CS 관점에서 Collection<Optional<Walrus>>에서 Collection<Walrus>로 필터링하는 건 아마도 나쁜 생각일 것이다. 컬렉션이 작다면 아무것도 중요하지 않다. 컬렉션이 크다면 아마도 그것의 새 복사본을 만드는 데 시간을 쓰고 싶지 않을 것이다. 만약 필터가 하드 카피가 아니라 뷰를 반환한다면 최적화 이점이 없으므로 SE 관점에서 가장 말이 되는 대로 하면 된다. frobnicate가 저렴하다면 언제 frobnicate하든 분기 예측 실패 비용을 어차피 지불하게 되고, frobnicate가 더 비싸다면 아마 병렬화해서 각 스레드가 Optional을 언패킹하도록 해야 할 것이다. 어느 쪽이든 복사본을 만드는 데 시간을 쓰고 싶지는 않을 것이다.

    이것들은 모두 가상의 상황에 기반한 일반화이고 예외가 분명 많겠지만, 대체로 여기서 강한 논거는 보이지 않는다. 최적화가 중요하다면 자신의 상황에 대한 프로파일링을 기반으로 최적화하고, 최적화가 중요하지 않다면 feat에 기반해 함수를 설계하라 […]

  3. rtpg

    나는 항상 반대라고 믿어왔다: 조건문을 코드 깊숙이 넣어서 상위 수준 제어 흐름이 규칙적이게 하라는 것.

    하지만 버그를 피하는 코드를 만드는 나의 더 큰 철학은 데이터를 다룰 때 몇 가지 일이 있다는 것이다:

    - 분배

    - 결정

    그리고 분배와 결정이 같은 지점에서 섞이는 것을 피하고 싶다.

    "분배"는 for 루프일 수도 있지만, 어떤 키를 기준으로 데이터를 N개의 별개 버킷으로 나누는 것일 수도 있다.

    "결정"은 데이터를 더 자세히 살펴보며 어떤 결정을 내리는 곳이다(예: "이게 큰 고객인가 작은 고객인가").

    분배는 종종 의사 결정을 포함하지만, 모두 한 곳에 섞으면 결정 지점이 모호해질 수 있다. 나누면 일이 "명백히" 맞거나 "명백히" 틀리게 된다. 물론 성능 문제는 별개의 논의지만, 실제로는 대부분의 것이 문제될 규모가 아니다.

    by_category = defaultdict(list)

    for d in data:

    by_category[category(d)].append(d)

    for category, per_category_data in by_category.items():

    do_thing(category, per_category_data)

    나는 실수를 명백하게 만들거나, 적어도 어딘가에 실수를 집어넣기 어렵게 만드는 코드 패턴을 정말 중요하게 여긴다. 다만 이 모델로 설명하기 어려운 패턴도 있다.

    (컬렉션 작업에 일관된 어휘를 갖추라는 조언은 원칙으로서 좋아한다. 다만 최상위 수준의 조건문 사용은 금방 "... 왜 이 메서드는 n […]"로 빠져들게 되는 경향이 있다.)

  4. ivanjermakov

    시간을 아끼고 원문을 읽어라: https://matklad.github.io/2023/11/15/push-ifs-up-and-fors-do...

  5. gorgoiler

    어, 아니지 않나? 당신은 f(w: Walrus) -> Walrus를 작성하고, 호출자가 Walrus|None과 Iterable[Walrus]를 원하는 대로 처리하게 하면 된다!

    그리고 누군가 코드베이스에 Iterable[Walrus|None]에 대한 추상화(따라서 이를 처리할 특정 함수)가 필요하다고 결정하면, 날씨를 확인하고 잠깐 쉬면서 산책이라도 하라고 제안하라. (날씨를 확인하는 건 그들에게 우산을 빌려줘야 할지 보려는 것이다.)

    내가 놓치고 있는 게 뭐지?

이 날의 다른 글

2026-10-07