Vimscript 배우기 cho.books
제 9 장 · 원서 §35–40

리스트 · 딕셔너리 · 함수형 · 토글

집합 자료형 두 가지 — 리스트와 딕셔너리. 반복문, 함수형 프로그래밍 스타일, 그리고 옵션·창을 토글하는 매핑과 경로 다루기.

List Dictionary for / while funcref toggling

Section 01리스트

지금까지 변수로 많은 일을 했지만, 집합(aggregate) 자료형은 아직 다루지 않았습니다. Vim에는 두 가지 주요 집합 타입이 있고, 첫 번째는 리스트입니다.

Vimscript 리스트는 순서가 있고, 이종(異種) 원소를 담는 컬렉션입니다. 대괄호로 만듭니다:

vimscript — 리스트 인덱싱과 슬라이싱
:echo ['foo', 3, 'bar']         " 이종 원소 OK
:echo [0, [1, 2]][1]            " [1, 2] — 0부터 시작
:echo [0, [1, 2]][-2]           " 0 — 끝에서부터 인덱싱
:echo ['a','b','c','d'][0:2]     " ['a','b','c'] — 슬라이싱

리스트는 Python처럼 음수 인덱싱·슬라이싱을 지원하고, 상한을 초과해도 안전합니다. +로 리스트를 이을 수 있습니다 — 문자열과 달리 리스트의 +는 정상 작동합니다. 내장 함수도 풍부합니다:

vimscript — 리스트 함수
:call add(foo, 'b')        " foo 끝에 'b' 추가 (제자리 변경)
:echo len(foo)             " 리스트 길이
:echo get(foo, 100, 'def')  " 범위 밖이면 기본값
:echo index(foo, 'b')      " 첫 인덱스, 없으면 -1
:echo join(foo, '---')     " 구분자로 이어 문자열로
:call reverse(foo)         " 제자리 뒤집기
직관 Vimscript 리스트는 언어의 다른 부분에 비해 놀랍도록 제정신입니다. add, reverse, sort 같은 함수는 리스트를 제자리에서 변경한다는 점만 기억하세요 — 원본을 건드리고 싶지 않으면 deepcopy()로 복사부터 하세요.

Section 02반복문

여기까지 반복문 없이 35개 단원을 지나왔다는 사실에 놀랐을지 모릅니다. Vimscript는 텍스트에 작업하는 다른 수단(normal! 등)을 워낙 많이 제공해서, 반복문이 다른 언어만큼 필요하지 않습니다. 그래도 언젠가는 필요합니다 — 두 종류가 있습니다:

vimscript — for 루프와 while 루프
:for i in [1, 2, 3, 4]
:  let c += i
:endfor

:while c <= 4
:  let c += 1
:endwhile

Vimscript의 for 루프는 리스트(또는 딕셔너리)를 순회합니다. C 스타일의 for (i = 0; i < n; i++) 형태는 없습니다 — 처음엔 아쉬워 보여도 실제로는 그립지 않습니다. while은 고전적이지만 자주 쓰지 않습니다.

Section 03딕셔너리

두 번째 집합 타입은 딕셔너리입니다 — Python의 dict, Ruby의 해시, JavaScript의 객체와 비슷합니다. 중괄호로 만듭니다. 값은 이종이지만 키는 항상 문자열로 강제 변환됩니다:

vimscript — 딕셔너리
:echo {'a': 1, 100: 'foo'}       " 키 100이 '100'으로 변환됨
:echo {'a': 1}['a']               " 1 — 인덱싱
:echo {'a': 1}.a                  " 1 — 점 표기도 가능
:let foo.b = 200                 " 항목 추가/할당
:call remove(foo, 'a')            " 항목 제거

딕셔너리에는 후행 쉼표를 늘 쓰세요 — 특히 여러 줄로 정의할 때, 새 항목 추가 시 오류가 줄어듭니다. 유용한 함수: get(기본값 포함 조회), has_key(키 존재 확인), items·keys·values.

