Io Language/Tutorial
소개 ¶
설치 ¶
> wget http://io.urbanape.com/release/Io-2007-02-19.tar.gz Io>와 같은 프롬프트가 뜬다면 인터프리터를 실행시키는데 성공한 것입니다.
HelloWorld! ¶
aObject otherMethod(arg1, arg2, arg)
42이나 3.14 같은 다른 언어에서 흔히 봐온 정수, 실수 표현을 똑같이 이용하여 숫자를 표현합니다. 또한 정수이건 실수이건 간에 둘 다 같은 Number 객체를 부모로 합니다. 이 Number 객체로부터 나온 객체이기 때문에 42은 Number객체가 제공하는 메서드를 이용할 수 있습니다. 예를 들어서 다음처럼 Number객체의 메서드들을 이용할 수 있습니다.
Io> 42 factorial ... Io> 255 asHex ... Io> 2 pow(3) ... Io> 2 ** 3 ... Io> 2 **(3) factorial와 asHex은 단순합니다. 아무런 인자없이 그 숫자을 인자로해서 결과를 만들어줍니다. 하지만 pow(n)은 인자를 필요로 합니다. 이 경우에는 위에서 보는 것처럼 괄호로 그 인자를 묶어줍니다. 여기에 한가지 예외가 있는데 일반적으로 산술연산자로 쓰이는 연산자의 경우에는 이렇게 괄호를 치지 않아도 이 메서드를 실행하는 메시지를 보내는 것으로 이해합니다. 2 ** 3은 2 **(3)의 SyntaticSugar입니다. (이렇게해서 다른 언어가 제공하는 것처럼 산술연산자를 이용할 수 있으면서 연산자를 재정의하거나 메서드만으로 이를 구현합니다.) 또한 이를 메시지전달로 구현한 다른 언어인 SmalltalkLanguage와는 다르게 연산자의 우선순위를 갖습니다.
Io> 4+5*2 ==> 14연산자의 평가순서를 바꾸고자 한다면 괄호를 이용해서 감싸주면 괄호부터 평가합니다. Io> (4+5)*2 ==> 18메시지전달과 PrototypeBasedLanguage의 특징에 대해서 더 설명할 것이 남아있지만 이 문서의 나중으로 미루고 우선은 우리의 짧은 코드로 돌아가서 설명을 계속하겠습니다. 아까 설명하려던 코드조각은 분명히 1에 대해서 그 메서드 to(n)을 실행하는데 인자로서 5을 넘겨주는 코드입니다. 다음처럼 1 to(5)을 인터프리터에 입력하면 그 결과를 보여줍니다.
Io> 1 to(5) ==> Range_0x8699b90:이는 표현식의 결과가 Range객체의 자식임을 말해줍니다. Range객체는 숫자의 범위를 나타내는데 사용합니다. 실제로 리스트나 배열과 같은 자료형처럼 그 처음과 끝까지의 모든 요소를 갖는 자료형이 아니라 처음값과 끝값, 그리고 처음값이 끝값이 되기 위한 단계값만을 갖는 자료구조입니다. 간단하게 말해서 Range객체를 이용해서 1에서 5까지의 각 단계값들을 나열할 수 있습니다.
48879 asHex위의 코드는 48879에 해당하는 16진수를 문자열로 되돌려줍니다. 하지만 약간 변덕을 부려서 이 16진수를 대문자로 표현하고자 한다면 String객체의 asUppercase 메서드를 사용해서 이를 변환할 수 있습니다. 48879 asHex asUppercase이는 결과값에 대해서 연동하여 새로운 결과를 만드는 방법으로 IoLanguage에서 흔하게 사용됩니다. (다른 언어에 익숙한 분들은 하나의 평가식을 한 변수에 넣고, 다시 그 변수를 변환는 일이 흔합니다.) 1 to(5) foreach(n, ...) : 이 부분 또한 IoLanguage의 특징을 잘 보여주는 표현식입니다. IoLanguage에는 키워드나 예약어가 없고 그러므로 고정된 제어구조 또한 없습니다. 대신에 이렇게 객체 대한 메서드로서 코드블럭을 넘겨줘서 루프나 제어구조를 구현합니다. 여기에서는 Range 객체의 foreach메서드를 이용합니다. 이 메서드는 2개의 인자를 필요로 하는데 첫번째 인자는 각 단계값을 바인딩해줄 변수의 이름(여기서는 n)과 두번째는 루프의 각 단계별로 실행할 코드블럭입니다. 즉, 다시 말해서 1에서부터 5까지 1씩 증가하면서 1, 2, 3, 4, 5에 대해서 차례대로 n을 설정하고 두번째 인자를 실행하는 것입니다.
이를 손으로 풀어보면 다음과 같습니다.
n := 1; (n .. ": Hello, World!") n := 2; (n .. ": Hello, World!") ...귀찮아서그만생략... n := 5; (n .. ": Hello, World!") ;은 여러표현식을 한줄에 적을 수 있도록 표현식을 서로 나눠주는 역활을 합니다.
사용상의 팁 ¶
문법 ¶보통 다른 언어에서 문법에 대한 부분은 항상 이런 문서의 뒷쪽에 부록으로 위치하지만 반대로 그 페이지수는 엄청나게 많습니다. 하지만 IoLanguage에서는 그런 커다랗고 복잡한 문법이 없습니다. 대신에 이 부분이 가장 앞에 위치하는 이유는 몇가지 단순하고 완전한 문법규칙을 이해한다면 이후에 나오는 모든 부분들이 이 단순한 문법에 기초한 것이라는 점이 명확하기 때문입니다.
메시지 전달 ¶사실 문법을 나열하려고 해도 힘든게 문법이라고 부를만한게 없습니다. IoLanguage에는 오직 메시지전달만이 있습니다. 나머지는 상수를 표현하고, 주석을 표시하는 정도입니다. 메시지 전달을 간략하게 표현해보면 다음과 같습니다.
Io> Dog bark
Io> Person say("hello")
Io> list(1, 2, 3)
또한 이렇게 메시지에 넘겨주는 모든 인자들은 언제나 수신자에게 넘겨져서 필요한 때에만 평가가 이루어집니다. LispLanguage에서 말하는 SpecialForm이나 그런것이 필요없이 단순하게 메서드를 통해서 제어구문을 표현할 수 있음을 의미하며, 다른 언어에서 말하는 LazyEvaluation과 비슷합니다. 만약 인자를 이렇게 평가하지 않고, EagerEvaluation으로서 JavaLanguage나 PythonLanguage처럼 인자를 평가한다면 다음의 예제는 작동하지 않을 것입니다.
Io> 1 to(10) select(n, (n % 2)==0)
객체 메시지 메서드
이 코드는 1에서 10까지의 모든 짝수를 구합니다. 하지만 다른 언어의 평가규칙을 적용한다면 select의 인자인 n과 "(n%2)==0"은 각각 평가하려고 해서 절대 작동하지 않을 것입니다.
주석 ¶프로그램을 작성하면서 중간중간 주석을 달고 싶다면 다음 세가지 스타일 모두가 허용됩니다.
/* C-style
multiline
*/
// C++ style
# Shell-style
문자열 ¶다음과 같이 2가지 스타일의 문자열 표현을 지원합니다.
Io> "foobar" Io> """multiple lines""" """으로 감싼 문자열은 여러줄에 걸쳐서 적거나 그안에 다른 문자열을 표현할 수 있습니다. 또한 C등에서 지원하는 백슬래쉬를 이용한 EscapeCharacter의 표현도 가능합니다.
숫자 ¶정수, 실수, exponent표기법, 16진수표기등을 지원합니다.
123 123.456 0.456 .456 123e-4 123e4 123.456e-7 123.456e2 0x0 0x0F 0XeE 슬롯? 메서드? 메시지? ¶조금 뜬금 없지만, 간단하게 몇가지 개념을 소개해 드리고 싶습니다. 알고 있는 것이더라도 혹시나 제가 말하고자 하는 것과 다를 수 있으므로 읽어보며 확인해주시기 바랍니다.
연산자 ¶IoLanguage에서 연산자라는 말은 사실 숫자와 영문자로 이루어지지 않은 이름을 갖는 메서드를 말합니다. 이러한 메서드들은 인자가 있더라도 괄호를 생략하고서 호출할 수 있습니다. 또한 or, and, return 등의 미리 지정된 영어단어들 또한 연산자로서 취급합니다. 이들은 모두 예외적인 규칙이지만 사용상의 편의를 위해서 제공하는것들입니다.
Io> 3 + 4 ==> 7 Io> 3 type ==> Number Io> Number slotNames ==> list(..., "+", ...) 대입연산자 ¶다른 언어에서는 변수를 위한 이름공간이 있습니다. IoLanguage에서는 모든 것이 객체로 표현되므로 이름공간 또한 어떤 객체로 표현될 필요가 있습니다. 그래서 전역변수와 같은 것을 위한 이름공간을 위해서 Lobby객체를 제공합니다. 어쨌든 이러한 현재라는 시점에서 바인딩된 변수들을 위한 공간이 필요합니다. (이후에 설명할 평가문맥(EvaluationContext)에서 이러한 것에 대해서 논의하겠습니다.) 이를 위해서 새로운 방법을 추가하지 않고 단순히 Lobby 객체에 슬롯을 추가하고 그 슬롯을 통해서 변수를 구현합니다.
다음의 코드는 현재 평가문맥에 a라는 이름의 슬롯을 만들고 1에 해당하는 객체의 참조를 대입합니다.
Io> a := 1 Io> a ==> 1단순히 이름-값의 관계이기 때문에 DynamicTyping이 가능합니다. Io> a := 1 Io> a := "foobar"IoLanguage에서 대입연산자는 :=와 =가 있습니다. 전자의 경우는 이미 슬롯이 있으면 기존 슬롯을 대체하고, 슬롯이 없으면 슬롯을 생성합니다. 하지만 후자는 슬롯이 없으면 예외를 던지고 슬롯이 있을때만 슬롯을 대체합니다.
true, false, and nil ¶진리값을 나타내는 두가지 값이 있습니다. 하나는 참을 나타내는 true와 거짓값을 나타내는 false입니다. 이들은 그 자체로서 진리값을 나타냅니다. 또한 논리연산을 수행하는 메서드의 결과는 항상 이 둘 중 하나입니다.
Io> (3 < 4) type ==> true이들은 그 자체로서는 별 기능이 없지만 이들 자체로서도 객체이기 때문에 BooleanArithmetic이나 SmalltalkLanguage 스타일의 제어구조를 만들고 싶을 때 유용합니다.
(balance >= amount) ifTrue("ok" println) ifFalse("not enough mineral!" println)
또한 다른 언어에서 null의 역활을 하는 nil이 있습니다. 어떠한 변수(슬롯)가 아무것도 참조하지 않고자 할 때 이를 사용합니다.
Io> a := nil
Io> a ifNil("void..." println)
객체 ¶IoLanguage의 특징적인 개념들은 다른 언어에서 찾을 수 있는 복잡하고 어려웠던 개념들을 잘 통합하고 있습니다.
프로토타입 ¶IoLanguage에서는 코드블럭의 지역변수를 위한 저장공간, 이름공간까지 포함하여 모든 것이 객체로 표현합니다. 그리고 대입을 포함한 모든 행위까지 메시지로 표현합니다. 객체는 슬롯이라고 불리는 '키/값'쌍의 목록과 내부적으로 객체가 상속 받은 객체의 목록을 저장하는
protos라 불리는 목록으로 이루어져 있습니다. 슬롯의 키는 심볼(symbol, 유일하며 불변적인 시퀀스)이며 값은 모든 형태의 객체일 수 있습니다.
ageldama : 기존 ClassBased 객체지향언어들에 익숙한 사람들에게는 이상해 보이거나 비효율적이라고 생각할수도 있습니다. 처음으로 "객체를 만들기 위해서는 클래스를 우선 만들어야 한다"라는 기존상식에서 많이 벗어나고, "어째서 XX처럼 그냥 클래스를 만들어서 인스턴스를 만들면 안되는가?"라는 물음이 뒤따를것입니다. 기존의 객체를 만드는데 있어서 클래스와 객체와의 갭을 줄이고, 클래스를 만들듯이 원형 객체를 만들고 그것을 복제해서 사용하면 됩니다. 그러한 언어들이 제공하는 상속의 개념보다 훨씬 유연한 방법으로 상속과 같은 효과를 제공하는 것이 protos 목록입니다. 이는 이후에 설명할 메시지전달과 함께 조합하여 간단하면서도 강력한 객체모델을 제공합니다.
clone와 init ¶새로운 객체는 기존 객체를 복제(cloning)해서 만들어냅니다. clone은 새로운 객체의
protos 목록에 부모 객체를 포함한 새로운 빈 객체를 만들어냅니다. 새로운 인스턴스(instance)의 init 슬롯은 새로운 객체를 초기화하기 위해서 활성화될 것입니다. NewtonScript에서처럼 IoLanguage에서 슬롯은 대입과 함께 만들어집니다.(CreateOnWrite)
me := Person clone인스턴스에 변수나 메서드를 추가하려면 간단히 그냥 대입하면 됩니다.:
myDog name := "rover"
myDog sit := method("I'm sitting" println)
객체를 복제하면 새로운 객체의 init 슬롯이 객체를 초기화하기 위해서 활성화됩니다.
상속 ¶객체가 적절한 슬롯을 찾는 메시지를 받았을때, 수신객체 자신에게는 일치하는 것이 없을 경우에는
protos목록의 객체들에 대해서 깊이우선검색을 재귀적으로 수행합니다. 다시 말해서 객체가 어떠한 메서드나 슬롯을 갖고 있지 않더라도 그 객체의 원본객체나 protos 목록에 추가한 객체가 그러한 것을 갖고 있다면 마치 메시지를 수신한 객체가 이를 상속 받는 것처럼 처리합니다. 또는 객체 자신에게 일치하는 슬롯이 있다면, 또한 그 슬롯의 객체가 Block이나 CFunction타입에 속하는 활성(activatable, 실행)가능한 객체라면 이를 활성화합니다. 혹은 다른 타입이라면 단순히 그 객체를 되돌립니다.
IoLanguage에는 전역변수가 없으며, 이름공간에서 최상위의 객체를 Lobby라고 부릅니다.(이를 전역이름공간으로 이용합니다.)
클래스가 없기 때문에 서브클래스(subclass)와 인스턴스간의 어떠한 차이도 없습니다. 다음은 새로운 서브클래스를 만드는 코드입니다.:
Io> Dog := Object clone ...위의 코드는 Lobby의 "Dog"라는 슬롯을 Object객체의 복제로 설정합니다. 이렇게만 한다면 Dog객체의 protos 목록에는 오직 Object객체만이 포함되어 있습니다. Dog은 이제 순수하게 Object의 서브클래스입니다. 인스턴스변수와 메서드들은 protos목록의 객체들로부터 상속을 받습니다. 슬롯을 설정하면 protos에 속한 객체를 수정하는 것이 아니라 대상 객체만을 수정합니다.
Io> Dog color := "red" Io> Dog ==> Object_0x4a7c0: color := "red" ... 다중상속 ¶protos 목록에 얼마든지 다른 객체를 추가할 수 있습니다. 이를 통해서 다른 객체들을 부모처럼 사용할 수 있습니다. 이렇게 하더라도 메시지에 응답하기 위해서 슬롯을 검색할 때 깊이우선검색을 ProtoChain에 대해서 수행합니다.
메서드 ¶메서드(method)은 호출시에 각기 다른 지역이름공간을 위한 객체를 만들고 메시지의 수신객체를 참조하는 self 슬롯을 이에 할당하는 익명함수(AnonymousFunction)입니다.
Object의 method()메서드를 사용해 새로운 메서드를 만들 수 있습니다.:
method((2+2) print)객체와 연관된 메서드를 만들려면 단순히 메서드 객체를 객체의 슬롯에 배정하면 됩니다.:
Dog := Object clone
Dog bark := method("woof!" print)
위의 코드는 새로운 서브클래스를 만들고 새로운 클래스의 "bark" 슬롯에 "woof!"을 출력하는 메서드를 대입한 것입니다. 이를 사용하려면 다음처럼 하면 됩니다.:
Io> Dog bark ... Io> poochi = Dog clone Io> poochi bark ...메서드의 기본 되돌림값은 맨 마지막으로 평가한 표현식의 결과값입니다. 인자 ¶메서드가 인자를 넘겨받도록 정의하려면 다음처럼 합니다.:
add := method(a, b, a + b)일반적인 형식은 다음과 같습니다.: method(<arg name 0>, <arg name 1>, ..., <do message>)맨 마지막의 인자는 선택적으로 메서드에 넘겨줄 코드블럭 객체를 위한 인자일 수 있습니다. 블럭 ¶블럭(blocks)은 메서드와 유사하지만, LexicallyScoped이며 그 해당슬롯에 접근할 때 자동적으로 활성화되지 않는다는 점에서 다릅니다.(활성화하기 위해서는
call메서드를 사용합니다.)
LexicallyScoped란 것은 블럭이 실행될 때 슬롯에 대한 검색이 블록이 생성된 문맥에서 계속된다는 것이지 블럭이 실행되는 문맥에서 계속된다는 것이 아니라는 뜻입니다. 블럭은 Object의 block() 메서드를 사용해서 만듭니다.:
b := block(a, a*2) b call(1)다시 LexicallyScoped에 대해서 설명하면 다음과 같습니다.: m := 2 b := block(a, a*m) Foo := Object clone Foo m := 3 Foo b := b Foo b call(1) println위의 코드에서 Foo객체는 m슬롯을 갖고 있지만 이 슬롯을 사용하지 않고 블럭이 만들어진 이름공간의 m 슬롯을 사용하는 것을 알 수 있습니다. 다음은 메서드를 이용해서 이를 구현하였습니다. 참조하는 대상이 전혀 다릅니다.:
v := 2 Foo := Object clone Foo v := 3 Foo m := method(n, n * v) Foo m(1) println Blocks vs. Methods ¶충분히 혼란의 여지가 있기 때문에 블럭과 메서드의 세부적인 사항에 대해서 설명하고 넘어가도록 하겠습니다. 메서드와 블럭은 둘 다 활성화될 때에 각각의 지역변수를 저장하기 위한 객체를 생성합니다. 둘의 차이점은 지역공간에 설정되는 "protos"와 "self"슬롯에 있습니다. 메서드에서는 이 슬롯들이 메시지의 대상객체(수신객체)로 설정됩니다. 블럭에서는 이들이 블럭이 처음 생성된 지역공간객체를 참조하도록 설정이 됩니다. 그러므로 블럭에서 슬롯 찾기가 실패하면 블럭이 생성된 이름공간에 대해서 검색을 계속합니다. 메서드에 있어서는 이것이 메서드가 생성된 이름공간이 아니라 메서드가 포함된 객체에 대해서 계속해서 검색을 진행합니다.
call, self 슬롯 ¶"locals" 객체가 생성되면 이 객체의 "self" 슬롯이 설정됩니다.(메서드의 경우에는 메시지의 수신자, 블럭의 경우에는 생성된 문맥으로) 그리고 "call" 슬롯은 다음의 슬롯들을 포함하는 객체로 설정이됩니다.
가변길이인자 ¶"call message" 슬롯은 평가되지 않은 인자들을 접근하기 위해 사용할 수 있습니다. 예를 들어, IoLanguage에서
if()의 구현을 보면:
myif := method(
(call sender doMessage(call message argAt(0))) ifTrue(
call sender doMessage(call message argAt(1))) ifFalse(
call sender doMessage(call message argAt(2))))
myif(foo == bar, write("true\n"), write("false\n"))
doMessage()메서드는 수신자의 문맥에서 인자를 평가합니다.
이를 짧게 표현하면 call객체의 evalArgAt()메서드를 이용하면 됩니다.:
myif := method(
call evalArgAt(0) ifTrue(
call evalArgAt(1)) ifFalse(
call evalArgAt(2)))
myif(foo == bar, write("true\n"), write("false\n"))
forward ¶만일 객체가 어떤 메시지에 대해서 응답할 수 없을때는 "forward" 메서드가 있다면 이를 호출합니다. 다음은 슬롯에 대한 검색이 실패했을때 이 메시지에 대한 정보를 출력하는 예입니다.:
MyObject forward := method(
write("sender = ", call sender, "\n")
write("message name = ", call message name, "\n")
args := call message argsEvaluatedIn(call sender)
args foreach(i, v, write("arg", i, " = ", v, "\n") ))
resend ¶"self"의 문맥에서 현재의 메시지를 수신자의 "proto"로 다시 보내려면:
A := Object clone
A m := method("in A" println)
B := A clone
B m := method("in B" println; resend)
B m
이는 같은 메시지를 재전달하고자 할 때 사용하며, 다른 메시지를 전달하고자 할때에는 "super" 슬롯을 이용합니다.
super ¶객체의 "proto"에 대해서 메시지를 직접 보내고 싶을때에는 "super"을 이용합니다.:
{{{
Dog := Object clone
Dog bark := method("woof!" println)
fido := Dog clone
fido bark := method("ruf!" println; super(bark))
=== Introspection ===
다음 메서드들을 이용해서 모든 객체들에 대해 introspect 할 수 있습니다. 또한 실행시간에 객체의 모든 속성을 수정할수도 있습니다.
==== slotNames ====
"slotNames" 메서드는 객체의 모든 슬롯의 이름목록을 되돌립니다.:
{{{
Io> Dog slotNames
==> list("bark")
}}}
==== protos ====
"protos" 메서드는 객체가 상속하는 객체들의 목록을 되돌립니다.:
{{{
Io> Dog protos
==> list("Object")
}}}
==== getSlot ====
"getSlot" 메서드는 객체의 슬롯을 활성화하지 않고서 슬롯의 값을 접근하려고 할 때 사용합니다.:
{{{
myMethod := Dog getSlot("bark")
}}}
위 코드에서 "locals"의 "myMethod" 슬롯을 "bark"메서드로 설정했습니다.(이때 "bark"메서드를 활성화하지 않았음을 명심하세요.)
{{{
otherObject newMethod := getSlot("myMethod")
}}}
이렇게 한다면 "getSlot"의 대상은 "locals" 객체이고, 이렇게 "myMethod" 슬롯을 활성화하지 않고 얻어서 그 객체를 다른 객체의 슬롯에 대입하여 메서드만을 복사할 수 있습니다.
==== code ====
메서드의 인자들과 표현식은 introspection에 대해 개방되어 있습니다. "code" 메서드는 메서드의 소스코드를 문자열로 되돌려줍니다.:
{{{
Io> method(a, a*2) code
==> "method(a,a*(2))"
}}}
----
== 제어구조 ==
=== true, false, nil ===
IoLanguage은 true, false, nil에 대해서 미리 정의된 singleton을 제공합니다. "true", "false"은 각각 불리언진리값을 나타내며 "nil"은 일반적으로 설정되지않음(unset)이나 가용하지않은값(unavailable value)을 나타냅니다.
또한 이러한 값들은 표식으로서의 기능 이외에도 객체로서 그들과 연관되어 조건식과 같은 제어구조를 만들 수 있도록 해줍니다. (SmalltalkLanguage 스타일의 제어구문...)
=== 비교 ===
표준비교연산(`==`, `!=`, `>=`, `<=`, `>`, `<`)은 true 혹은 false을 되돌립니다.
{{{
Io> 1 < 2
==> true
}}}
=== 조건식 ===
==== if ====
"Lobby"은 조건식과 루프를 위한 메서드들을 포함하고 있습니다. 조건식은 다음과 같습니다.:
{{{
if(<조건식>, <then-메시지>, <else-메시지>)
}}}
예를 들어:
{{{
if(a == 10, "a is 10" println)
}}}
"else"인자는 선택적입니다. 조건식이 "false"나 "nil"로서 평가되면 이는 참이 아닌, 거짓으로 판단합니다. 또한 평가된 메시지의 결과를 되돌리므로 다음은:
{{{
if(y < 10, x := y, x := 0)
}}}
다음과 같습니다.:
{{{
x := if(y < 10, y, 0)
}}}
조건제어는 다음과 같이 쓸수도 있습니다.(성능은 더 떨어지지만):
{{{
if(y < 10) then(x := y) else(x := 2)
...
if(y < 10) then(x := y) elseif(y==11) then(x := 0) else(x := 2)
}}}
또한 SmalltalkLanguage 스타일의 "ifTrue", "ifFalse", "ifNil", "ifNonNil" 메서드도 있습니다.:
{{{
(y < 10) ifTrue(x := y) ifFalse(x := 2)
}}}
위에서 조건식은 반드시 괄호로 둘러쳐야 합니다. ;-)
=== 루프 ===
==== loop ====
"loop" 메서드는 '''무한루프'''를 구성하는데 사용합니다.:
{{{
loop("foo" println)
}}}
==== while ====
조건제어처럼 루프들 또한 메시지입니다. "while()"은 다음의 인자들을 요구합니다.:
{{{
while(<조건식>, <do-메시지>)
}}}
예를 들어:
{{{
a := 1
while(a < 10,
a print
a = a + 1)
}}}
==== for ====
"for"은 4개의 인자를 필요로 합니다.:
{{{
for(<counter>, <start>, <end>, <do-message>)
}}}
"start", "end" 메시지는 오직 루프가 시작될때 한번만 평가됩니다.:
{{{
for(a, 0, 10, a println)
}}}
역순으로 루프를 돌리려면 그냥 "start", "end"의 위치를 바꿔주면 됩니다.:
{{{
for(a, 10, 0, a println)
}}}
'''주의''': "start", "end"은 모두 루프에 포함이 됩니다. 다시 말해서 다른 언어에서 루프를 구성하듯이 0에서 10까지 루프를 돌린다면 실제로 11번 루프를 실행할것입니다.
==== repeat ====
"Number"의 "repeat" 메서드는 그 숫자만큼 반복해서 루프를 실행하고자 할 때 유용합니다.("counter"은 제공이 안됩니다.):
{{{
3 repeat("foo" println)
}}}
==== break, continue ====
흐름제어작업을 중단("break")하거나 다음으로 뛰어넘기("continue") 할 수 있습니다.:
{{{
for(i, 1, 10,
if(i==3, continue)
if(i==7, break)
i print)
}}}
결과는...:
{{{
12456
}}}
==== return ====
"return" 메서드를 사용해서 블럭의 어떤 부분에서도 바로 값을 되돌리며 실행을 되돌릴 수 있습니다.:
{{{
Io> test := method(123 print; return "abc"; 456 print)
Io> test
123
==> "abc"
}}}
=== Importing ===
"Importer" 프로토는 IoLanguage의 내장 AutoImporter기능을 구현합니다. 각각의 프로토들을 파일에 넣고, 파일이름은 프로토와 같은 이름의 ".io"확장자로 저장하면, "Importer"가 자동으로 해당 프로토가 필요할때에 파일을 로딩할 것입니다. Impoter의 기본검색경로는 현재 작업디렉토리이며, "appendSearchPath()" 메서드를 이용하여 추가할 수 있습니다.
----
== 동시성 ==
=== Coroutine ===
IoLanguage은 선점적인 OS수준 스레드(preemptive OS level threads) 대신에 CoRoutine(user-level cooperative threads)을 사용합니다. 이는 NativeThread을 이용함에 있어서 추가적인 비용(메모리, 시스템콜, locking, 캐슁문제...)등을 없애고 수많은 활성스레드에 대해서 고수준의 동시성(concurrency)을 가능하게 합니다.
=== Scheduler ===
"Scheduler" 객체는 "yielding"중인 CoRoutine을 계속하도록 하는것에 책임이 있습니다. 현재의 스케쥴링시스템은 단순히 우선순위 없는 FirstInFirstOut 정책을 사용합니다.
=== Actors ===
"actor"은 스레드를 갖는 객체를 말합니다.(이 경우에는 coroutine) 이는 비동기메시지(asynchronous message)들을 위한 큐(queue)를 관리합니다. IoLanguage의 모든 객체는 "@"이나 "@@"을 메시지 이름 앞에 붙여서 비동기메시지로서 보낼 수 있습니다.("@"의 "a"을 "asynchronous"로 생각해보세요)
{{{
result := self foo // synchronous
futureResult := self @foo // async. 즉시 리턴하지만 리턴값의 타입은 Future
self @@foo // async. 즉시 nil을 리턴.
}}}
객체가 비동기메시지를 받으면 각각의 큐에 메시지를 넣습니다. 만일 큐에 메시지가 하나도 없었다면 바로 메시지를 처리합니다. 큐에 넣은 메시지는 FirstInFirstOut 순서에 의해서 차례대로 처리됩니다. 제어권을 다른 CoRoutine으로 넘기려면 "yield"을 사용합니다.:
{{{
obj1 := Object clone
obj1 test := method(for(n, 1, 3, n print; yield))
obj2 := obj1 clone
obj1 @@test; obj2 @@test
while(Scheduler activeActorCount > 1, yield)
}}}
출력은 "112233"이 됩니다. 다음은 좀 더 실제적인 예제입니다.:
{{{
HttpServer handleRequest := method(aSocket,
HttpRequestHandler clone @@handleRequest(aSocket))
}}}
=== yield ===
객체는 다른 객체와 비동기메시지를 처리하기 위해 자동적으로 "yield"합니다. "yield"메서드는 오직 비동기메시지의 실행 도중에만 호출할 필요가 있습니다.
=== 일시정지, 계속 ===
객체를 일시정지(pause) 했다가 다시 계속(resume) 할 수 있습니다. "Object" primitive의 동시성(concurrency)관련 메서드들을 살펴보세요. :-<
=== Future ===
IoLanguage의 "futures"은 투명합니다. 이는 결과가 준비되면 결과값으로 바뀝니다. 만일 메시지가 "future"에 대해서 보내지만(2개의 메서드를 구현하고 있습니다), 메시지를 처리가 끝나고 결과가 나올때까지 기다립니다. 투명한 "future"은 프로그램의 블록(blocking)된 상태를 최소화해주며, 프로그래머를 동기화의 세부적인 부분으로부터 자유롭게 해주는 강력함을 제공합니다.
=== AutoDeadlockDetection ===
"future"을 사용하는데 또 다른 장점은 "future"가 "wait"할 필요가 있을때, 결과가 교착(deadlock)으로 이끌지 아닐지를 검사할 수 있다는 것이며, 이를 통해서 교착상태를 예방하고 예외를 발생시킵니다. 이는 관련된 "future"들에 대해서 순회함으로서 구현됩니다.
=== @, @@ 연산자 ===
"@", "@@"을 일반적인 메시지의 앞에 붙여주면 이를 연산자로서 작용합니다.:
{{{
self @test
}}}
...은 파싱되면 다음과 같습니다.:
{{{
self @(test)
}}}
----
== 예외 ==
=== raise ===
예외를 던지려면 "raise()"을 "exception" 프로토에 대해서 호출하면 됩니다.:
{{{
exceptionProto raise(<description>)
}}}
"Exception" 프로토의 자식으로서 미리 정의된 3개의 프로토가 있습니다.: "Error", "Warning", "Notification":
{{{
Exception raise("generic foo exception")
Warning raise("No defaults found, creating them")
Error raise("Not enough memory")
}}}
=== try, catch ===
예외를 잡기 위해서는 "Object" 프로토의 "try()"메서드를 사용합니다. "try()"은 그 안에서 일어난 예외를 잡아서 예외를 되돌리거나 예외가 없었다면 "nil"을 되돌립니다.:
{{{
e := try(<doMessage>)
}}}
이렇게 잡은 예외를 처리하려면 "Exception"의 "catch()"메서드를 사용합니다.:
{{{
e := try(
//...
)
e catch(Exception, (e coroutine backtraceString) println)
}}}
"catch"의 첫번째 인자는 잡을 예외의 타입을 나타내고, 두번째는 그에 대한 처리블럭입니다. 만일 "catch()"에 일치하는 것이 하나도 없다면 예외를 되돌리고, 있었다면 "nil"을 되돌립니다.
=== pass ===
"try()"에 의해서 잡은 예외를 다시 던지기 위해서는 "pass" 메서드를 사용합니다. 이 메서드는 그 상위의 "try()"에게 예외를 전달하고자 할 때 유용합니다. 일반적으로 적절하게 처리하지 못한 예외가 있을때 이를 이용하여 예외를 상위 핸들러에게 전달하고자 할 때에 사용합니다.
{{{
e := try(
// ...
)
e catch(Error,
// ...
) catch(Exception,
// ...
) pass
}}}
=== 사용자정의 예외 ===
단순히 "Exception" 프로토를 복제하여 자신만의 예외를 만들 수 있습니다.:
{{{
MyErrorType := Error clone
}}}
=== NilMagic ===
사실 예외를 위의 예제와 같이 연동해서 사용하는데 조금 불안한 사람도 있을것입니다. 예외가 합당하게 처리되었다면 "catch"메서드는 "nil"을 되돌릴 것이고, 그렇다면 "nil"에 대해서 "pass"나 다른 "catch" 메시지를 보내는 것이 되므로 JavaLanguage등에 익숙한 분들은 상당히 불안해 하실 것 같습니다. 하지만 "nil slotNames"와 같이 "nil" 프로토를 찾아보면 다행히 이러한 메서드들을 구현하고 있다는 것을 알 수 있습니다.(당연히 내부 구현은 아무것도 하지 않는 것이겠지요.) 또한 이후에 이런식으로 "nil"을 이용하고자 하는 경우에는 이런 처리를 하는 프로토를 하나 만들고서 "nil" 프로토의 "protos"목록에 그 객체를 추가하면 "nil"로도 그러한 행동을 잘 처리하도록 프로그램을 작성할 수 있습니다. 이런게 PrototypeBased의 강력함이 아닐까요... ^^;
----
== Primitives ==
"Primitive"("원소"라고 말하겠습니다.)은 일반적으로 C언어로 구현하여 IoLanguage에 내장되어 있는 기본적인 객체들을 말합니다. 예를 들어 "Number"원소는 배정도실수(double precision floating point number)를 그 내부의 숨은 자료로서 포함하며, 이에 대한 메서드들은 사실 C언어로 구현된 함수입니다. 모든 IoLanguage의 원소들은 "Object" 프로토로부터 파생되었으며 변형가능(mutable)합니다. 이는 원소의 메서드도 변경할 수 있음을 의미합니다. 레퍼런스 문서들은 원소들에 대한 더 많은 정보를 포함하고 있습니다.
이 문서는 레퍼런스 문서는 아니지만, IoLanguage을 이용하는데 있어서 기본적으로 사용하는 원소들과 바인딩(binding)들에 대해서 설명하며 어떠한 것들이 제공되는지를 알려드릴것입니다.
무엇보다 이 문서에서 제공하는 것들은 말그대로 입문에 관한 맛보기 수준의 것들이며 더 유용하고 많은 기능들이 IoLanguage에는 포함되어 있습니다. :-)
=== Object ===
=== "?" 연산자 ===
종종 어떤 객체에 메서드가 존재하는지 검사하고 있을때에만 이를 호출해야만 할 때가 있습니다. 이럴 때에 예외를 일으키지 않으려면 다음처럼 해야할것입니다.:
{{{
if(obj getSlot("foo"), obj foo)
}}}
이를 짧게 줄이면 메시지 앞에 "?"을 붙이면 같은 효과를 낼 수 있습니다.:
{{{
obj ?foo
}}}
=== List ===
"List"은 참조들로 이루어진 배열로서 표준적인 배열조작과 나열을 위한 메서드들을 포함합니다.
빈 리스트를 만들려면:
{{{
a := List clone
}}}
또는 바로 몇개의 객체들을 포함하는 리스트를 만들려면 "list()" 메서드를 이용합니다.:
{{{
a := list(33, "a")
}}}
이에 객체를 추가하려면:
{{{
a append("b")
==> list(33, "a", "b")
}}}
리스트의 크기를 구하려면:
{{{
a size
==> 3
}}}
또는 어떤 위치의 요소를 구하고자 한다면("List"의 색인은 0부터 시작합니다.):
{{{
a at(1)
==> "a"
}}}
'''주의''': "List"의 색인은 0부터 시작하고 접근한 색인이 존재하지 않으면 "nil"을 되돌립니다.
특정 위치의 요소를 설정하려면:
{{{
a atPut(2, "foo")
==> list(33, "a", "foo")
a atPut(6, "Fred")
==> Exception: index out of bounds
}}}
특정 요소를 삭제하려면:
{{{
a remove("foo")
==> list(33, "a", "b")
}}}
특정 위치에 요소를 삽입하려면:
{{{
a atInsert(2, "foo")
==> list(33, "a", "foo", ...)
}}}
==== foreach ====
"foreach", "map", "select" 메서드는 3가지 형태로 사용됩니다.
{{{
Io> a := list(65, 21, 122)
}}}
첫번째 형태로서 첫번째 인자는 "index variable", 두번째 인자는 "value varaible", 세번째는 각각의 값에 대해서 평가할 문장입니다.:
{{{
Io> a foreach(i, v, write(i, “:”, v, “, ”))
==> 0:65, 1:21, 2:122,
}}}
두번째 형태는 "index argument"을 제외한 경우입니다.:
{{{
Io> a foreach(v, v println)
==> 65
21
122
}}}
세번째 형태는 모든 인자를 제외하고 표현식을 각각의 값에 대해서 보내도록 하는 방법입니다.:
{{{
Io> a foreach(println)
==> 65
21
122
}}}
==== map, select ====
IoLanguage의 "map", "select"("filter"라고도 다른 언어에서 알려진) 메서드는 다양한 표현식을 map/select predicate로서 사용하게 해줍니다.:
{{{
Io> numbers := list(1,2,3,4,5,6)
Io> numbers select(isOdd)
==> list(1,3,5)
Io> numbers select(x, x isOdd)
==> list(1,3,5)
Io> numbers select(i, x, x isOdd)
==> list(1,3,5)
Io> numbers map(x, x*2)
==> list(2,4,6,8,10,12)
Io> numbers map(i, x, x+i)
==> list(1,3,5,7,9,11)
Io> numbers map(*3)
==> list(3,6,9,12,15,18)
}}}
"map", "select" 메서드는 새로운 리스트를 되돌립니다. InPlace형식으로 현재의 객체를 변경하려면 "selectInPlace()", "mapInPlace()"을 사용하세요.
=== Sequence ===
IoLanguage에서 불변적인 시퀀스(ImmutableSequence)은 "Symbol"이라고 불리고 "Buffer"나 "String"처럼 가변적인 시퀀스는 "Sequence"라고 합니다. 문자열표현식(Literal Strings, 소스코드에서 쌍따옴표로 묶여져서 표현하는)은 "Symbol"입니다. "Symbol"에 대해서는 변경을 요하는 작업(MutableOperations)은 적용할 수 없으며, "asMutable" 메서드를 사용하여 복사한 다음에 이를 적용해야합니다.
==== 공통적인 문자열 작업들 ====
문자열의 길이를 얻기:
{{{
"abc" size
==> 3
}}}
문자열이 부분문자열을 포함하는가?:
{{{
"apples" containsSeq("ppl")
==> true
}}}
N의 위치의 문자(바이트)을 구하기:
{{{
"Kavi" at(1)
==> 97
}}}
슬라이스(Slicing):
{{{
"Kirikuro" slice(0,2)
==> "Ki"
"KiriKuro" slice(-2) # != slice(-2, 0)
==> "ro"
"Kirikuro" slice(0, -2)
==> "Kiriku"
}}}
공백제거하기(stripping whitespace):
{{{
" abc " asMutable strip
==> "abc"
" abc " asMutable lstrip
==> "abc "
" abc " asMutable rstrip
==> " abc"
}}}
대소문자변환:
{{{
"Kavi" asUppercase
==> "KAVI"
"Kavi" asLowercase
==> "kavi"
}}}
리스트로 분리하기:
{{{
"the quick brown fox" split
==> list("the", "quick", "brown", "fox")
}}}
다른 문자를 분리자로 해서 분리하기:
{{{
"a few good men" split("e")
==> list("a f", "w good m","n")
}}}
숫자로 변환하기:
{{{
"13" asNumber
==> 13
"a13" asNumber
==> nil
}}}
변수포함하기(interpolation):
{{{
name := "Fred"
"My name is #{name}" interpolate
==> "My name is Fred"
}}}
"interpolate"은 "#{}"안의 어떠한 코드든지 LocalContext안에서 평가하여 결과를 되돌립니다. 이 코드는 루프나 어떠한 것이든 "asString" 메시지에 응답할 수 있는 것은 뭐든지 할 수 있습니다.
=== Range ===
"range"은 시작점과 끝점을 포함하는 자료구조이며, 시작점으로부터 끝점까지 어떻게 이동하는지를 포함합니다. "Range"을 이용하여 커다란 리스트나 순차적인 데이터를 만들어 리스트나 "for()"을 대체하는 것을 쉽게합니다.
==== "Range" 프로토콜 ====
어떤 객체이건 "nextInSequence" 메서드를 구현하면 "Range"에 포함할 수 있습니다. 이 메서드는 하나의 선택적인 인자를 취합니다.(객체들의 순서에서 몇개나 그냥 지나칠 것인지를 지정하는 숫자입니다.) 또한 지나간 다음 다음의 객체를 되돌리면 됩니다. 기본적인 "skip value"은 1입니다. "skip value" 0은 정의되지 않았습니다.:
{{{
Number nextInSequence := method(skipVal,
if(skipVal isNil, skipVal = 1)
self + skipVal)
}}}
이렇게 "Number"에 이 메서드를 넣으면(기본적으로 포함되어 있지만), "Number"을 "Range"에서 사용할 수 있습니다.:
{{{
1 to(5) foreach(v, v println)
}}}
이상의 코드는 1부터 5까지 출력할것입니다.
=== File ===
File객체를 생성하는 것만으로는 파일내용에 관련된 작업을 하지 않고, 파일관리(삭제와 같은)을 할 수 있으며, 실제로 파일의 내용을 읽거나 쓰려면 "openForAppending", "openForReading", "openForUpdating"을 사용하여 열어야 합니다. 파일을 삭제하려면 "remove"메서드를 사용합니다.
{{{
f := File with(“foo.txt)
f remove
f openForUpdating
f write(“hello world!”)
f close
}}}
=== Directory ===
디렉토리 생성하기:
{{{
dir := Directory with(“/Users/steve/”)
}}}
디렉토리의 모든 파일들의 목록 구하기:
{{{
files := dir files
==> list(File_0x820c40, File_0x820c40, ...)
}}}
디렉토리의 디렉토리를 포함한 파일목록 구하기:
{{{
items := Directory items
==> list(Directory_0x8446b0, File_0x820c40, ...)
items at(4) name
==> DarkSide-0.0.1 # a directory name
}}}
절대경로로 설정하고 이를 사용하기:
{{{
root := Directory clone setPath("c:/")
==> Directory_0x8637b8
root fileNames
==> list("AUTOEXEC.BAT", "boot.ini", "CONFIG.SYS", ...)
}}}
존재하는지를 검사하기:
{{{
Directory clone setPath("q:/") exists
==> false
}}}
현재작업디렉토리 구하기:
{{{
Directory currentWorkingDirectory
==> “/cygdrive/c/lang/IoFull-Cygwin-2006-04-20”
}}}
=== Date===
새로운 "Date"인스턴스를 만들기:
{{{
d := Date clone
}}}
현재 시각으로 맞추기:
{{{
d now
}}}
초단위로 환산하기:
{{{
Date now asNumber
==> 1147198509.417114
Date now asNumber
==> 1147198512.33313
}}}
"Date"객체의 부분부분을 얻기:
{{{
d := Date now
==> 2006-05-09 21:53:03 EST
d
==> 2006-05-09 21:53:03 EST
d year
==> 2006
d month
==> 5
d day
==> 9
d hour
==> 21
d minute
==> 53
d second
==> 3.747125
}}}
어떤 코드를 실행하는데 걸린 시간 구하기:
{{{
Date cpuSecondsToRun(100000 repeat(1+1))
==> 0.02
}}}
=== Networking ===
모든 IoLanguage의 네트워킹 관련 객체들은 비동기적 소켓(AsynchronousSocket)의 아래에 있지만, 읽기나 쓰기와 같은 CoRoutine이 호출되어야 하는 작업들은 이러한 작업이 완료될 때까지 동기적으로 처리하거나, 타임아웃(timeout)이 발생합니다.
'''주의''': 다음을 이용하기 위해서 "Socket" addon과 같은 addon을 먼저 컴파일하여 포함할 필요가 있습니다.
URL 객체 만들기:
{{{
url := URL with("http://example.com/")
}}}
URL의 내용가져오기:
{{{
data := url fetch
}}}
URL의 내용을 파일로 쓰기:
{{{
url streamTo(File with("out.txt"))
}}}
SimpleWhoisClient:
{{{
whois := method(host,
socket := Socket clone setHostName("rs.internic.net") setPort(43)
socket connect streamWrite(host, "\n")
while(socket streamReadNextChunk, nil)
return socket readBuffer)
}}}
MinimalWebServer:
{{{
WebRequest := Object clone do(
handleSocket := method(aSocket,
aSocket streamReadNextChunk
request := aSocket readBuffer betweenSeq("GET ", " HTTP")
f := File with(request)
if(f exists, f streamTo(aSocket), aSocket streamWrite("not found"))
aSocket close))
WebServer := Server clone do(
setPort(8000)
handleSocket := method(aSocket,
WebRequest clone @handleSocket(aSocket)))
WebServer start
}}}
=== XML ===
XML 파서를 사용하여 해당 페이지의 링크들을 얻기:
{{{
xml := URL with("http://www.yahoo.com/") fetch asXML
links := xml elementsWithName("a") map(attributes at("href"))
}}}
----
== 내장하기 ==
이 섹션에서는 IoVm을 다른 C프로그램에 내장하는 방법에 대해서 간략하게 설명하겠습니다.
=== 코딩규약 ===
IoLanguage의 C언어 코드들은 구조체를 객체로, 함수들을 메서드로 하는 객체지향적인 스타일 규약을 따라서 작성되었습니다. 이러한 것들과 친숙해지는 것은 내장 API들을 더 쉽게 이해하는데 있어서 도움이 될 것 입니다.
==== 구조체 ====
멤버의 이름은 일반적인 소문자로 시작하는 낙타로 표기합니다.:
{{{
typedef struct {
char *firstName;
char *lastName;
char *address;
} Person;
}}}
==== 함수 ====
함수이름은 그들이 연관된 구조체의 이름과 밑줄을 가운데 두고서 나머지 단어들을 밑줄로 잇습니다. 또한 모든 구조체는 그들에 해당하는 "new", "free" 함수가 있습니다.:
{{{
List *List_new(void);
void List_free(List *self);
}}}
"new"을 제외하고 모든 메서드들은 ("객체")에 대한 구조체를 첫번째 인자로서 받는데 이름을 "self"로 합니다. 메서드 이름은 "keyword format"으로 짓습니다. 이는 각각의 인자에 대해서 메서드 이름 끝에 밑줄을 달아줍니다.:
{{{
int List_count(List *self); /* no argument */
void List_add_(List *self, void *item); /* one argument */
void Dictionary_key_value_(Dictionary *self, char *key, char *value);
}}}
==== 파일이름 ====
각각의 구조체는 분리된 .h, .c 파일에 저장합니다. 파일의 이름은 구조체의 이름과 동일하게 짓습니다. 이러한 파일들은 연관된 모든 함수(메서드)를 포함합니다.
=== IoState ===
"IoState"은 IoVm의 인스턴스로 생각할 수 있습니다.
==== 여러개의 상태들 ====
IoLanguage은 MultiState합니다. 이 뜻은 한번에 여러개의 상태가 하나의 프로세스 안에 존재할 수 있다는 뜻입니다. 이러한 인스턴스들은 각기 격리되어 있으며 어떠한 메모리도 공유하지 않으므로 동시에 다른 스레드들로부터 안전하게 접근이 가능합니다.(하나의 스레드가 하나의 상태만을 접근해야겠지만)
==== Creating a state ====
간단한 문자열을 평가하는 코드입니다. 상태를 만들고, 초기화하고, 평가한 다음에 다시 해제합니다.:
{{{
#include "IoState.h"
int main(int argc, const char *argv[]) {
IoState *self = IoState_new();
IoState_init(self);
IoState_doCString_(self, “writeln(\”hello world!\””);
IoState_free(self);
return 0;
}
}}}
=== Values ===
되돌림 값을 받아서 이를 다시 출력하려면 다음과 같이 합니다.:
{{{
IoObject *v = IoState_doCString_(self, someString);
char *name = IoObject_name(v);
printf(“return type is a ‘%s’, name);
IoObject_print(v);
}}}
==== Checking value types ====
간단한 타입체크를 위한 매크로가 있습니다.:
{{{
if (ISNUMBER(v)) {
printf(“result is the number %f”, IoNumber_asFloat(v)); }
else if(ISSEQ(v)) {
printf(“result is the string %s”, IoSeq_asCString(v)); }
else if(ISLIST(v)) {
printf(“result is a list with %i elements”, IoList_rawSize(v)); }
}}}
'''주의''': 되돌림값은 언제나 확실한 Io 객체입니다.(모든 값이 Io안의 객체이듯이.) 이러한 것들에 대해서 C언어 수준에서 작업할 수 있는 메서드들("IoList_rawSize()"와 같은 함수들)을 "./libs/iovm/source"의 헤더파일에서 쉽게 찾을 수 있을 것 입니다.
----
##TODO: 확장?
----
CategoryLanguage |
"Perl is executable line noise, Python is executable pseudo-code." |











