문자열과 구조 처리 기능이 풍부한 고수준 명령형 언어 Icon의 핵심 특징과 예제를 소개합니다.
1993년 Preprints of the Second ACM SIGPLAN History of Programming Languages Conference (HOPL-II), SIGPLAN Notices 28, 3, 359-360에서 재수록.
Icon은 문자열 및 구조 처리 기능이 풍부한 매우 고수준의 명령형 언어이다. 다양한 컴퓨터에서 사용할 수 있으며 널리 사용되고 있다.
Icon에서는 변수가 아니라 값에 형이 부여된다. 내장 데이터 형에는 수치형, 문자 집합, 문자열, 집합, 리스트, 연관 테이블, 레코드, 프로시저가 포함된다. 집합형 자료형인 집합, 리스트, 테이블, 레코드는 어떤 형의 값이든 담을 수 있다. 테이블은 어떤 형의 값으로도 인덱싱할 수 있다. 수치형, 문자 집합, 문자열은 원자적 값이며, 이들에 대한 연산은 새로운 값을 만든다. 집합형 자료는 포인터 의미론을 사용하므로, 이에 대한 연산은 새로운 값을 만들 뿐 아니라 기존 값도 변경할 수 있다. 문자열과 집합형 자료는 크기에 제한이 없고, 실행 중에 크기가 바뀔 수 있다. 메모리 관리는 자동이다.
Icon은 식 중심의 구문을 가지며, 제어 구조조차 식이다. 프로시저는 개행이나 세미콜론으로 구분된 0개 이상의 식으로 이루어진다. Icon 프로그램은 하나 이상의 프로시저 정의로 구성되며, main이라는 이름의 프로시저를 호출하면서 실행이 시작된다.
식은 0개 이상의 값을 생성할 수 있다. 전통적인 언어에서와 같이 많은 Icon 식은 하나의 값을 생성한다. 예를 들어 x+5는 x와 5의 합을 생성한다. 다른 식들은 하나보다 많은 값을 생성할 수 있다. 예를 들어 x|5는 먼저 x를 생성하고, 필요하면 5를 생성한다. 이러한 식을 생성기 라고 하며, 여러 내장 생성기가 있고 프로시저도 생성기가 되도록 작성할 수 있다. 다른 내장 생성기의 예로는 x부터 y까지의 정수를 생성하는 x to y, 그리고 문자열 x의 모든 문자나 집합형 자료 x의 모든 원소를 생성하는 !x가 있다.
목표 지향적(goal-directed)' 식 평가 메커니즘은 Icon의 가장 두드러진 특징이다. 식 평가는 성공(success)'을 추구한다. 즉, 식에 대해 적어도 하나의 값이 나와야 한다. 식이 값을 생성하지 않으면 그 식은 실패(fails)'한다. 이 평가 메커니즘은 성공적인 결과를 얻기 위해 생성기들이 만들어내는 값들의 모든 조합을 시도한다. 예를 들어 y<(x|5)는 먼저 y를 x와 비교한다. y가 x보다 작으면 평가는 성공하고 값 x를 생성한다. y가 x보다 작지 않으면, y는 부분식 x|5가 다음으로 생성하는 값인 5와 비교된다. y`가 5보다 작으면 평가는 성공하고 5를 생성한다. 그렇지 않으면 평가는 실패하고 아무 값도 생성되지 않는다. 비교 연산자는 성공할 경우 오른쪽 피연산자의 값을 생성한다.
실패는 제어 식을 구동하며 이후의 평가를 억제한다. 예를 들어 max:=max<x는 max가 x보다 작을 때에만 max에 x를 대입한다. 마찬가지로,
if y < (x | 5) then write("y=", y)
는 y가 x 또는 5보다 작을 때에만 y의 값을 출력한다. 이 평가 메커니즘은 Icon 전반에 스며들어 있다. 예를 들어 프로시저는 모든 인자가 성공할 때에만 호출되므로, 마지막 예는 더 간결하게 다음과 같이 쓸 수 있다.
write("y=", (x | 5) > y)
목표 지향적 평가 메커니즘이 암시하는 역추적은 그것이 발생한 식으로 제한된다. 예를 들어,
max := max < x
write("y=", (x | 5) > y)
에서 두 번째 식의 실패는 첫 번째 식의 결과에 영향을 주지 않는다.
식 every _e_ 1 do _e_ 2는 e 1을 구동(drives)'한다. 즉, _e_ 1이 성공할 때마다 _e_ 2를 평가한다. 따라서 p`가 100개 원소를 가진 리스트라면,
every i := (1 to 10) | (91 to 100) do write(p[i])
는 p의 처음 열 개와 마지막 열 개 원소를 출력한다. do 절은 선택 사항이며, 이 평가 메커니즘은 흔히 i 같은 임시 변수를 없애는 데 도움을 준다. 예를 들면,
every write(p[(1 to 10) | (91 to 100)])
도 p의 처음 열 개와 마지막 열 개 원소를 출력하며,
every !p := 0
는 p의 각 원소를 0으로 설정한다.
Icon에는 문자열 분석을 돕는 수많은 내장 프로시저와 연산자가 있다. 이들 중 많은 것은 문자열과 위치를 피연산자로 받아 문자열이나 위치를 반환한다. 위치는 문자 사이의 장소를 나타낸다. 문자열은 위치를 명시적으로 조작하여 분석할 수도 있지만, Icon의 문자열 스캐닝 기능은 대부분의 명시적 위치 지정이 필요 없게 만든다. 식 _s_?_e_는 e 안의 문자열 처리 연산이 적용될 대상으로서 _s_를 설정한다. 식 _e_는 전형적으로 문자열 분석 연산을 포함하지만, 어떤 연산이든 포함할 수 있다. 문자열 분석 연산의 한 예는 find(_s_)인데, 이것은 대상 문자열 안에서 _s_가 부분 문자열로 나타나는 위치들을 생성한다. 예를 들어 line이
"a fish is a fish is a fish"
라면
every line ? write(find("fish"))
는 3, 13, 23을 출력한다.
또 다른 예는 move(_i_)인데, 이것은 위치를 _i_개의 문자만큼 전진시킨다. 전진이 성공하면 move는 처음 위치와 마지막 위치 사이의 _i_문자 부분 문자열을 반환한다. 예를 들어,
t := ""
line ? while t := t || move(1) || "."
는 t를 line의 문자들 뒤에 마침표들이 붙은 문자열로 설정한다. ||는 문자열 연결을 뜻한다. while은 t:=t||move(1)||"."를 실패할 때까지 반복해서 평가하는데, 이 실패는 대상 문자열의 끝에서 move(1)이 호출될 때 발생한다.
move는 위치를 바꾼 결과 일치된(matched)' 대상 문자열의 부분 문자열을 반환하므로 _매칭 함수_ 이다. 또 다른 매칭 함수는 tab(i)`인데, 이것은 대상 문자열의 위치 _i_로 이동하고 이전 위치와 새 위치 사이의 부분 문자열을 반환한다.
upto(_s_)는 s 안의 문자들 중 하나가 있는 위치를 반환하고, many(_s_)는 현재 위치에서 시작하여 s 안의 문자만으로 이루어진 가능한 가장 긴 부분 문자열 다음의 위치를 반환한다. many 같은 함수들이 위치를 반환한다는 점을 주목하는 것이 중요하지만, 그 위치의 구체적인 값 자체는 드물게만 중요하다. 위치는 가장 흔히 tab 같은 매칭 함수의 인자로 사용된다. 예를 들어, 다음 함수는 인자로 받은 문자열 안의 `단어들'을 생성한다.
procedure getword(str)
str ? while tab(upto(&letters)) do {
word := tab(many(&letters))
suspend word
}
end
&letters는 모든 대문자와 소문자를 포함한다. 식 tab(upto(&letters))는 다음 글자까지 위치를 전진시키고, tab(many(&letters))는 단어를 일치시켜 word에 대입한다. suspend는 return과 비슷하지만, 필요할 경우 대체 값을 위해 호출을 재개 할 수 있도록 그 호출을 중단한다. 호출이 재개되면 suspend가 중단한 지점에서 계속하므로, 이후의 재개는 남아 있는 단어들을 반환한다. while은 str에 더 이상 단어가 없어 tab(upto(&letters))가 실패하면 종료된다.
getword의 사용 예로는 common words가 있는데, 이는 입력에서 가장 흔히 사용된 단어들과 그 출현 빈도를 나열하는 프로그램이다. 줄 번호는 설명을 위한 것이며 프로그램의 일부는 아니다.
1 procedure main(args)
2 k := integer(args[1]) | 10
3 words := table(0)
4 while line := read() do every words[getword(line)] +:= 1
5 words := sort(words, 4)
6 every 1 to k do write(pull(words), "\n", pull(words))
7 end
이 프로그램은 가장 자주 사용된 k개의 단어를 출력한다. 2행은 명령행 인자가 존재하고 그것이 올바른 정수이면 k를 그 값으로 설정하고, 그렇지 않으면 10으로 설정한다. 3행은 연관 테이블을 words에 대입한다. table에 주는 인자는 각 항목의 초기값을 지정한다. 이 테이블은 빈도 수를 누적한다. 4행은 줄들을 읽고 getword를 사용해 각 줄의 단어들을 생성하는데, 이 단어들은 words를 인덱싱한다. 연산자 +:=는 왼쪽 피연산자를 증가시킨다. 5행의 sort(words,4)는 빈도 수 기준 오름차순으로 정렬된 테이블 words의 내용을 리스트로 반환한다. 이 리스트에서는 인덱스와 그 개수 값이 번갈아 나타나며, 그 리스트가 words에 대입된다. 6행은 리스트의 끝에서 값을 pull하여 꺼내면서 가장 자주 사용된 k개의 단어와 그 개수를 출력하는데, 이 과정에서 리스트 길이도 줄어든다. 단어 수가 k보다 적을 경우를 대비해, 리스트가 비면 pull은 실패한다.
이 예는 Icon 프로그램의 전형을 보여준다. 대부분은 전통적인 언어의 동등한 프로그램보다 훨씬 짧다. 예를 들어, 이 13행짜리 Icon 프로그램을 C 프로그래밍 언어로 작성된 동등한 프로그램과 비교해 보라(Comm. ACM 30, 594-599, 1987).