함정 Vimscript 딕셔너리는 순서가 보장되지 않습니다. items()로 꺼낸 항목이 특정 순서일 거라고 기대하지 마세요.
모션 · 리스트 대 딕셔너리 01 / 4
스페이스: 재생 · ← →: 단계 이동

Section 04함수형 프로그래밍

Vimscript는 약간 투박하지만 함수형 스타일로 프로그래밍하는 데 필요한 조각들을 모두 갖추고 있습니다. 부작용을 피하는 핵심 도구는 deepcopy() — 리스트를 변경하기 전에 복사합니다:

vimscript — 불변 헬퍼 함수
function! Sorted(l)
  let new_list = deepcopy(a:l)
  call sort(new_list)
  return new_list
endfunction

함수를 변수에 담을 수도 있습니다 — function()으로 만듭니다. 함수를 가리키는 변수는 대문자로 시작해야 합니다:

vimscript — 함수를 변수로, 고차 함수
:let Myfunc = function("Append")
:echo Myfunc([1, 2], 3)        " [1, 2, 3]

function! Mapped(fn, l)
  let new_list = deepcopy(a:l)
  call map(new_list, string(a:fn) . '(v:val)')
  return new_list
endfunction
개념 funcref와 고차 함수

funcref는 "함수를 담은 변수"를 가리키는 Vim 용어입니다. 함수를 인자로 받아 무언가를 하는 함수가 고차 함수입니다.

Mapped, Filtered, Removed 같은 헬퍼를 만들면 리스트를 함수형으로 다룰 수 있습니다. 다만 Vim 리스트는 구조 공유를 하지 않아 복사 비용이 듭니다 — 거대한 리스트가 아니라면 대개 차이를 느끼지 못합니다.

Section 05토글과 경로

토글

불리언 옵션은 set option!으로 토글할 수 있고, 매핑으로 묶으면 편리합니다. 불리언이 아닌 것을 토글하려면 함수와 if가 필요합니다:

vimscript — foldcolumn 토글
function! FoldColumnToggle()
  if &foldcolumn
    setlocal foldcolumn=0
  else
    setlocal foldcolumn=4
  endif
endfunction

옵션이 아닌 것 — 예를 들어 quickfix 창의 열림/닫힘 — 을 토글하려면 전역 변수에 상태를 저장하는 방식을 씁니다. 작은 Vimscript 함수에서는 이런 "빠르고 더러운" 해법이 실용적입니다. 모든 엣지 케이스를 다루려다 일을 못 끝내느니, 대부분의 경우에 동작하는 것을 만들고 넘어가는 편이 낫습니다.

경로

Vimscript에는 경로를 다루는 내장 도구가 있습니다 — expand()(특수 문자열을 경로로), fnamemodify()(임의 파일명 변형), globpath()(디렉터리의 파일 목록):

vimscript — 경로 다루기
:echom expand('%:p')                  " 현재 파일의 절대 경로
:echo split(globpath('.', '*'), '\n')  " 디렉터리의 파일 리스트
:echo split(globpath('.', '**'), '\n') " 재귀적 파일 목록
직관 globpath()는 줄바꿈으로 구분된 문자열을 반환하므로, 리스트가 필요하면 split()으로 직접 나눠야 합니다. *는 한 단계, **는 재귀적 와일드카드입니다.

Section 06연습문제

  • :help List:help Dictionary를 전부 읽으세요(대문자 L, D에 주의).
  • :help add(), :help get(), :help index(), :help join(), :help has_key(), :help keys()를 읽으세요.
  • :help for, :help while를 읽으세요.
  • :help sort(), :help deepcopy(), :help map(), :help function()을 읽으세요.
  • Sorted(), Reversed(), Mapped(), Filtered() 같은 불변 헬퍼 함수를 직접 구현하세요.
  • :help foldcolumn, :help winnr(), :help globpath(), :help wildcards를 읽으세요.