- K 프로그래밍은 REPL에서 실험한 코드를 스크립트로 옮기며, 큰 명령형 패턴을 더 작고 선언적인 배열 패턴으로 계속 줄이는 데 초점을 둠
ngn/k스크립트는 REPL 입력처럼 줄 단위로 실행되고,\l file.k로 저장된 데이터와 함수를 REPL에 로드할 수 있음- Wikipedia식 3중 루프 행렬 곱셈을 그대로 옮기면 전역 변수, 중첩 루프, 변경이 많아져 K의 장점과 어긋남
- 개선 과정은
+/fold,'each,/:eachright,\:eachleft, 전치 제거, tacit 변환을 거쳐matmul: {x{+/x*y}\:y}에서matmul: (+/*)\:까지 응축됨 - 행렬 곱셈 예시는 K 실력이 코드 응축 과정을 반복하며 복잡한 절차를 더 읽기 쉬운 배열 표현으로 바꾸는 데 있음을 보여줌
REPL 중심의 K 개발 흐름
- 전체 소스 코드는 GitHub의
matmul.k에서 볼 수 있음 - K 프로그래밍은 대부분 REPL에서 이루어지며, 이전 코드 위에서 빠르게 실험하고 개선하기 좋음
ngn/k와rlfe조합은 위/아래 화살표 히스토리를 지원해 더 큰 K 프로그램을 개발하기에 충분함- 함수는 먼저 REPL에서 테스트한 뒤 실제 코드로 옮기는 흐름이 자연스러움
ngn/k의 prettyprinting은 항상 유효한 K 데이터를 반환하므로, 일부 값을 미리 계산해 프로그램 속도를 높일 수 있음
K 스크립트 실행 모델
- K 스크립트는 REPL에 입력한 것처럼 실행됨
- 각 줄이 순서대로 실행됨
- 줄이 세미콜론으로 끝나지 않으면 반환값이 출력됨
- 스크립트는 여러 줄 정의를 허용해 가독성을 높일 수 있음
- 저장된 데이터와 함수를 REPL에서 쓰려면
\l file.k를 실행함- 파일이 실행됨
- 파일의 데이터가 로드됨
- 같은 파일을 여러 번 로드하면 이전 데이터를 덮어씀
\로 접근하는 REPL 도움말에서 더 많은 명령을 확인할 수 있음
배열 언어에서 패턴을 줄이는 법
- K와 배열 프로그래밍은 패턴을 계속 단순화하는 과정임
- 크고 다루기 어려운 패턴도 더 작고 선언적이며 읽기 쉬운 형태로 줄일 방법이 하나 이상 있음
- 관련 논의는 Patterns and Anti-patterns in APL: Escaping the Beginner's Plateau - Aaron Hsu - Dyalog '17에서 자세히 볼 수 있음
- 흔한 출발점은 GeeksforGeeks나 Wikipedia의 잘 알려진 알고리듬을 K로 번역하려는 상황임
- 예제는 행렬 곱셈을 사용함
명령형 행렬 곱셈을 그대로 옮겼을 때
- Wikipedia의 Matrix multiplication algorithm은
i,j,k3중 루프와sum누산으로 행렬C를 채움 - 이를 K로 직접 번역하면
A,B,n,m,p,C,i,j,k,sum같은 전역 값을 많이 할당하게 됨 - 이 코드는 K를 명령형 언어처럼 쓰는 방식이라 K의 설계와 잘 맞지 않음
- 문제는 세 가지로 좁혀짐
- 전역 할당이 많음
- 여러 단계의 중첩 루프가 남음
- 변경이 자주 발생함
안쪽 루프부터 접어 줄이기
- 가장 안쪽 루프는
sum을 0으로 초기화하고k를 돌며A[i;k]*B[k;j]를 누산함 - 첫 개선은 fold인
/를 써서 합산을+/로 바꾸는 것임sum전역이 사라짐C[i;j]::+/...형태로 정리됨
- 이어서
'each가 배열을 반환한다는 점을 이용하면C를 변경하지 않고 중첩 루프의 반환값을 그대로 사용할 수 있음 - 이 단계 이후에는 변경 없는 세 개의 루프만 남고, 핵심 변수는
i,j,k가 됨
k, j, i를 없애는 과정
- 세 변수의 역할은 다음과 같음
i는A의 각 행을 인덱싱함j는B의 각 열을 인덱싱함k는A의 각 열과B의 각 행을 인덱싱함
k는A의 각 행과B의 각 열을 짝지어 곱하게 하므로, 중간 인덱스를 없애고 직접 매칭할 수 있음- 이 단계에서 루프 하나와
m이 필요 없어짐
- 이 단계에서 루프 하나와
j를 제거하려면B의 각 열을 가져와A[i]와 짝지어야 함B를 전치하고 eachright/:로 각 요소를 짝지음
i도 같은 방식으로 없앨 수 있음- eachleft
\:를 사용해A의 각 행과B의 각 열을 짝지음
- eachleft
- 이 과정을 거치면 전역 없이 다음 형태가 됨
matmul: {x{+/x*y}/:\:+y}
전치 제거와 최종 tacit 형태
+전치는 비용이 크므로 제거할 수 있음- 기존 방식은
x의 각 행과y의 각 열을 곱하는 순진한 방식임 - 대신
B의 각 행을A전체에 맞추면 같은 작업을 암묵적으로 수행할 수 있음
matmul: {x{+/x*y}\:y}
- 이 함수는 Chapter 3의 규칙을 적용해 tacit 형태로 바꿀 수 있음
- 최종 결과는 다음과 같음
matmul: (+/*)\:
연습으로 만드는 배열 언어 직관
matmul: (+/*)\:는 K다운 행렬 곱셈 함수로 정리됨- 응축 과정은 처음에는 단계가 많아 보일 수 있음
- K를 연습할수록 코드 응축이 더 쉽고 직관적인 작업으로 바뀜
- 행렬 곱셈은 K의 배열 지원과 잘 맞는 단순 절차임
- 이후 장에서는 K와 잘 맞지 않는 알고리듬과 그 처리 방법을 다룰 예정임