메모

Small Quine

print(call.function.body)

이름 주머니

  • 첫 구현: Comedy
  • Standard library: Anthology
  • RubyGems-like repository service: Cliche
  • Unit testing framework: Rehearsal
  • Build utility: Ticket
  • Mock object framework: Nuance

고민

print

print("hello world")

equals to:

stdout.write("hello world\n")

Scope Object

(.).identifier

Variable Arguments

>>> f := \(a, b, [args], {kwargs}) {
...   [a, b, args, kwargs]
... }
==> \(a, b, [args], {kwargs}) { [a, b, args, kwargs] }
>>> f(1, 2, 3, 4, 5, c: 5, d: 6)
==> [1, 2, [3, 4, 5], <context>(c: 5, d: 6)]
>>> f(d: 1, c: 2, b: 3, a: 4)
==> [4, 3, [], <context>(c: 2, d: 1)]

Asynchronous Call

>>> some-function := \(str: <string>) { sleep!(5000); str }
==> \(str: <string>) { sleep!(5000); str }
>>> future! := some-function.async("return value")
==> <future>(some-function, <argument-list>("return value"))
>>> future!()
!!! <not-finished-error>
>>> future!()
==> "return value"

future! is an instance of <future> and is function object. An instance of <future> is called then the result of the asynchronous call is returned if it is finished, or an exception rise.

Boolean Value As Function

true(then: true) = then
false(_: nil, else: false) = else

Usage:

>>> true(1, 2)
==> 1
>>> false(1, 2)
==> 2

Defining Constant

A := 1
constant: b := 1
  1. Capitalized identifiers are treated as constant like Ruby.
  2. There is the macro constant to define constant explicitly.

Reference implementation of the macro constant:

constant := macro {
    assignment := parameters[0]
    (assignment isa <definition>) or <exception>.rise!
    assignment.identifier.define(parameters[1](context), context, true)
}

<liberal-type>

  • <type>
    • <liberal-type>

Examples:

>>> <number-for-string> := <number> for <string>
==> <liberal-type>(class: <number>, for: <string>)
>>> <number-for-string> isa? <liberal-type>
==> true
>>> <number-for-string> is-type-of? 1
==> true
>>> <number-for-string> is-type-of? "abc"
==> true
>>> <number-for-string> adapt 1
==> "1"
>>> <number-for-string> adapt 'abc'
==> "abc"

Example 2:

say! := \(object: <object> for <string>) {
    io.stdout.write!(object + "\n")
}

say!: "Hello world!"
say!: 123

Exmaple 2 program prints the following result:

Hello World!
123

<function>.returns

리턴 도메인 표기.

<function>.returns := \(type: <type>) {
    {
        (value := self.apply(call.arguments)) isa? type else {
            <invalid-return-type-error> rise()
        }

        value
    }
}

사용은 아래처럼.

take-number := \(a) {
    a
} returns <number>

실제 상황.

>>> take-number(1)
==> 1
>>> <take-number>('string')
!!! <invalid-return-type-error>
  - lobby()
  - take-number()
  - (--anonymous-function--)()

Factorial Number

<integer>.prototype.factorial := <property>: { self.-(1).factorial * self }
1.factorial := 1

Using product instead of recursion is also available.

<enumerable>.prototype.product := {
    self.inject: (first, second) { first * second }
}

<integer>.prototype.factorial := <property>: { 1..self.product() }

<property>(deterministically: true) (Cached Property)

>>> a.a = <property>(<number>.random(), deterministically: false)   # or <property>(<number>.random())
==> <property>(...)
>>> a.b = <property>(<number>.random(), deterministically: true)
==> <property>(...)
>>> a.a
==> 123
>>> a.b   # not cached yet
==> 456
>>> a.a   # regenerated random number
==> 789
>>> a.b   # cached number
==> 456

Random number

>>> <boolean>.get-any()
==> true
>>> 5..10.get-any()
==> 8
>>> 1..10.shuffle()
==> [6, 2, 4, 7, 9, 1, 3, 10, 5, 8]

Expression Literal

>>> :(1 + 2)
==> :(1.+(2))
>>> :(1 + 2).class
==> <call>
>>> :((1) + (2))
==> :(1.+(2))
>>> :symbol
==> :symbol
>>> :symbol.class
==> <identifier>

Expression.prototype.define

AttributeAccess.prototype.define := (rvalue: Expression, constant: Boolean) {
    ~ receiver(context: ..) setAttribute(name: ~ name, value: rvalue(context: ..), constant: constant)
}

Call.prototype.define := { InavalidDefinitionException rise 'Cannot define function call' }

대입문은 모두 Expression.prototype.define문으로 변환되어 해석되어진다. Expression은 호출 연산자를 통해 평가할 수 있다.

기본 인수의 평가 시점

