posted by 아겔-_- 2009.04.11 16:19
이전 '다양한 클래스 선언'에서 믹스인에 대해서 설명했었음.

믹스인은 유니온과 유사하지만, 어떤 범주가 되는 클래스에 계속해서 다른 클래스를 추가해넣을수있음.

팩터는 단일상속이지만, 어떤 객체나 클래스는 다른 클래스에 속하거나 할 수 있는 '관계'를 지정할있으므로 이런것이 가능.

프로토콜 슬롯은 이러한 믹스인을 이용하여 다른 언어에서 프로토콜이나 인터페이스처럼 특정한 '규약'을 클래스가 준수함을 표현.

  1. MIXIN:으로 믹스인 클래스를 선언한다.
  2. SLOT: 선언으로 공유할 슬롯을 선언한다.
  3. 믹스인을 구현할 클래스에서 SLOT: 선언에 해당하는 슬롯들을 구현한다
  4. 믹스인을 구현한 클래스를 INSTANCE:을 이용하여 믹스인에 추가한다.

은근히 이런식으로 구현한 클래스들이 팩터에 많은듯. (시퀀스 프로토콜, growable 프로토콜...)

그리고 믹스인과 슬롯의 구분이 분리된듯한데 이럴때는 사실 PROTOCOL: 선언을 통해서 프로토콜의 이름과 그 프로토콜이 구현하기를 원하는 word들을 나열하고 이를 구현하는것으로 대신할수있다. (자세한것은 delegates을 참고)



신고
posted by 아겔-_- 2009.04.11 16:01
객체의 동일성 판별은 기본적으로 다음의 워드들을 이용한다.

  • eq? : 두 객체가 완전히 같은 객체의 참조인지를 판별
  • = : 두 객체가 같은 클래스, 같은 슬롯값을 갖는지를 갖고 판별 (실제 참조가 다르더라도)

그리고 number 클래스의 값들은 서로 다른 범위(실수, 정수...)에 속하더라도 같은 값인지를 판별하려면 "number="을 이용한다.

또한, 직접 객체의 유일성을 구현할 경우에는 해당 클래스에 대한 equals? 제네릭을 구현하면된다.

(객체의 유일성을 판별하는 테스트를 작성하고 싶으면 dup, clone 워드를 이용해서 참조의 복사, 객체의 복제를 하면된다.)


어떤 클래스의 인스턴스들이 값에 의해서 서로 대소관계를 가지면 "linear order protocol"을 구현하면 된다.

기본적으로 두 객체에 대해서 <=> 워드를 적용하면 대소판별의 결과를 (좌변을 기준으로) 심볼로 되돌립니다. (+lt+, +gt+, +eq+)

어떤 사용자 클래스가 대소비교를 적용해야할 경우에는 (정렬등을 적용하려면 그래야겠죠?) 단순히 <=> 제네릭을 구현합니다.

프로토콜에 대해서는 이후에 다시 설명하겠습니다.
신고
posted by 아겔-_- 2009.04.06 00:52
팩터는 단순히 상속, 구현이외에 클래스간의 관계를 집합논리에 따라 설정할수있음. 다른 언어의 객체시스템들과는 전혀 다르면서도 매우 유연한 객체시스템을 제공.

우선 가볍게 간단한 싱글턴부터.

( scratchpad ) SINGLETON: foobar
( scratchpad ) GENERIC: do-foo ( obj -- )
( scratchpad ) M: foobar do-foo ( obj -- ) drop "THIS IS FOOBAR!" . ;
( scratchpad ) foobar do-foo
"THIS IS FOOBAR!"
"foobar" 클래스를 만들고, 싱글턴이니까 인스턴스에 대한 정의도 필요없이 그냥 클래스 자체에 메서드를 붙이는것을 볼수있음.

predicate, union, intersection은 기존의 클래스에 다른 '범주'를 추가하는것으로 생각할수있음. 이들로 선언한것들도 일반적인 클래스와 동일하게 취급.

predicate은 기존의 클래스로부터 특정 조건을 만족하는 인스턴스를 새로운 'predicate class'에 속하는것으로.

( scratchpad ) \ even? see
IN: math
: even? ( n -- ? ) 1 bitand zero? ;

