openlang

init의 setq가 fill-column을 버퍼 하나에서만 바꿨다

init에 (setq fill-column 72)를 오래전부터 넣어 뒀는데, 새 버퍼에서 M-q를 누르면 여전히 70에서 줄이 바뀐다는 걸 이제야 알았어요. (*scratch*에서는 72에서 바뀌어서 그동안 스스로를 속였어요.)

fill-column은 자동으로 버퍼 로컬이 되는 변수예요. 이런 변수를 setq로 설정하면 기본값은 안 건드리고, 그 줄이 실행될 때 현재 버퍼에 로컬 값을 만들어요. init 중에는 보통 *scratch*가 현재 버퍼라서, *scratch*만 72를 받고 나머지 버퍼는 손대지 않은 기본값으로 시작한 거예요.

(setq fill-column 72)          ; local to the current buffer only
(setq-default fill-column 72)  ; the value new buffers start with

C-h v fill-column을 보면 도움말에 "Automatically becomes buffer-local when set"이라고 대놓고 적혀 있어요. 그 문장을 열두 번은 읽었을 텐데 제 init과는 한 번도 연결을 못 했어요. 지금 파일 나머지에서 같은 실수를 찾는 중이에요.

이어지는 대화 8

서로의 근거를 따라
  1. 고유 링크

    고치기 전에 먼저 세 보세요. 이건 자기 값을 따로 가진 버퍼 수를 줘요.

    (seq-count (lambda (b) (local-variable-p 'fill-column b))
               (buffer-list))

    답이 1이면 init 줄 때문이에요. 그보다 많으면 다른 무언가도 로컬로 설정하고 있는 거고, 그 버퍼들은 setq-default로도 안 바뀌어요.

  2. 고유 링크

    저는 질문 두 개를 순서대로 던져서 정리해요. 먼저, 새 버퍼는 어떤 값을 읽나요? 기본값이에요. 그러니 init 파일이 설정해야 하는 것도 그 값이에요. 그다음, 이미 로컬 값을 가진 버퍼는 어디인가요? 그 버퍼들은 나중에 기본값이 뭐가 되든 자기 값을 유지해요. 파이썬의 클래스 속성과 인스턴스 속성에 가까워요. setq-default는 클래스 속성을 설정하고, 자동 버퍼 로컬 변수에 대한 setq는 인스턴스에 대입하는 셈이에요.

  3. 고유 링크

    그 비유는 fill-column에서는 맞고 변수 하나만 옆으로 가도 깨져요. 아무도 make-variable-buffer-local로 표시하지 않은 평범한 변수를 보세요. 현재 버퍼에 로컬 값이 없으면 setq는 전역 값을 설정하고 모든 버퍼가 그 값을 봐요. 파이썬에서는 인스턴스를 통한 대입이 언제나 인스턴스에 떨어지고요. 그러니 setq가 self.x = 1처럼 동작할지 Cls.x = 1처럼 동작할지가 소스 어딘가 다른 곳의 선언에 달려 있는 거예요. 바로 이런 조용한 기교 때문에 도움말 버퍼가 계속 크게 떠들어 줬으면 해요.

  4. 고유 링크

    수정이 미치는 범위를 한 가지 짚겠습니다. setq-default는 기본값을 설정하고 이미 있는 로컬 값은 건드리지 않습니다. 따라서 init 줄이 실행된 뒤에 만들어지는 버퍼는 바로잡지만, 이미 로컬 fill-column을 가진 버퍼, 여기서는 *scratch*는 앞선 setq나 모드가 준 값을 그대로 유지합니다. 실행 중인 세션에서 깨끗한 결과를 얻으려면 해당 버퍼에서 kill-local-variable로 남은 로컬 값을 지워야 합니다.

  5. 고유 링크

    저도 커밋 메시지 버퍼에서 똑같이 당했어요. 거기선 M-q가 70에서, *scratch*에선 72에서 줄을 바꿨고, 한참 커밋 모드 탓을 했는데 그 버퍼엔 로컬 값이 아예 없었어요.

  6. 고유 링크

    돌려 봤어요. 1이 아니라 3이었어요. init 줄 때문에 생긴 *scratch*, 그리고 제 모드 훅이 그냥 setq를 하고 있던 버퍼 두 개요. 그중 한 버퍼에서 C-h v를 보면 그 버퍼의 로컬 값과 전역 값이 나란히 나와요. 특정 버퍼가 의심될 때는 세는 것보다 이쪽이 빨라요. 훅은 이제 setq-local을 써서 의도가 그 자리에 적혀 있어요.

  7. 고유 링크

    맞는 말이에요. 도움말 버퍼가 떠들긴 하는데 직접 찾아가서 봐야만 떠들어요. 코드 안에서는 setq-local로 답해요(선언이 뭐라고 하든 먼저 변수를 로컬로 만든 다음 설정해요). 그러면 읽는 사람이 선언을 알 필요가 아예 없어요. init 파일에서는 버퍼별 변수는 setq-default로 옮기고, 나머지는 그냥 setq로 두고, 이상한 것들 옆에는 짧은 주석을 달았어요.

  8. 고유 링크

    시험하다가 바로 그거에 반대 방향으로 물렸어요. 효과를 보려고 *scratch*에서 (setq-default fill-column 80)을 실행했는데, init이 준 로컬 값 때문에 *scratch* 자신은 72에 그대로 있었어요. 거기서 M-x kill-local-variable을 하니까 바로 80이 됐어요. (재시작해도 됐겠지만 열어 둔 버퍼가 너무 많아서 그러기 싫었어요.)

같은 질문의 다른 갈래

  1. 멈춘 테스트는 print보다 덤프로 읽는 게 낫다

    예전에는 테스트가 멈추면 fmt.Println을 여기저기 넣고 다시 돌렸어요. 요즘은 다르게 해요. 타임아웃을 짧게 걸어 두면, 시간이 다 됐을 때 testing 패키지가 패닉을 내면서 모든 고루틴의 스택을 찍어 줘요. go…

    답글 9go
  2. SilentlyContinue는 철자만 나아진 On Error Resume Next

    물려받은 워크북에서 물려받은 PowerShell 스크립트로 넘어왔더니 옛 친구가 새 옷을 입고 있네요. cmdlet 호출 절반에는 -ErrorAction SilentlyContinue, 나머지에는 2>$null이 붙어 있어요…

  3. 업그레이드 가능하다는 건 스토리지 레이아웃이 영원하다는 뜻이다

    프록시 패턴이라는 말을 들으면 불변성 문제가 사라졌다고 생각하는 사람이 많습니다. 사라진 게 아니라 옮겨 갔습니다. 프록시가 상태를 들고 delegatecall로 호출을 넘기므로 구현 쪽 코드는 프록시의 스토리지 위에서 돕니…