defaultValue = 1
lateBinding = (a = defaultValue) { a }
staticBinding = (a := defaultValue) { a }
defaultValue = 2

위 상태에서 lateBinding()2를 반환, staticBinding()1을 반환.

도메인 지정을 했을 경우엔?

defaultValue = 1
lateBinding = (a(defaultValue): Number) { a }
staticBinding = (a[defaultValue]:Number) { a }

SAX

xml = SAX(
    procesingInstruction: (attributes: Map) { ... }
    text: (text: String) { ... },
    elementBegin: (tag: String, attributes: Map) { ... },
    elementEnd: (tag: String) { ... },
    comment: (comment: String) { ... },
)

xml("<xml>xml string</xml>")
xml(stream: File("xml_file.xml"))
xml(file: "xml_file.xml")

<predicate>s about <list>

<list>.of(type: <type>) := if(
  type is <object>,
  <list>,
  <predicate>: \(instance: <list>) {
    instance are-all { _ isa type }
  }
)

<type>.in := \(container: <container>) {
    <predicate>: { container contains _ }
}

Examples:

>>> <number-list> := <list> of <number>
==> <list>.of(<number>)
>>> [1, 2, 3] isa <number-list>
==> true
>>> [1, 2, "3"] isa <number-list>
==> false
>>> <-t> := <object> in [1, "2"]
==> <object> in [1, "2"]
>>> 1 isa <-t>
==> true
>>> 2 isa <-t>
==> false

CompositeFunction

Callable.generictype.<- := (Callable functor) {
    CompositeFunction(functor, ~)
}

CompositeFunction := Class(
    functors::{
        wrong = functors.indexOf(reversely(Callable.isDomainOf))
        wrong.as(Boolean) then {
            WrongDomainParameterException.rise(wrong, Callable)
        }

        # save funtors ...
    },

    <-: (Callable functor) {
        ~.class.apply(~.as(Array) + [functor])
    }
).implement(Cursor)

Callable.makeClassCallable(CompositeFunction, arguments::{
    ~.map: (functor) {
        arguments = [functor.apply(arguments)]
    }

    arguments[0]
})

사용.

countIf := filter <- :length
countIf({ '3' in _.as(String) }, 1..999)
# 1부터 999까지 중에서 십진수 표기시 아무 자리수에든 “3”이 들어가는 숫자의 갯수 구하기.

countIf는 아래와 같다.

countIf := args::{ filter(:length.apply(args)) }

-> 대신 <-를 사용한 이유는 두 가지이다.

  1. a <- b <- c라고 했을 때 a(b(c(_)))처럼 합성되기 때문. 순서가 반대라면 헷갈릴 염려가 있다.
  2. ->는 이미 쌍(Pair) 객체를 만들기 위해 사용된다. (a -> b == Pair(a, b))

Concurrency

functor()
functor.delay[10]()
functor.periodical[10]()

STM

see http://agbird.egloos.com/3646893.

Higher-order function

함수 이름을 동사형으로 짓듯, 함수에 작용하는 함수―고차 함수(higher-order function)의 이름은 부사형으로 짓는다. 왜냐? 동사를 꾸며주듯, 함수를 꾸며주기 때문이다.

>>> isOkay?()
==> true
>>> reversely(isOkay?)()
==> false

Symbol isa SubclassOf[Callable]

>>> Symbol isa SubclassOf[Callable]
==> true
>>> l = [1, 2, 3, 4]
==> [1, 2, 3, 4]
>>> l.size
==> 4
>>> :size(l)
==> 4

따로 pluck이 필요 없다. map에다 Symbol 인스턴스를 집어 넣으면 똑같이 작동할테니까.

>>> "따로 pluck이 필요 없다. map에다 Symbol 인스턴스를 집어 넣으면 똑같이 작동할테니까.".words.map(:size)
==> [2, 6, 2, 3, 5, 6, 5, 2, 3, 3, 7]

Domain

  • Domain (is a Protocol)
    • Interface (is a Class)
    • Protocol (is a Class)
    • AbstractClass (is a Class)
      • Singleton (is a Class)
      • Class (is a Class)
    • Template (is a Template[Class])

Predicate

>>> OddInteger := Predicate: (object: Number) {
...     object % 2 == 1
... }
==> OddInteger
>>> [5 isa OddInteger, 4 isa OddInteger]
==> [true, false]

Protocol

Protocol은 Java에서의 인터페이스와 같은 역할을 한다.

>>> Domain := Protocol(
...     isDomainOf: Callable[Object: :object]
... )

Template

>>> OneOf := Template[instance: Protocol]: (object: Container) {
...     object in container
... }
==> OneOf
>>> 1 in [1, 2, 3, 4]
==> true
>>> 1 isa OneOf1, 2, 3, 4
==> true

Pair