( scratchpad ) PREDICATE: even-p < number even? ;
( scratchpad ) 4 even-p? .
t
( scratchpad ) 9 even-p? .
f
"PREDICATE: class < superclass predicate... ;"로 선언했음. "even?" 워드를 predicate로 사용하고 number 클래스의 인스턴스들 중에서 짝수인것들을 even-p 클래스로 선언했음. 그리고 간단히 even-p 클래스가 선언되면서 자동적으로 생긴 "even-p?" 워드를 이용하여 클래스에 속하는지를 판별.

여기서 볼수있듯이 단순히 type-hierarchy에 속할때만이 아니라, 동적으로 어떤 인스턴스가 어떤 클래스에 속하는지 판별이 가능하다는것을 알수있음.

union은 클래스들을 하나로 묶어 공통된 클래스에 속하는것으로 이미 존재하는 클래스들에 대해 적용한다.

( scratchpad ) SINGLETON: apple
( scratchpad ) SINGLETON: strawberry
( scratchpad ) SINGLETON: pear
( scratchpad ) SINGLETON: carrot
( scratchpad ) UNION: fruits apple pear ;
( scratchpad ) UNION: vegetables strawberry carrot ;
( scratchpad ) strawberry fruits? .
f
( scratchpad ) strawberry vegetables? .
t
( scratchpad ) apple fruits? .
t
( scratchpad ) apple vegetables? .
f



mixin은 union과 같이 클래스들을 하나의 범주로 묶는데, 이후에 계속해서 해당 범주에 클래스들을 추가해나가는것이 가능하다. (다른 언어의 '믹스인'들과 좀 다름)

( scratchpad ) MIXIN: talking
( scratchpad ) MIXIN: flying
( scratchpad ) SINGLETON: dragon
( scratchpad ) INSTANCE: dragon talking
( scratchpad ) INSTANCE: dragon flying
( scratchpad ) SINGLETON: beaver
( scratchpad ) INSTANCE: beaver talking
( scratchpad ) beaver flying? .
f
( scratchpad ) beaver talking? .
t
( scratchpad ) dragon [ flying? ] [ talking? ] bi
--- Data stack:
t
t

정말 다른 언어에서 믹스인처럼 공통적인 behavior을 공유하고자 한다면, 단순히 MIXIN클래스에 메서드를 붙이는것으로 똑같이 할수있음.


intersection은 이렇게 유니온이나 predicate등으로 선언한 클래스를 어떤 '조건'이나 범주로 보았을때 그러한 조건들을 모두 충족하는 '교집합'에 속하는 인스턴스의 클래스를 선언할때 사용함.

( scratchpad ) SINGLETON: male
( scratchpad ) SINGLETON: female
( scratchpad ) UNION: gender male female ;
( scratchpad ) TUPLE: person age sex ;
( scratchpad ) PREDICATE: young-person < person age>> 30 < ;
( scratchpad ) PREDICATE: woman < person sex>> female = ;
( scratchpad ) INTERSECTION: young-girl young-person woman ;
( scratchpad ) 17 female person boa
--- Data stack:
T{ person f 17 female }
( scratchpad ) young-girl? .
t
( scratchpad ) 28 male person boa young-girl? .
f







신고
posted by 아겔-_- 2009.03.30 23:21
팩터에서 튜플 클래스를 선언하고 속성을 붙이는것까지 설명했다.
이제는 객체에 메서드를 붙여줄 시간이다. (객체지향에서 객체는 속성과 행위로 표현하니까.)

팩터의 타입시스템 자체는 하스켈이나 OCaml의 그것들과 유사한데, 메서드나 슬롯에 대한 부분은 커먼리습의 CLOS와 유사하다.

무슨말인가하면, 어떤 객체에 대해서 메서드를 붙이는것과는 조금 다르다는말이다.
예를 들어서 우리는 메서드의 polymorphism에 대해서 다음에 익숙해있다.

someCircle.area();
someRectangle.area();
단순히 이름이 같다는것정도.

팩터는 그 언어의 특색(nature)에 따라 객체의 메서드도 사실은 그냥 일반적인 워드와 같다.
리습에서 객체의 메서드는 그냥 일반적인 함수호출과 같고, 하스켈에서 어떤 타입에 대한 함수는 일반적인 함수와 같다.
단지 그 인자중 하나가 그 클래스/타입에 속할때 원하는 메서드의 코드를 선택하는것이 다르다.

팩터에는 별도로 연산자가 없다. 모두 워드다. 마찬가지로 메서드를 만드는것만으로 기존의 연산자를 재정의하는것이 가능하다.

