GN⁺: 배열 언어로 사고하기
(github.com/razetime)K 언어로 생각하기
- K 프로그래밍은 대부분 REPL을 통해 이루어짐.
- ngn/k의 rlwrap은 화살표 키로 이력을 탐색할 수 있어 큰 프로그램 개발에 유용함.
- 함수는 REPL에서 테스트 후 실제 코드로 옮겨짐.
- ngn/k의 예쁜 출력은 항상 유효한 K 데이터를 반환하며, 프로그램 속도 향상을 위해 미리 계산할 수 있음.
- K 스크립트는 REPL에 입력한 것처럼 실행되며, 각 줄의 반환값이 세미콜론으로 끝나지 않는 한 출력됨.
- 스크립트는 다중 라인 정의를 허용하여 가독성에 도움이 됨.
- 작업을 스크립트에 저장하고 REPL에서 사용하려면,
\lfile.k
를 사용하여 파일을 실행하고 데이터를 로드할 수 있음. - REPL에 파일을 여러 번 로드하여 이전 데이터를 덮어쓸 수 있음.
-
\
로 접근하는 REPL 도움말에는 다양한 유용한 명령어가 있음.
배열 프로그래밍의 단순화
- 배열 프로그래밍은 복잡한 패턴을 더 작고 선언적이며 읽기 쉬운 패턴으로 단순화하는 지속적인 과정임.
- 복잡한 패턴을 단순화하는 방법은 "APL에서의 패턴과 안티패턴: 초심자의 고원을 탈출하기 - Aaron Hsu - Dyalog '17"에서 자세히 논의됨.
행렬 곱셈의 K 변환
- 위키피디아 기사에서 가져온 행렬 곱셈의 반복 알고리즘을 K로 직접 변환할 수 있음.
- K로 변환한 최악의 코드 예시는 많은 전역 변수 할당, 중첩 루프, 많은 수정이 필요함.
- 코드를 단순화하여 이러한 문제들을 하나씩 해결할 수 있음.
내부 루프의 단순화
- 내부 루프에서
sum
을 fold(/
)를 사용하여 단순화할 수 있음. -
'
(each)는 배열을 반환하므로C
전역 변수를 제거할 수 있음. -
i
,j
,k
변수를 제거하여 루프를 단순화할 수 있음.
루프 제거 및 전역 변수 최소화
-
k
없이 직접 행과 열을 매칭하여 중간 루프를 제거할 수 있음. -
j
를 제거하기 위해B
의 각 열을A[i]
와 짝지을 수 있음. -
i
를 제거하기 위해 eachleft를 사용하여A
의 각 행을B
의 각 열과 짝지을 수 있음. - 전역 변수가 더 이상 필요 없음.
행렬 곱셈 함수의 최종 형태
-
+
(전치)는 비용이 많이 들므로 제거할 수 있음. -
x
의 각 행을y
의 각 열과 곱하는 대신,B
의 각 행을A
전체에 맞춰 암시적으로 동일한 작업을 수행할 수 있음. - 최종적으로 간결하고 명시적인 행렬 곱셈 함수를 얻을 수 있음.
- 코드를 단순화하는 과정은 처음에는 여러 단계를 거치지만, K에서의 숙련도가 높아질수록 더 쉽고 직관적으로 될 수 있음.
- 행렬 곱셈은 K의 배열 지원과 잘 맞는 간단한 절차임.
- K와 잘 맞지 않는 더 많은 알고리즘과 그 처리 방법을 미래 장에서 살펴볼 예정임.
GN⁺의 의견
- 이 글은 K 언어를 사용하여 행렬 곱셈과 같은 알고리즘을 어떻게 단순화하고 최적화할 수 있는지를 보여줌.
- REPL을 통한 즉각적인 피드백과 코드의 반복적 개선은 K 프로그래밍의 핵심적인 특징으로, 초급 소프트웨어 엔지니어에게도 유용한 학습 방법임.
- 코드 단순화 과정은 프로그래밍 능력을 향상시키는 데 중요하며, 이 글은 그 과정을 구체적인 예시를 통해 이해하기 쉽게 설명함.
Hacker News 의견
-
배열 언어의 유용성과 이해 가능성에 대해 여러 사람들이 의문을 제기하고 있음.
- 배열 언어는 모든 문제에 적합하지 않음.
- 많은 유형의 문제에 대해 놀라울 정도로 유능함.
- 배열 언어 사용자들은 대체로 매우 똑똑함.
- 배열 언어의 작동 방식을 배우는 것은 큰 도전임.
- 배열 언어에서 "절차적" 코드를 작성하는 것은 매우 나쁜 일임.
- 암시적 프로그래밍(tacit programming)의 이해는 정신을 확장시키는 멋진 경험임.
- 동사 연쇄(verb trains)를 내면화할 때의 경험.
- 배열 기반 언어가 모든 차원의 배열을 다루는 방식의 이해.
- "under"의 작동 방식 이해.
- 함수 지수(function exponents) 작동 방식 이해.
-
배열 언어의 놀라운 측면이 많으며, 위의 목록은 그중 일부에 불과함.
- Aaron Hsu가 병렬 APL 컴파일러를 개발하는 과정을 보며 배열 언어의 실제 잠재력을 확신하게 됨.
- APL 코드의 "의미 밀도(semantic density)"에 대한 논의.
-
배열 프로그래밍에 대해 들어본 적이 없고 소개를 원한다면 "The Array Cast" 추천.
-
70년대에 APL/APL2를 발견하고 반했지만, 함수를 구성하는 능력에 더 매력을 느낌.
- Haskell은 순수하고 타입화되어 있어 APL보다 더 재미있고 강력함.
- APL의 "사고의 도구로서의 표기법"은 과도한 간결함을 정당화하는 논리로 보임.
-
배열 언어 사용에서 가장 중요한 깨달음:
- 동사는 알고리즘임.
- 동사(또는 부사)의 연속은 사용해본 가장 직접적인 구성 방식임.
- 프로그램은 문장과 표현의 집합이 아니라 알고리즘의 구성임.
- 배열, 맵, 함수에 걸쳐 도메인과 범위를 일관되게 다루는 개념.
- 코드를 읽을 때 눈이 "뛰어다닐" 필요가 없는 왼쪽에서 오른쪽으로의 평가.
- 데이터에 코드를 보내는 것이 가능하며 선호됨.
- K 언어의 추가 이점: 뷰(즉, 의존성)를 직접 구현할 수 있고, 인터프리터를 통한 핫 코드 로딩이 가능함.
-
배열 언어에 대한 질문: "N보다 작은 숫자 중에서 조건 P가 참인 모든 숫자를 찾기"와 같은 작업을 어떻게 수행하는가?
- 배열 언어에서는 일반적으로 1부터 N까지의 배열을 생성하고, 배열에 대해 조건을 테스트한 후, 조건이 참인 요소만 얻기 위해 마스크를 적용함.
- N이 크고 조건이 자주 참이 아닐 경우, 불필요하게 많은 임시 배열을 생성하는 것은 메모리와 자원 낭비로 보임.
- 배열 언어의 구현은 이러한 문제를 최적화하거나, 게으른 평가(lazy evaluation)와 같은 기법을 사용하여 해결할 수 있음.
-
J 언어에 대한 경험: 배열 언어의 패러다임이 편향되어 있으며, 모든 문제를 배열의 중첩으로 생각하는 것이 도움이 되는지 확신할 수 없음.
- 문제를 단순화할 수 있는 데이터 구조를 자유롭게 생성하는 것이 알고리즘 부분을 크게 단순화할 수 있음.
- APL/J/K를 사용하기 위해서는 이러한 편향 때문에 더 똑똑해야 함.
-
K 언어 문제를 풀면서 얻은 인상: K 언어는 의도적으로 난해함.
- 퍼즐과 영리한 해결책에 적합한 언어지만, Python에서 numpy 배열로 작업하는 것이 배열 언어를 배우고 배열로 생각하는 방법을 가르쳐줌.
-
배열 언어 J의 예시:
dot =: +/ . *
를 사용하여 P와 Q의 점곱(dot product)을 계산함. -
K 언어의 문법이 더 짧지만, K 언어의 작동 방식에 대한 많은 내장된 맥락을 머릿속에 유지해야 함.