>>> Pair(1, 2)
==> 1 -> 2
>>> (1 -> 2).class
==> Pair

클래스간 변환

Number("123")
"123".asNumber()
String(123)
123.asString()

ClassA의 인스턴스를 ClassB의 인스턴스로 변환하는 것에 대해,

  1. ClassB(a)
  2. a.asClassB()

차라리 클래스간 변환을 중간에서 매개해주는 모듈이 있는 건 어떨까?

Object as := (class) { Juggling[~.class, class](~) }
Juggling[ClassA -> ClassB] := (a: ClassA) { ClassB(...) }

a.as(ClassB)

A -> BPair(A, B). 즉, Object -> := (to) { Pair(~, to) }.

음수 표현

어떻게 해야 하나?

>>> -(1)
==> -(1)
>>> writeln: -(1)
-1
==> nil

인자 기본값 (default parameter)

(argName: Domain, argName(defaultValue): Domain, argName(defaultValue), argName = defaultValue) {
    # ...
}

도메인 표기가 없을 때는 대입 연산자 := 혹은 =으로 표기 가능.

Fibonacci number

0.fibonacci := 0
1.fibonacci := 1

Number.prototype.fibonacci := Property: () {
    (~ - 1).fibonacci + (~ - 2).fibonacci
}

Infinite Loop

Number.prototype.times = ...
Infinity.prototype.times = ...

Protocol Multiply

RisableComparer := Risable & Comparer
isARisableComparer? = (o) { o isa RisableComparer }

# equals:

isARisableComparer? = (o) { o.isa(Risable) and o.isa(Comparer) }

Lazy Protocol

OddInteger := LazyProtocol: (number: Number) {
    number % 2 > 0
}

EvenInteger := LazyProtocol: (number: Number) {
    number % 2 < 1
}

>>> 1 isa EvenInteger
==> false
>>> 2 isa EvenInteger
==> true
>>> 3 isa OddInteger
==> true
>>> 4 isa OddInteger
==> false
>>> 3.14 isa OddInteger
==> false

ContextSwitch

object.contextSwitch: {
    a() # same as object.a()
}

{ a() }.withContext(object)()

AttributeProxy

a = property(get, set)
b = shouldBe(type)

부분 적용 (Partial Application)

+5 = +.partial(5)
# eq:
+5 = (x):{ +(5, x) }

바인딩 (Binding)

# order isa Callable
orderBlackTea = order.bind(good: :blackTea)

부분 적용은 인자 위치를 기준으로, 바인딩은 인자 이름(키워드)을 기준으로 고정시킨다.

주석 (Comment)

Metaphor는 한줄 주석과 여러줄 주석을 모두 지원한다.

  • # 뒤에 오는 모든 문자는 다음 줄을 만날 때까지 무시된다.
  • #{ 뒤에 오는 모든 문자는 }# 문자열을 만날 때까지 무시된다.

단, 문자열 리터럴 안쪽에서는 무시된다.

표현식 (Expression)

Metaphor의 표현식은 크게 다섯 종류가 있다. Metaphor의 문법 요소들 중 표현식이 아닌 문장은 주석밖에 없다. 모든 표현식은 괄호로 감싸질 수 있으며, 그 괄호는 감싼 표현식이 반환하는 객체를 그대로 반환한다.

>>> (1)
==> 1
>>> ((123))
==> 123
>>> (((((((((((((((((("홍민희"))))))))))))))))))
==> "홍민희"

식별자 (Identifier)

식별자는 마침표(.)와 공백 문자를 포함하지 않는 문자열이며, 대부분의 문자를 포함할 수 있다. 첫 글자로 숫자는 올 수 없다.

Object
abc123
가나다
writeln!
isTrue?
+
--
/