팩터나 CLOS에서 이런 "메서드의 이름"을 규정하는것을 제네릭(generic)이라고한다. 그리고 다음처럼 선언한다.

( scratchpad ) GENERIC: area
( scratchpad ) GENERIC: perimeter ( obj -- perimeter )
단순히 제네릭의 이름을 주거나, 제네릭의 stack effect declaration을 적어줘도 좋다.

이렇게하면, 객체별로 이 제네릭에 대한 메서드를 붙일 준비가 끝났다.

일단 넓이를 구하는 메서드들은 다음처럼 붙여봤다.

( scratchpad ) USE: math.constants
( scratchpad ) pi .
3.141592653589793
( scratchpad ) pi sq .
9.869604401089358
( scratchpad ) M: circle area r>> sq pi * ;
( scratchpad ) 9 <circle> area .
254.4690049407732
( scratchpad ) M: rectangle area [ w>> ] [ h>> ] bi * ;
( scratchpad ) 3 4 <rectangle> area .
12

"M: 클래스 제네릭 ..." 이렇게 시작해서 일반적인 워드와 같이 그 구현을 하는것을 알 수 있다.

다수개의 인자에 대해서도 제네릭을 다르게 만들어놓고서 하면 되는데, 이때 주의점은 GENERIC: 선언은 반드시 스택의 맨 위가 해당 클래스의 인스턴스여야 한다는것이다. 특정 클래스를 다른 위치에 두고자 한다면 "GENERIC#" 선언을 이용한다.

또한, HOOK:을 통해서 multiple dispatch에서처럼 다수의 인자의 타입에 따른 메서드 dispatch을 구현이 가능하다.
(그 이외에도 MATH:등을 통해서 수학연산자에서처럼 양변의 타입에 따른 dispatch도 가능하다)

또한 상속 받은 클래스의 메서드에서 상위 메서드를 호출하려면 super등을 통하지 않고 단순히 "call-next-method"을 호출한다.
(circle의 area에서 shape의 area을 호출하고자 한다던지...)

마지막으로 메서드의 인자가 많거나 할때도, locals의 M::을 이용하여 "::"와 같이 인자들에 이름을 붙이거나 할수있다.

( scratchpad ) M: circle perimeter r>> pi * 2 * ;
( scratchpad ) 9 <circle> perimeter .
56.54866776461628
( scratchpad ) M: rectangle perimeter [ w>> 2 * ] [ h>> 2 * ] bi + ;
( scratchpad ) 3 4 <rectangle> perimeter .
14








신고
posted by 아겔-_- 2009.03.30 00:18
팩터에서 모든 값은 어떤 클래스의 인스턴스.
( scratchpad ) 1 class .
fixnum
( scratchpad ) 1 class class? .
t
( scratchpad ) 1 fixnum instance? .
t
( scratchpad ) 1 number instance? .
t
( scratchpad ) 1 string instance? .
f
"class" 워드를 통해서 어떤 값의 클래스를 보거나, 어떤 객체가 어떤 클래스의 인스턴스인지를 실행시간에 확인이 가능함을 보임.

팩터에서 클래스는 크게 다음과 같이 나뉜다.

  • 내장 클래스 : builtin class, 프리미티브로 vm자체에서 구현하는 값에 대한 클래스.
  • union, intersection class : 기존의 클래스들의 합집합, 혹은 클래스 워드의 교집합이 일치하는 클래스들을 묶는 클래스
  • predicate class : 어떤 객체가 'predicate'을 충족하는지에 따라 동적으로 수용여부가 결정되는 '값에 의한' 클래스
  • singleton class
  • 믹스인 클래스 : mixin class, union class와 유사하게 클래스들을 묶지만, 계속해서 그 집합에 사용자정의 클래스를 더해나갈수있는 클래스
다른 객체지향언어와는 다르고, 차라리 Algebraic Data Type와 유사함을 알수있다. (그리고 더 디테일한 조정이 가능하다)

이들 클래스의 대부분은 이 시리즈에서 다룰것임.

어쨌든 사용자 정의 클래스 '튜플(Tuple)'이라고 부르고 다음처럼 선언한다.

TUPLE: shape ;

TUPLE: rectangle < shape w h ;

TUPLE: circle < shape r ;
단순히 아무런 속성도 없는 "shape" 클래스를 선언하고 이를 상속하는 rectangle, circle 클래스를 선언. 각각의 속성은 w, h, 그리고 r.

