= IoLanguage/Tutorial = * 최초 작성자 : [ageldama] 2007-02-21 * http://www.iolanguage.com/darcs/Io/docs/guide.html 1:1.1 번역-_- * 최종 수정 : [rafael] 2008-10-10 오타 및 예제 == 소개 == * 홈페이지 : http://www.iolanguage.com/ * 최소한의 언어를 지향하여 우아아고 단순한 문법이 특징이다. * PureObjectOriented : 모든것이 '''정말로''' 객체이다. * PrototypeBased : JavaLanguage, SmalltalkLanguage 등의 객체지향언어들과 달리 ''클래스''와 ''객체''의 구분이 따로 없다. * 오픈소스(BsdLicense), 표준C로 작성되어서 이식성이 좋고 작은 크기로 다른 스크립팅 언어들과 비교할만한 실행속도. * Reflective : 프로그램의 모든 상태가 실행시간에 프로그램에 의해서 평가, 수정이 이루어질 수 있다. * 단순한 문법 : IoLanguage에는 예약어나 키워드가 없다. 모두 객체와 메시지전달로 이루어져 있다. ---- == 시작하기 == === 설치 === * http://www.iolanguage.com/downloads/ IoLanguage을 사용하기 위해서 기존에 이미 컴파일되어 있는 PrecompiledBinary을 사용하거나 아니면 소스를 받아서 직접 컴파일합니다. 컴파일하기 위해서는 각각의 환경마다 필요한 프로그램과 컴파일절차가 다를 수 있습니다. 여기서는 일반적인 리눅스 환경에서 컴파일 하는 것을 기준으로 설명하겠습니다. 또한 IoLanguage에는 여러가지 바인딩이 있고 조금씩 다른 라이브러리들이 제공되지만 여기서는 오직 기본적인 사용과 언어의 기본에만 집중하도록 하겠습니다. 1. 소스 다운로드 : DarcsVersionControl을 사용하여 소스를 가져와도 되지만 그냥 압축된 버젼의 소스를 가져다 압축을 풉니다. {{{ > wget http://io.urbanape.com/release/Io-2007-02-19.tar.gz }}} 2. 압축풀고 컴파일하기 : {{{ > tar xvfz Io-2007-02-19.tar.gz ... > cd Io-2007-02-19 > make ... }}} 컴파일이 완전히 성공하지 않을수도 있습니다. IoLanguage에 포함된 여러가지 ApiBinding이 있는데 이 바인딩들을 모두 컴파일하지 못하고 시스템 설정에 따라서 실패하는 경우도 있습니다. 하지만 기존 인터프리터만을 컴파일 완료했다면 이 문서를 따라서 IoLanguage을 맛보실 수 있으므로 계속 진행합니다. ;-) 3. 실행하기 : 위 과정이 성공했다면 _build/{binaries,dll,lib}등에 실행화일, 다른 프로그램에 내장하기 위한 라이브러리, SharedObject등이 생성되어 있을것입니다. {{{ > ./_build/binaries/io_static }}} 위처럼 쉘에서 실행하였을때 `Io>`와 같은 프롬프트가 뜬다면 인터프리터를 실행시키는데 성공한 것입니다. === HelloWorld! === * 첫번째 프로그램 : 인터프리터의 프롬프트에서 다음을 입력하고 엔터를 쳐보세요. {{{ 1 to(5) foreach(n, (n .. ": Hello, World!") println) }}} 화면에 다섯줄의 문구가 찍혔나요? * 첫번째 프로그램의 분석 : 정말 보잘것 없이 단순한 프로그램이지만 위의 한줄의 코드를 뜯어보면서 IoLanguage의 문법과 특징을 알아보도록 하겠습니다. 코드가 나타난 순서가 아니라 각각의 기능하는 부분을 나눠서 설명하도록 하겠습니다. (그러므로 코드의 순서랑은 조금 다를 수 있습니다.) 위 코드는 한줄로 표현했지만, 크게 네부분으로 나눌 수 있습니다. * `1 to(5)` : IoLanguage에서는 '''모든것이 객체로 취급'''됩니다. 다른 언어에서처럼 PrimitiveType 같은 것이 필요없이 숫자 또한 다른 객체와 마찬가지로 그에 관련된 메서드를 호출하는 것이 가능합니다. 메서드 호출은 다음과 같은 형식으로 합니다. {{{ aObject someMethod }}} 위의 예제는 인자가 없는 메서드 UnaryMethod인 경우에 해당하고, 인자가 있는 경우에는 다음처럼 메서드 이름 뒤에 괄호로 인자들을 묶고 쉼표로 서로 구분해줍니다. {{{ aObject otherMethod(arg1, arg2, arg) }}} 이를 응용해 보겠습니다. IoLanguage에서도 `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까지의 각 단계값들을 나열할 수 있습니다. * `n .. ": Hello, World!"` : 이 부분은 문자열에 관한 부분입니다. 다른 언어들처럼 IoLanguage 또한 문자열을 쌍따옴표를 이용하여 표현합니다. 또한 여러줄에 걸친 문자열을 표현하고자 한다면 쌍따옴표 3개를 연이어서 사용하면 됩니다.(뱀다리로 첨언하면 IoLanguage은 현재 Utf8만을 지원합니다. 그러므로 한글을 포함한 프로그램을 작성하고자 한다면 반드시 프로그램을 Utf8형식으로 저장하여야 합니다.) {{{ Io> "foobar" ==> foobar Io> """this is "-> multiline "-> string.""" ==> this is multiline string. }}} 다시 원래의 부분으로 돌아와서 `n`이라는 변수와 ": Hello..."라는 문자열을 서로 이어주는 연산자는 `..`이라는게 이 코드의 설명입니다. (사실 `..` 메서드는 String이 아니라 Object에서 구현하고 있어서 String이 아닌 다른 객체가 먼저 앞에오는 경우에도 문자열로 붙이는데 불편하지 않도록 하고 있습니다.) 한마디로 이 부분은 출력할 문자열을 생성하는 부분입니다. `n`은 왜 `n`인지는 미뤄기로 합시다. * `(n .. ": Hello, World!") println` : 이 표현식은 괄호안의 부분은 이미 설명한 부분이므로 생략하고 본다면... {{{ "..." println }}} ...이란 코드와 같습니다. 괄호안의 부분은 문자열이 되는 부분이고 그 문자열에 대해서 println이란 메시지를 보내서 이를 화면에 출력합니다. IoLanguage에서는 모든 표현식이 결과값을 되돌립니다. 문자열이나 정수, 실수와 같은 상수표현은 그 값 자체가 되돌림 값이고 메시지전달은 그 메서드의 되돌림값, 혹은 마지막으로 그 메서드 안에서 평가된 값이 그 메시지호출의 결과값입니다. 모든 것들은 객체이므로 계속해서 그 되돌림값에 대해서 적당한 메시지를 전달해서 원하는 결과를 이끌어 낼 수 있습니다. {{{ 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에서는 프로그램의 코드 또한 객체로서 취급될 수 있습니다. (CommonLisp나 LispLanguage에서의 매크로를 통한 문법적인 추상화, 다른 여타 언어들에서 말하는 AnonymousFunction 같은 것들을 괴상한 매크로를 작성하며 괴로워하거나 할 필요없이 간단하게 이용할 수 있습니다.) 위 예제에서 `;`은 여러표현식을 한줄에 적을 수 있도록 표현식을 서로 나눠주는 역활을 합니다. ---- == 사용상의 팁 == * IoLanguage로 일일이 InteractiveMode에서 입력하기 싫다면 편집기로 확장자가 `.io`인 파일로 저장하고서 `io` 인터프리터에 인자로 넘겨줘서 실행하면 스크립트처럼 사용할 수 있습니다. 아니면 `#!/home/ageldama/my/bin/io`처럼 파일의 맨 앞줄에 인터프리터를 설치한 경로를 지정해주고 실행권한을 주면 다른 스크립트처럼 동일하게 사용할 수 있습니다. 특별한 EntryPoint 같은 것이 없이 IoScript은 첫줄부터 그냥 순서대로 평가됩니다. * 변수나 바인딩 같은 개념을 위해서 평가문맥(EvaluationContext)가 있습니다. 이는 어떤 코드를 실행할 때 그 문맥이 어디에 속하는지를 나타냅니다. 이에 대해서 자세하게는 이후에 설명하도록 하겠습니다. 대신에 기본적으로 수신자(Receiver)을 지정하지 않고서 메서드를 호출하면 그 해당 평가문맥을 사용합니다. 이 평가문맥은 `thisContext`로 참조할 수 있습니다. 또한 인터프리터를 실행하면 이 기본 평가문맥은 `Lobby`라고 부릅니다. {{{ Io> println ... Io> thisContext println ... }}} 인터프리터에서 이를 실행하면 두가지 문장 모두 같은 결과를 출력할것입니다. * 모든 객체는 `Object`객체와 관련이 있습니다. 또한 Object객체는 객체에 대한 기본적인 메서드들을 포함합니다. 어떤 객체에 대해서 슬롯(slot)의 목록을 얻고 싶으면 다음처럼 합니다. ('''슬롯'''이 무엇인지는 이후에 설명하겠습니다.) {{{ Io> String slotNames ... }}} 이렇게 어떤 객체의 슬롯을 알 수 있다면 그 객체가 어떤 작업을 할 수 있는지 대략적으로 파악할 수 있습니다. 하지만 다음과 같은 경우도 있습니다. {{{ Io> "foobar" slotNames ==> list() }}} 분명히 문자열에 대해서 다른 메시지를 보낼 수 있음에도 그 슬롯이 없는것처럼 보입니다. 하지만 이것은 잘못된 것이 아닙니다. "foobar"은 String으로부터 복제된 객체이고 이는 String이 이해하는 메시지를 "foobar"에 보내도 아무런 문제가 없으며 실제로 "foobar"가 수신하는 메시지는 특별한 경우가 아니면 String에게 그대로 전달되기 때문입니다. 이것을 자세하게 이해하기 위해서는 이후에 설명하는 Protos에 대한 부분을 참고하시기 바랍니다. 지금으로서는 이러한 상수나 파생객체들은 그 원래의 객체를 알고 그 원래 객체의 슬롯목록을 구해서 이를 할 수 있다고 이해하는 것이 편합니다. 어떤 객체가 속한 타입을 알려고 한다면... {{{ Io> "foobar" type ==> Sequence Io> Sequence slotNames ... }}} * 스크립트에서 다른 스크립트를 실행하려고 한다면 `anObj doFile(filename)`을 사용해보세요. 문자열인 경우에도 `anObj doString(s)`처럼 하면 됩니다. `anObj`은 평가문맥(EvaluationContext)이 되므로 생략하면 전역문맥인 `Lobby`로서 평가합니다. Lobby 객체는 프로그램 실행에 관련된 인자나 실행경로등에 대한 처리를 도와줍니다. ---- == 문법 == 보통 다른 언어에서 문법에 대한 부분은 항상 이런 문서의 뒷쪽에 부록으로 위치하지만 반대로 그 페이지수는 엄청나게 많습니다. 하지만 IoLanguage에서는 그런 커다랗고 복잡한 문법이 없습니다. 대신에 이 부분이 가장 앞에 위치하는 이유는 몇가지 단순하고 완전한 문법규칙을 이해한다면 이후에 나오는 모든 부분들이 이 단순한 문법에 기초한 것이라는 점이 명확하기 때문입니다. === 메시지 전달 === 사실 문법을 나열하려고 해도 힘든게 문법이라고 부를만한게 없습니다. IoLanguage에는 오직 메시지전달만이 있습니다. 나머지는 상수를 표현하고, 주석을 표시하는 정도입니다. 메시지 전달을 간략하게 표현해보면 다음과 같습니다. * 메시지 이름 : 항상 메시지 이름은 반드시 적어줘야 합니다. (필수) * 대상 객체 : 메시지를 받을 객체를 메시지 이름 앞에 적어줍니다. 선택적으로 이 객체를 지정하지 않으면 `self`나 `thisContext` 같은 특별한 의미를 갖는 객체가 수신합니다. * 인자 : 선택적입니다. 대신 메서드가 필요로 하는만큼 인자를 지정해야 합니다. 인자가 하나보다 많을 경우에는 인자들 끼리 쉼표(,)로 분리를 하고 인자의 목록을 괄호로 감싸줍니다. 어떠한 인자도 없는 경우에는 JavaLanguage, PythonLanguage에서처럼 빈 괄호를 쓰는 것이 아니라 괄호 부분을 생략합니다. 또한 특별한 메서드 이름인 경우, 다시말해 연산자인 경우에는 괄호를 생략하고 인자만을 적어줄 수 있습니다. 다음은 위의 규칙을 적용한 몇가지 예입니다. {{{ 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 }}} === 슬롯? 메서드? 메시지? === 조금 뜬금 없지만, 간단하게 몇가지 개념을 소개해 드리고 싶습니다. 알고 있는 것이더라도 혹시나 제가 말하고자 하는 것과 다를 수 있으므로 읽어보며 확인해주시기 바랍니다. * 슬롯(slot) : 객체의 데이터와 메서드를 이름으로서 참조하기 위한 방법. 이름그대로 객체는 슬롯의 목록을 갖고, 각각의 슬롯은 하나의 다른 객체에 대한 참조입니다. 이 참조는 메서드객체이거나 그밖에 다른 객체의 속성으로서 사용되는 객체일 수 있습니다. * 메서드(method) : 객체지향에서 함수(function)과 유사한 코드의 단위를 나타내는 말로서, 메서드라고 하는 것은 주로 어떤 객체와 연관되어서 연관된 객체에 대한 작업을 하는 루틴을 말합니다. * 메시지(message) : 객체지향에서 메시지는 객체들간의 통신방법을 말합니다. 객체들은 서로 메시지를 주고 받고, 메시지의 응답을 받음으로서 FunctionalProgramming과 같은 패러다임에서 프로그램의 모든 부분들이 함수인 것처럼 객체지향에서는 프로그램의 모든 구성요소들이 객체이며 이 객체들은 서로 메시지를 주고 받음으로서 프로그램을 이룹니다. 정리해보면, IoLanguage에서 어떤 객체 "anObj"에 어떤 메서드 "someMeth"을 실행시키는 작업은 "anObj"에 "someMeth"라는 메시지와 그에 필요한 인자들을 전달하고 이를 기반으로 "anObj"의 슬롯목록에서 "someMeth"에 해당하는 객체를 얻어서 이에 인자를 넘겨주고 실행합니다. 왜 C++Language에서처럼 직접 ''vtable''을 갖고 있다가 이를 이용해서 함수포인터를 찾아 점프하지 않느냐고 한다면 이렇게 메시지를 전달하는 방법이 훨씬 유연하기 때문이라고 밖에 말씀드릴수밖에 없습니다. 실행시간에 어떤 객체에 새로운 슬롯을 추가하거나 이후에 설명할 Protos에 변경을 해서 기존 객체들의 행동양식을 바꾸는 것이 얼마든지 가능하고 크게 언어구현을 확장하거나 직교성이 떨어지는 방법으로 언어의 내부구현을 파고들거나 괴상한 방법을 사용하지 않고 이러한 것을 얼마든지 구현할 수 있기 때문입니다. 이 문서에서 설명할 IoLanguage의 객체시스템이나 그밖에 다른 부분들을 더 읽어보신다면 왜 메시지전달이 유용한지를 이해하실겁니다. === 연산자 === 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의 특징적인 개념들은 다른 언어에서 찾을 수 있는 복잡하고 어려웠던 개념들을 잘 통합하고 있습니다. || '''concept''' || '''unifies''' || || 프로토타입(prototypes) || 객체, 클래스, 이름공간(namespace), || || || 지역변수(locals), 함수 || || 메시지(messages) || 연산자(operators), 호출(calls), 대입(assignment), || || || 변수접근(variable accesses) || || blocks with || 메서드, 클로져(closures), 함수 || || assignable scope || || === 프로토타입 === 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" 슬롯은 다음의 슬롯들을 포함하는 객체로 설정이됩니다. || '''slot''' || '''references''' || || ''sender'' || locals object of caller || || ''message'' || message used to call this method/block || || ''activated'' || this activated method/block || || ''slotContext'' || context in which slot was found || || ''target'' || current object || ==== 가변길이인자 ==== "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