위에서 말한 대부분의 문자에는 괄호((, ), [, ], {, })와 따옴표(', "), 이콜(=), 콜론(:)과 세미콜론(;)이 제외된다.

속성 접근 (Attribute Access)

표현식 뒤에 마침표(.)가 오고, 식별자가 잇다르면 그것은 해당 표현식이 평가되어 반환하는 객체에서 잇다른 식별자의 이름을 가진 속성을 반환한다. 다른 언어에서는 체이닝(chaining)이라고도 한다.

expression.identifier
Object.id
123.class
list(1, 2, 3).length

대입 (Assignment)

식별자나 속성 접근 표현식 뒤에 =가 이어지면 대입이다.

constants = Object()

선언 (Declarement)

식별자나 속성 접근 표현식 뒤에 :=가 이어지면 선언이다.

constants.pi := 3.14

선언은 쉽게 말해 상수 대입.

호출 (Call)

표현식 뒤에 괄호가 잇다르면 그 표현식을 호출하는 표현식이 된다. 괄호 안쪽에는 키워드와 인자가 들어갈 수 있다. 키워드는 필수적이지 않다.

expression(argument, argument, keyword: argument, keyword2: argument)
expression.apply(Argument.fromList(argument] * 4, [:keyword, :keyword2))

호출은 약식 문법도 제공된다. 표현식 뒤에 괄호를 열고 닫는 대신, 콜론(:)을 사용하면 된다. 다만, 약식 문법은 키워드 인자를 사용할 수 없다.

expression: argument, argument, argument

아래와 같은 경우에는 어떻게 해석할까?

function(no_keyword_but_function: argument)

no_keyword_but_function: arugment는 당연히 약식 호출 문법으로 해석되지 않고, 키워드와 인자값으로 해석된다. no_keyword_but_function: arugment를 함수 호출로 해석하도록 만들고 싶다면 아래와 같이 해야 한다.

function(no_keyword_but_function(argument))
function((no_keyword_but_function: argument))

다만 아래와 같은 경우는 조금 다르다.

function(this.is.function: argument)

this.is.function은 도저히 키워드로 볼 수 없기 때문에(키워드는 단일한 식별자여야 한다), 표현식으로 처리 되고, 결국 저것은 하나의 인자인 약식 호출로 해석된다.

약식 호출은 C/C++ 스타일의 “블럭 문장”과 같은 타이포그래피를 위해 종종 사용된다.

list(1, 2, 3, 4).map: {
    writeln(_)
}

속성 접근과 호출을 합친 구문도 있다. 단, 인자는 항상 한 개를 받는다. 인자가 없거나, 둘 이상일 때는 아래와 같은 구문을 사용할 수 없다.

object attribute argument

위 코드는 아래와 동일한 작동을 한다.

object.attribute(argument)

Metaphor에는 연산자(operator)가 따로 없지만, 위 구문을 이용해서 다른 언어에서 말하는 이항 연산자(binary operator)를 흉내낼 수 있다.

>>> true and false
==> false
>>> true.and(false)
==> false

>>> 1 + 2 * 3
==> 9
>>> 1.+(2).*(3)
==> 9

당연히 위 코드에서의 * 메서드가 + 메서드보다 우선순위가 높지는 않기 때문에, + 메서드부터 호출되게 된다. Metaphor는 평가 우선순위에 있어서 공정하기 때문에, 다른 언어와 달리 괄호로 감싸주지 않는 이상 *가 먼저 평가될 일은 없다. 애초에 +*는 단지 메서드일 뿐 연산자도 아니다.

>>> 1 + (2 * 3)
==> 7

Smalltalk를 생각하면 되겠다.

리터럴 (Literal)

숫자 리터럴 (Number Literal)

숫자 리터럴은 0으로 시작할 경우 8진수, 0x로 시작할 경우 16진수이며, 그 외에는 10진수로 해석된다.

123
123.456
0xFF
077

심볼 리터럴 (Symbol Literal)

심볼은 식별자를 담는 자료형으로, 콜론(:) 뒤에 식별자를 잇는 형태이다.

:object
:abc123

문자열 리터럴 (String Literal)

문자열은 컴퓨터에서 표현 가능한 모든 문자들의 순차열을 뜻한다. 큰 따옴표(") 둘 사이에 넣거나, 작은 따옴표(') 둘 사이에 넣으면 문자열 객체로 평가된다.

"hello world"
'가나다라 마바사'
"a 'single quote' in double quote"
"a \"double quote\" in double quote"
'a "double quote" in double quote'
'a \'single quote\' in single quote'

"string
literal
of
many\nlines"

큰 따옴표 리터럴과 달리 작은 따옴표 리터럴은 작은 따옴표(\')와 역슬래시(\\) 문자 이스케이프만 가지고 있다. 따라서 줄바꿈(\n)이나 탭(\t) 문자 이스케이프는 큰 따옴표 리터럴에서만 사용 가능하다.

'this object does not contain new line charater.\n'

리터럴에는 직접 문자 코드 포인트를 숫자로 입력할 수 있다. 역슬래시 뒤에 16진수로 표기 가능하다.

"\68\65\6c\6c\6f\2c\20"     :: "hello, "
'\4d\65\74\61\70\68\6f\72'  :: "Metaphor"

Object Model

Metaphor’s object model is basically similar to class-based object.

Attributes

The object is a set of attributes. Access attributes is fundamentally transparent.

-(123).absolute
"道可道 非常道. 名可名 非常名.".size
(5 * 4).odd

Instantiation

In order to instantiate the class, should call the class object.

>>> <range>.class
==> <class>
>>> <range>(1, 5)
==> 1..5

The class clones its prototype object when its constructor is called, then the cloned object is referenced to self in the constructor function body.

<range>(1, 5)
<range>.initialize.bind(<range>.prototype.clone())(1, 5)
changed April 13