이 이름만으로는 기본적으로 인스턴스가 아니다. (인스턴스를 만드는 방법이 별도로 존재한다.)

( scratchpad ) shape shape instance? .
f
( scratchpad ) shape boa shape instance? .
t
"boa"라는 워드를 이용해서 인스턴스를 생성했음. ("by order of arguments") BOA 생성자는 그 슬롯(속성)의 순서대로 스택에서 취하여 인스턴스를 생성.

생성자는 컨벤션에 의해서 <circle>, <rectangle>, <shape>와 같이 표현.

"C:"워드를 통해서 BOA 컨스트럭터를 자동으로 선언할수있다.

( scratchpad ) C: <circle> circle
( scratchpad ) C: <rectangle> rectangle
( scratchpad ) 9 <circle> .
T{ circle { r 9 } }
( scratchpad ) 3 4 <rectangle> .
T{ rectangle { w 3 } { h 4 } }
( scratchpad )
<circle>, <rectangle>을 일반적인 워드와 같이 이용함을 알수있음.

추가적으로 속성, 즉, 슬롯에 타입제한이나 읽기전용슬롯, 초기값을 지정하는것도 가능하다. (이후에 슬롯만을 다룰때 다루겠다.)

기본 생성자를 이렇게 만드는것이고, 추가적인 생성자를 만들려면 단순히 <circle>와 같은 이름의 word을 선언하면 되겠다. (이미 <circle>이 있다면 컨벤션에 의해서 <circle>*와 같은 이름으로도 되겠죠)

인스턴스의 슬롯들은 "slot accessors"을 통해서 접근이 가능.

( scratchpad ) 3 4 <rectangle> 5 >>h .
T{ rectangle { w 3 } { h 5 } }
( scratchpad ) 8 <circle> 9 >>r r>> .
9
이름이 "r"인 슬롯에 대해서 getter가 "r>>", setter가 ">>r"임을 볼수있다. 이들 accessor들은 자동적으로 생성된다.


마지막으로 클래스 선언과 함께 predicators도 함께 선언을 해주는데, 어떤 객체가 해당 클래스에 속하는지를 판별하는데 이용한다.

( scratchpad ) 9 <circle> shape? .
t
( scratchpad ) 9 <circle> circle? .
t
( scratchpad ) 9 <circle> rectangle? .
f


기본적인 클래스의 선언과 인스턴스의 생성, 그리고 속성에 대해서 살펴봤다. 이제 메서드를 붙이고 하는 방법에 대해서 살펴보고, 계속해서 디테일하고 다양한 부분을 살펴보도록 하겠다.



ps. 9서클을... 드디어 내가! 타이번!
신고
posted by 아겔-_- 2009.03.30 00:12

팩터에서 모든것은 객체다.

모든것이 객체라는 선언이 별다를것도 없다고 생각할 수 있지만, 요즘 세상에서 화두가 되는 몇가지를 생각해보자.

스몰톡, 루비, Io와 같은 언어들에서는 모든것이 객체다. 이런 언어에서 모든 primitve들도 모두 객체를 다루듯이 메시지를 보내고, 자유롭게 변수에 할달하고한다. 심지어 객체라고 생각하기 힘든것들도 객체로 표현한다. 코드, 코드블럭, 메서드, 클래스, 메타 클래스, 메시지, 심지어 코드의 실행상태(continuations)까지.

그러면 그렇지 않은것은 뭘까? 모든것이 객체가 아닌것? 그게뭐지? 그건 좀 아닌것 같은데. 조금 범위를 좁혀보면, "몇몇것은 객체가 아닌것"이라고 생각할수있겠다.

자바와 C#은 객체지향언어임에는 변함이 없겠지만 몇몇은 객체가 아니다.(혹은 아니었다.) 자바에서 "int x"와 "Integer x"은 서로 다르다. 컴파일러와 API적인 지원으로 갭을 메우지만, 여전히 서로 다른것으로 취급함을 주의해야하고 boxing/unboxing이 '세심한 배려'라고 그냥 생각하며 사용하는게 속편하다.

"모든것이 객체"라는 명제가 객체지향에만 국한되는것은 아닌것 같다.(조금 뜬금없을수도있고, 결벽적으로 접근한다면 다른 영역에 해당할수도있겠지만)

