select는 필요 없었다

고전적 병행 툴킷에는 이 언어에 없는 유명한 primitive가 하나 있다: 여러 channel을 동시에 기다리는 것. pub/sub broker —— 생각할 수 있는 가장 멀티소스인 프로그램 —— 이 벽을 찾으러 갔다. 벽은 없었다: broker가 반응하는 모든 것은 variant 타입으로 하나의 inbox에 깔때기질할 수 있다. actor 패턴이다. 언어는 필요한 부품을 전부 갖고 있었다. dogfood가 대신 찾은 것은, 두 기능이 만나는 곳에 숨어 있던 진짜 컴파일러 버그: 재귀 루프가 부르는 helper의 중첩 함수가 helper의 지역을 가둘 때, 그 지역이 루프의 캡처로 새고 있었다.

mereconcurrencychannelscodegenlanguage-design

shutdown, timeout —— 「끝」의 툴킷은 3분의 2가 고전대로 갖춰졌다. 셋째 고전은 select다: 여러 channel을 동시에 기다려, 먼저 값을 낸 쪽을 취한다. Go는 구문으로 갖고, 대부분의 channel 라이브러리는 어떤 형태로든 기른다. 자연스러운 상정은 「이 언어에도 필요하다, 아직 forcing되지 않았을 뿐」이었다. 그래서 이 probe는 그것을 강제하러 갔다. 수중에서 가장 멀티소스인 프로그램 —— pub/sub broker —— 으로. 한쪽에 publishers, 반대쪽에 subscribers, 가운데의 broker는 구독 요청·발행 메시지·shutdown 신호에 반응한다 —— 세 종류의 입력, 겉보기에 세 개의 channel, 겉보기에 그 위의 select.

깔때기

벽은 오지 않았다. broker는 세 channel을 기다릴 필요가 없다. 세 입력이 세 channel이어야 한다고 강제하는 것이 아무것도 없기 때문이다: 그것들은 하나의 variant 타입의 세 생성자 —— Pub, Sub, Stop —— 로서 단일 inbox를 흐르고, broker는 딱 하나의 수신을 블록하고 생성자로 분기하면 된다. 이것이 actor 패턴이고, 언어는 필요한 부품을 전부 갖고 있음이 판명됐다: variant는 튜플 payload를 channel 너머로 나를 수 있고, 그리고 —— 정말 자명하지 않았던 부분 —— channel 자신이 메시지 안에 탈 수 있다. Sub 커맨드는 구독자 자신의 배송 channel을 inbox 경유로 나른다. channel은 일급 메시지 payload다. 일반적인 도피로도 말할 가치가 있다: 자신이 제어하는 소스들에 대한 select 흉내는 하나의 inbox로의 깔때기가 되고, 제어하지 않는 소스에는 소스마다 forwarder 스레드를 세워 하나로 merge한다. 편리함은 편리함인 채다. 이것은 이 Part의 둘째 정직한 negative result로, 「mono가 dictionary」의 계보에 있다: 빠져 있던 기능은 기존 것으로 포섭됨이 판명되고, 기능을 세우는 대신 발견을 기록한다.

dogfood가 실제로 잡은 것

그래도 dogfood는 빈손으로 돌아오지 않는다 —— 이것은 두 기능이 만나는 곳의 진짜 컴파일러 버그를 잡았다. broker의 fan-out helper는 자신의 중첩 재귀 함수를 포함하고, 그것이 helper의 지역을 가둔다. broker의 메인 루프는 그 helper를 부른다. C backend의 closure-lifting 패스에서는, fixpoint가 callee의 캡처 변수를 caller에 통과시킨다 —— 일반적으로는 필요하지만, 그것은 helper의 캡처를, 그 이름이 helper 자신 안에서 이미 바인딩되었는지 확인하지 않고 루프에 더하고 있었다. 루프의 emit된 시그니처는, 그 스코프 어디에도 존재하지 않는 두 파라미터 hn·hv를 길렀다: “use of undeclared identifier”, 원인에서 추상 두 층 떨어진 컴파일 에러다. 수정은 fixpoint의 skip 조건 하나 —— caller 자신의 body 안에서 바인딩되는 callee 캡처는 이미 스코프 안에 있으니 통과시켜서는 안 된다 —— 와, channel 불요의 20줄로 증류한 회귀 테스트. broker는 이제 두 topic과 두 subscriber를 양 native backend에서 올바른 합계까지 돌린다. 아크의 「끝」 툴킷은 이렇게 섰다: close, timeout, close에 의한 취소 —— 그리고 의도적으로 select 없음. 이 Part에 남은 한 이야기는 반대 방향의 공격을 한다: 고전 프로그램 네 개를 한꺼번에 언어에 던져, 아직 무언가 넘어지는지 본다.

← Back to Mere: 언어를 만들다