값(value)을 다루는 방법에 대한것이기도 하다고 생각한다.

예를 들어, 스킴(Scheme)에서 객체지향에 대한 지원이 없더라도(클래스를 만들고, 인스턴스를 만들고 메서드를 구현하고...), 분명 그 언어에서는 다양한 타입(클래스)의 객체를 지원한다.(벡터, 문자, 정수, 함수...) 그리고 이런 객체(값)들은 모두 하나의 방법을 통해서 변수에 대입할수있고 모두 똑같은 방식으로 그 변수에 접근할수있음을 생각해보자. (굳이 스킴이 아니라고 하더라도.)

서론이 길었다. 팩터에서는 양쪽 모두에 해당한다.

모든값은 어떤 클래스에 속하고, 상속이 가능하며, 추가적인 메서드를 붙이거나 할 수 있는 '객체'이고, 모든 객체들은 동일한 방법으로 접근이 가능하다.




신고
posted by 아겔-_- 2009.02.28 13:27
팩터는 Self, CLOS의 영향을 받는 자체적인 객체시스템을 제공한답니다. 

매우 단순하고 유연한 독특한 시스템으로 팩터의 다른 vocab들처럼 불필요한 부분 때문에 그저 몸집만 크고 잘 이용하지도 않는 부분 때문에 사용자를 괴롭히지도 않습니다.

일단 몇가지 특징을 살펴보면요. (그리고 다음의 순으로 튜토리얼을 하나씩 적어볼려구요.)

  1. 모든것은 객체 : 팩터에서 모든것은 객체입니다. 특별히 의식하지 못했을수도 있지만, 간단한 프로그램을 짜는데 사용하는 모든것들이 사실은 객체의 메서드와 객체의 속성을 적용해야 가능합니다. 팩터에는 primitive타입과의 구분이 없고, 모든 값이 객체이고, 개별적인 클래스로 구분할수있습니다. "모든것이 객체"라는 선언을 지키기 위해서 뭔가의 구분을 지어야했던 언어들과는 달리 특별히 객체의 메서드를 호출하고 하는 문법을 익히지 않아도 팩터 언어 자체는 이미 객체지향적입니다.
  2. generic word, method : CLOS의 defgeneric, defmethod의 관계처럼 제네릭워드를 GENERIC:을 통해서 한번 선언하고, 각 클래스별로 개별적으로 메서드를 구현하는 방법으로 메서드를 정의합니다. 별도의 연산자 재정의나 복잡한 메서드 구현을 찾기 위한 방법이 필요치 않습니다.
  3. 튜플 & 슬롯 : 팩터의 객체인스턴스는 튜플(tuple)로 객체의 속성을 표현하는 슬롯(slot)을 갖습니다. 이는 Self, Io에서와 유사한 방식으로 동작하고, 정말 유연합니다. (복잡할 필요도 없이 유연하죠.)
  4. 단일상속 : 여러 클래스로부터의 상속을 지원하지 않습니다. 하지만, 단순히 is-a관계를 형성하기 위해서는 더 현명한 방법을 제공하고, mixin을 지원하여 오히려 다중상속을 지원할 필요가 없습니다.
  5. MixIn, Predicate Class, Union Class, Intersection Class ... : 스몰톡, 루비 등을 통해 널리 알려진 믹스인을 지원합니다. 그리고 독특한 predicate, union, intersection, singleton 클래스 선언을 지원하는데, 이들을 통해서 더 명쾌하게 객체간 관계를 표현할 수 있습니다.
  6. method combination : 팩터에서 어떤 객체의 메서드 호출은 일반적인 워드와 같이 표현합니다. (제네릭과 메서드 참조) 따라서 같은 워드라도 인자의 타입에 따라서 실제 수행하는 워드의 내용이 달라지는데, 이러한 dispatch을 수정할 수 있는 방법을 제공하는것이 method combination입니다. (예를 들면, 사칙연산자라던지 그런 조금 특이한 경우가 있겠죠?)
  7. Slots & Mirror : 객체의 속성은 모두 슬롯으로 표현이 가능하고, 객체을 실행시간에 살펴보는 기능(reflection이라고도 하죠)을 mirror을 통해서 제공합니다.
  8. 기타 : 그밖에 위에서 다루지 않은 세세한 주제들을 조금씩 적어볼 생각입니다.

다른 뭔가 재밌을수도 있을듯. ;-)




신고

티스토리 툴바