맨위로가기

위생 매크로

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

위생 매크로는 매크로 확장의 안전성을 보장하는 프로그래밍 개념으로, 변수 섀도잉과 같은 비위생적인 매크로의 문제점을 해결한다. 비위생적인 매크로는 매크로 내에서 변수가 외부 변수를 가려 예상치 못한 결과를 초래할 수 있으며, 해결책으로는 난독화, 읽기 시간 uninterned 심볼, 패키지, 리터럴 객체 등이 있다. 위생적인 매크로는 Scheme과 같은 언어에서 모든 식별자의 어휘적 범위를 보존하고 우발적인 포착을 방지하며, syntax-rules, syntax-case 등이 그 예시이다. 위생 매크로는 안전성을 제공하지만, 의도적인 변수 캡처를 어렵게 만들 수 있다는 비판도 존재하며, 많은 시스템은 위생을 해치지 않으면서 탈출구를 제공한다.

더 읽어볼만한 페이지

  • 메타프로그래밍 - 템플릿 (C++)
    C++ 템플릿은 다양한 자료형에 대해 동일한 코드를 재사용하는 기능으로, 함수, 클래스, 변수 템플릿을 지원하며 C++11부터는 가변 템플릿을 통해 인수의 개수를 유연하게 처리할 수 있지만, 과도한 사용은 코드 이해를 어렵게 하고 컴파일 시간을 증가시키는 등의 단점이 있다.
  • 메타프로그래밍 - 템플릿 메타프로그래밍
    템플릿 메타프로그래밍은 템플릿을 활용하여 컴파일 시간에 계산을 수행하는 프로그래밍 기법으로, 코드 중복을 줄이고 런타임 성능을 향상하며 정적 다형성을 구현하는 데 사용된다.
위생 매크로
위생 매크로
정의매크로 확장으로 인해 식별자가 캡처되지 않도록 보장하는 매크로
배경
문제매크로 확장은 의도치 않은 변수 캡처를 일으킬 수 있으며, 이는 프로그램의 의미를 변경할 수 있음
해결책위생 매크로는 매크로 확장의 결과로 도입된 모든 식별자에 자동으로 새로운 이름을 부여하여 이러한 문제를 해결함
이름 충돌 방지위생 매크로는 매크로 확장의 결과로 도입된 식별자가 기존의 식별자와 충돌하지 않도록 보장함
작동 방식
고유 식별자 부여위생 매크로는 매크로 확장 과정에서 생성된 모든 식별자에 고유한 식별자를 부여함
식별자 구분이러한 고유 식별자는 매크로가 확장될 때 동일한 이름을 가진 다른 식별자와 구별하는 데 사용됨
이름 캡처 방지따라서 위생 매크로는 매크로 확장에 의해 도입된 이름이 실수로 기존 변수나 다른 식별자를 캡처하는 것을 방지함
구현
방법위생 매크로는 일반적으로 매크로 확장기의 일부로 구현됨
기존 매크로 시스템이는 매크로가 확장되는 방식을 변경하여 구현되거나, 기존 매크로 시스템 위에 새로운 레이어를 추가하여 구현될 수 있음
예시 언어
SchemeScheme은 위생 매크로를 지원하는 잘 알려진 프로그래밍 언어 중 하나임
Common LispCommon Lisp에는 위생 매크로를 제공하는 여러 구현체가 있음
DylanDylan은 또한 위생 매크로를 지원하는 언어임
장점
신뢰성 향상위생 매크로는 매크로를 사용하여 작성된 코드를 더 신뢰성 있게 만들어줌
예방의도치 않은 변수 캡처로 인한 버그 발생을 예방할 수 있음
코드 유지보수 용이성매크로를 더 쉽게 작성하고 유지보수할 수 있도록 지원함

2. 비위생적인 매크로의 문제점

비위생적인 매크로 시스템을 가진 프로그래밍 언어에서는 매크로 확장 중에 생성된 변수나 함수 바인딩이 기존의 바인딩을 가릴 수 있다.

C 언어의 경우, 최상위 스코프에서 선언된 변수가 매크로 내의 변수에 의해 섀도잉될 수 있다.

Common Lisp의 경우, 표준 라이브러리 함수나 프로그램 정의 함수가 지역적으로 재정의되면 매크로의 동작이 변경될 수 있다. 하지만 Common Lisp 표준에서는 이러한 동작을 금지하고 있으며, 일부 구현에서는 패키지 잠금 기능을 제공하여 사용자의 실수를 방지한다.[1]

2. 1. 변수 섀도잉

C 언어의 매크로에서 흔히 발생하는 문제로, 매크로 내에서 선언된 변수가 매크로 외부의 변수를 가려 의도치 않은 결과를 초래한다. 다음 코드를 통해 이 문제를 설명할 수 있다.

```c

#define INCI(i) { int a=0; ++i; }

int main(void)

{

int a = 4, b = 8;

INCI(a);

INCI(b);

printf("a is now %d, b is now %d\n", a, b);

return 0;

}

```

위 코드를 C 전처리기를 통해 실행하면 다음과 같이 생성된다.

```c

int main(void)

{

int a = 4, b = 8;

{ int a = 0; ++a; };

{ int a = 0; ++b; };

printf("a is now %d, b is now %d\n", a, b);

return 0;

}

```

최상위 스코프에서 선언된 변수 `a`는 매크로 내의 `a` 변수에 의해 변수 섀도잉되며, 이는 새로운 스코프를 도입한다. 결과적으로 컴파일된 프로그램의 출력에서 `a`는 프로그램 실행에 의해 변경되지 않는다.

```text

a is now 4, b is now 9

```

`INCI` 매크로 안의 `a` 변수가 `main` 함수의 `a` 변수를 가려(`섀도잉`) `main` 함수의 `a`는 증가하지 않는다.

2. 2. 표준 라이브러리 함수 재정의

Common Lisp과 같은 언어에서, 매크로 내에서 사용된 표준 라이브러리 함수의 이름이 지역적으로 재정의될 경우, 매크로의 동작이 변경될 수 있다. 예를 들어, 아래 코드에서 `my-unless` 매크로는 `not` 함수를 사용하는데, `not` 함수가 지역적으로 재정의되면 `my-unless`의 동작이 의도와 다르게 바뀐다.[1]

```lisp

(flet ((not (x) x))

(my-unless t

(format t "This should not be printed!")))

```

하지만 Common Lisp 표준에서는 이러한 동작을 금지하고 있다.[1] 일부 구현에서는 패키지 잠금 기능을 제공하여 사용자의 실수를 방지한다.[1]

2. 3. 프로그램 정의 함수 재정의

위생 문제는 변수 바인딩을 넘어 확장될 수 있다. 다음 Common Lisp 매크로를 보자.

```lisp

(defmacro my-unless (condition &body body)

`(if (not ,condition)

(progn

,@body)))

```

이 매크로에는 변수에 대한 참조가 없지만, "if", "not", "progn" 심볼이 모두 표준 라이브러리에서 평소 정의에 바인딩되어 있다고 가정한다. 그러나 위의 매크로가 다음 코드에서 사용되는 경우:

```lisp

(flet ((not (x) x))

(my-unless t

(format t "This should not be printed!")))

```

"not"의 정의가 지역적으로 변경되어 `my-unless`의 확장이 변경된다.[1]

하지만 Common Lisp의 경우, [https://www.lispworks.com/documentation/lw70/CLHS/Body/11_abab.htm 11.1.2.1.2 규격 프로그램의 COMMON-LISP 패키지에 대한 제약 사항]에 따라 이 동작은 금지되어 있다. 어쨌든 함수를 완전히 재정의하는 것도 가능하다. Common Lisp의 일부 구현은 사용자가 실수로 패키지 내 정의를 변경하는 것을 방지하기 위해 [https://www.sbcl.org/manual/#Package-Locks 패키지 잠금]을 제공한다.[1]

물론, 이 문제는 프로그램 정의 함수에서도 유사하게 발생할 수 있다.[1]

```lisp

(defun user-defined-operator (cond)

(not cond))

(defmacro my-unless (condition &body body)

`(if (user-defined-operator ,condition)

(progn

,@body)))

; ... 나중에 ...

(flet ((user-defined-operator (x) x))

(my-unless t

(format t "This should not be printed!")))

```

사용 지점에서 `user-defined-operator`를 재정의하므로 매크로의 동작이 변경된다.[1]

3. 비위생적인 매크로 문제 해결 전략

위생적인 매크로를 지원하지 않는 언어에서는 비위생성 문제를 완전히 해결하기는 어렵지만, 몇 가지 방법을 통해 이 문제를 어느 정도 완화할 수 있다.


  • 난독화: 매크로 내에서 사용되는 변수 이름을 프로그램의 다른 부분에서 사용되지 않을 법한 특이한 이름으로 지정하여 충돌 가능성을 줄인다. 하지만 이 방법은 완벽하지 않으며, 프로그래머가 변수 이름을 주의 깊게 관리해야 한다. [1]
  • 읽기 시간 uninterned 심볼: `gensym`과 유사하게, 매크로 외부에서는 나타날 수 없는 특별한 심볼을 사용하여 충돌을 방지한다. 이 방법은 동일한 매크로의 여러 확장 간에 이름을 공유할 수 있다는 장점이 있다.[1]
  • 패키지 시스템 활용: Common Lisp과 같은 언어에서는 패키지 시스템을 이용하여 매크로 정의에 사용되는 심볼의 유효 범위를 제한할 수 있다. 이를 통해 사용자 코드와 매크로 내부 심볼 간의 충돌을 방지할 수 있다.
  • 리터럴 객체 사용: 매크로 확장을 텍스트 코드가 아닌 실제 객체를 포함하는 표현식으로 생성하여 변수 충돌 문제를 우회할 수 있다. 예를 들어, 매크로가 특정 변수를 사용해야 하는 경우, 클로저 객체를 호출하는 방식으로 확장할 수 있다.[1]

3. 1. 난독화

위생적이지 않은 매크로 시스템을 가진 프로그래밍 언어에서는, 매크로 확장 중에 생성된 변수 바인딩으로 인해 기존 변수 바인딩이 매크로로부터 숨겨질 수 있다. C 언어에서 이 문제는 다음 코드로 설명할 수 있다.

```c

#define INCI(i) { int a=0; ++i; }

int main(void)

{

int a = 4, b = 8;

INCI(a);

INCI(b);

printf("a is now %d, b is now %d\n", a, b);

return 0;

}

```

위 코드를 C 전처리기를 통해 실행하면 다음과 같이 생성된다.

```c

int main(void)

{

int a = 4, b = 8;

{ int a = 0; ++a; };

{ int a = 0; ++b; };

printf("a is now %d, b is now %d\n", a, b);

return 0;

}

```

최상위 스코프에서 선언된 변수 `a`는 매크로 내의 `a` 변수에 의해 섀도잉되며, 이는 새로운 스코프를 도입한다. 결과적으로, 컴파일된 프로그램의 출력에서 볼 수 있듯이 `a`는 프로그램 실행에 의해 변경되지 않는다.

```text

a is now 4, b is now 9

```

매크로 확장 중에 임시 저장이 필요한 경우 가장 간단한 해결책은 매크로 내에서 프로그램의 나머지 부분에서 사용하지 않을 것 같은 특이한 변수 이름을 사용하는 것이다.[1]

```c

#define INCI(i) { int INCIa = 0; ++i; }

int main(void)

{

int a = 4, b = 8;

INCI(a);

INCI(b);

printf("a is now %d, b is now %d\n", a, b);

return 0;

}

```

`INCIa`라는 이름의 변수가 생성되기 전까지, 이 해결책은 다음과 같은 올바른 출력을 생성한다.[1]

```text

a is now 5, b is now 9

```

현재 프로그램에 대한 문제는 해결되었지만, 이 해결책은 견고하지 않다. 매크로 내부에서 사용되는 변수와 프로그램의 나머지 부분에 있는 변수는 프로그래머가 동기화 상태로 유지해야 한다. 특히, `INCI` 매크로를 `INCIa` 변수에 사용하면 원래 매크로가 `a` 변수에서 실패했던 것과 동일한 방식으로 실패할 것이다.[1]

3. 2. 읽기 시간 uninterned 심볼

`gensym`과 유사하게, 매크로 외부에서는 발생할 수 없는 읽기 시간 uninterned 심볼(`#:` 표기법으로 표시)을 사용한다.[1] 이는 동일한 매크로의 여러 확장에서 단일 이름을 공유한다는 점에서 난독화와 유사하다.[1]

3. 3. 패키지

Common Lisp과 같은 패키지 시스템을 활용하면, 매크로 정의에 사용되는 심볼들을 해당 패키지 내에서만 유효하도록 만들 수 있다. 사용자 코드는 이중 콜론(::) 표기법(예: `cool-macros::secret-sym`)을 사용하여 이러한 내부 심볼에 접근할 수 있다.

ANSI Common Lisp 표준은 표준 함수 및 연산자의 재정의를 정의되지 않은 동작으로 규정한다. 즉, 이러한 재정의는 구현에 따라 오류로 처리될 수 있다. 따라서 리스프 패키지 시스템은 이름 충돌로 발생할 수 있는 매크로 위생 문제에 대한 완전한 해결책을 제공한다.

예를 들어, 프로그램 정의 함수 재정의 예시에서 `my-unless` 매크로는 자체 패키지에 정의될 수 있으며, `user-defined-operator`는 해당 패키지 내에서만 유효한 심볼이 된다. 사용자 코드에서 사용되는 `user-defined-operator` 심볼은 `my-unless` 매크로 정의에 사용된 심볼과는 다른 심볼이 된다.

3. 4. 리터럴 객체

일부 프로그래밍 언어에서 매크로 확장은 텍스트 코드가 아닌, 실제 객체를 포함하는 표현식으로 생성될 수 있다. 예를 들어, 매크로가 기호 `f`를 포함하는 표현식으로 확장되는 대신, `f`가 가리키는 실제 객체를 포함하도록 확장할 수 있다.[1] 마찬가지로, 매크로가 특정 변수나 객체를 사용해야 할 때, 매크로 정의를 둘러싼 클로저 객체를 호출하는 방식으로 확장할 수 있다.[1]

4. 위생적인 매크로 (Hygienic Macro)

위생적인 매크로 시스템은 Scheme과 같은 언어에서 모든 식별자의 어휘적 범위를 보존하고 우발적인 포착을 방지하는 매크로 확장 프로세스를 사용한다. 이러한 속성을 참조 투명성이라고 한다. 필요한 경우, 일부 시스템에서는 프로그래머가 매크로 시스템의 위생 메커니즘을 명시적으로 위반할 수 있도록 허용한다.[2][6][7][8][9]

입력 형식의 패턴을 출력 형식으로 변환하는 위생 매크로 프로세서는 기호 충돌을 감지하고 임시로 기호의 이름을 변경하여 이를 해결한다. 기본적인 전략은 매크로 정의에서 '바인딩'을 식별하고 해당 이름을 gensym으로 바꾸는 것이며, 매크로 정의에서 '자유 변수'를 식별하고 매크로가 사용된 범위가 아닌 매크로 정의의 범위에서 해당 이름을 검색하도록 하는 것이다.

아래는 Scheme의 `let-syntax` 및 `define-syntax`를 사용한 `my-unless` 매크로 구현 예시이다.

```scheme

(define-syntax my-unless

(syntax-rules ()

((_ condition body ...)

(if (not condition)

(begin body ...)))))

(let ((not (lambda (x) x)))

(my-unless #t

(display "This should not be printed!")

(newline)))

```

위 코드는 `my-unless` 매크로가 전역 네임스페이스의 `not` (논리 부정)이 아닌, 지역 변수 `not`을 사용함에도 불구하고 의도한 대로 동작하는 것을 보여준다.

```scheme

(define-syntax my-unless

(syntax-rules ()

[(_ condition body ...)

(if (not condition) ;; 전역적인 네임스페이스에 속하는 not: 논리 부정을 의미하는 본래의 정의이다.

(begin body ...)

)

]

)

)

(let ([not (lambda (b) b)]) ;; 국소적인 네임스페이스에 속하는 not: 논리 부정을 의미하는 본래의 정의와는 다른 정의이다.

(my-unless #t

(display "이 문장은 출력되지 않을 것이다!") (newline)

)

)

4. 0. 1. Syntax-rules

scheme

(define-syntax swap!

(syntax-rules ()

((_ a b)

(let ((temp a))

(set! a b)

(set! b temp)))))

```

`syntax-rules`는 패턴 매칭 기능을 제공하여 매크로 작성을 용이하게 한다. `syntax-rules`는 특정 종류의 매크로를 간결하게 표현할 수 있지만, 다른 매크로 시스템을 표현하기에는 불충분하다. R4RS 문서의 부록에서 설명되었지만 필수는 아니었으나, R5RS Scheme 표준에서는 표준 매크로 기능으로 채택되었다.[6][7] 위 코드는 `syntax-rules`를 사용하여 두 변수의 값을 바꾸는 `swap!` 매크로를 정의한 예시이다.

4. 0. 2. Syntax-case

scheme

(define-syntax swap!

(lambda (stx)

(syntax-case stx ()

((_ a b)

(syntax

(let ((temp a))

(set! a b)

(set! b temp)))))))

```

R6RS Scheme 표준 매크로 시스템으로, `syntax-rules`보다 더 강력한 기능을 제공한다.[10] 패턴 매칭 언어와 하위 수준 기능을 모두 포함하여, 매크로 작성을 위한 다양한 방법을 지원한다. 예를 들어, `syntax-case` 매크로는 임의의 Scheme 함수를 통해 패턴 매칭 규칙에 대한 부수 조건을 지정할 수 있다. 또는 매크로 작성자는 패턴 매칭 프런트엔드를 사용하지 않고 구문을 직접 조작하도록 선택할 수 있다. `datum->syntax` 함수를 사용하면, `syntax-case` 매크로는 의도적으로 식별자를 캡처하여 위생성을 깨뜨릴 수도 있다.

4. 0. 3. 기타 시스템

구문적 클로저(Syntactic Closures)와 명시적 이름 변경[11]은 위생성 강제를 매크로 작성자에게 맡기는 대안적인 매크로 시스템이다. 이는 기본적으로 위생성을 자동으로 강제하는 `syntax-rules`와 `syntax-case`와는 다르다. 다음은 구문적 클로저와 명시적 이름 변경 구현을 사용한 `swap` 예시이다.

```scheme

;; 구문적 클로저

(define-syntax swap!

(sc-macro-transformer

(lambda (form environment)

(let ((a (close-syntax (cadr form) environment))

(b (close-syntax (caddr form) environment)))

`(let ((temp ,a))

(set! ,a ,b)

(set! ,b temp))))))

;; 명시적 이름 변경

(define-syntax swap!

(er-macro-transformer

(lambda (form rename compare)

(let ((a (cadr form))

(b (caddr form))

(temp (rename 'temp)))

`(,(rename 'let) ((,temp ,a))

(,(rename 'set!) ,a ,b)

(,(rename 'set!) ,b ,temp))))))

4. 1. 위생적인 매크로를 지원하는 언어

Scheme에서 위생 매크로를 자동적으로 구현하는 시스템이 시작되었다. 1986년 Kohlbecker는 위생 매크로 시스템을 위한 최초의 KFFD 알고리즘을 제시했다.[2] 1987년 Kohlbecker와 Wand는 매크로 작성을 위한 선언적 패턴 기반 언어를 제안했는데, 이는 R5RS 표준에 채택된 `syntax-rules` 매크로 기능의 전신이었다.[6][7] 1988년 Bawden과 Rees는 구문적 클로저를 제안했다.[8] 1993년 Dybvig 등은 구문의 대체 표현을 사용하고 위생을 자동으로 유지하는 `syntax-case` 매크로 시스템을 도입했다.[9]

위생적인 매크로를 지원하는 프로그래밍 언어는 다음과 같다.

  • Scheme - syntax-rules, syntax-case, syntactic closures 등.
  • Racket - Scheme 변종으로, 매크로 시스템은 원래 syntax-case를 기반으로 했지만, 현재 더 많은 기능을 가지고 있다.
  • Nemerle[12]
  • Dylan
  • Elixir[13]
  • Nim
  • Rust
  • Haxe
  • Mary2 - 1978년경 ALGOL 68 파생 언어에서 범위가 지정된 매크로 본문.
  • Julia[14]
  • Raku - 위생 매크로와 비위생 매크로를 모두 지원한다.[15]

5. 위생적인 매크로에 대한 비판

위생 매크로는 안전성을 제공하지만, 의도적인 변수 캡처를 더 어렵게 만든다는 단점이 있다. Let Over Lambda의 저자인 더그 호이트는 다음과 같이 말했다.[16]

많은 위생적인 매크로 시스템은 위생성이 제공하는 보장을 훼손하지 않으면서 탈출구를 제공한다. 예를 들어, Racket에서는 https://docs.racket-lang.org/reference/stxparam.html#%28tech._syntax._parameter%29 구문 매개변수를 정의하여 바운드 변수를 선택적으로 도입할 수 있다.[17]

6. 결론

건전한 매크로 시스템에서는 매크로 전개 과정에서 처리 시스템이 모든 식별자(함수명, 변수명, `if`, `while` 등)의 렉시컬 스코프를 보장하여 의도하지 않은 캡처를 방지한다. 이 성질을 "참조 투명성"이라고 부른다. 캡처가 필요한 경우를 위해, 일부 구현에서는 건전한 매크로 메커니즘을 프로그래머가 명시적으로 위반할 수 있도록 하고 있다.[1]

예를 들어, Scheme의 `define-syntax`는 건전한 매크로 시스템이다. 즉, 매크로 전개 처리에서 겉으로 보기에는 같은 식별자라도 각각 소속된 문맥을 감안하여 처리되므로, 프로그램이 의도대로 동작하는 것이 보장된다.[1]

참조

[1] 서적 Dylan programming: an object-oriented and dynamic language Addison Wesley Longman Publishing Co., Inc. 1997
[2] 학술대회 Hygienic Macro Expansion http://www.cs.indian[...] 1986
[3] 웹사이트 CLHS: Function GENSYM http://www.lispworks[...]
[4] 웹사이트 hygiene-versus-gensym http://community.sch[...] 2022-06-11
[5] 논문 Embedding Hygiene-Compatible Macros in an Unhygienic Macro System 2010
[6] 논문 Revised5 Report on the Algorithmic Language Scheme http://www.schemers.[...] 1998-08
[7] 학술대회 Macro-by-example: Deriving syntactic transformations from their specifications http://jcmc.indiana.[...] 1987
[8] 학술대회 Syntactic closures https://apps.dtic.mi[...] 1988
[9] 논문 Syntactic abstraction in Scheme http://www.cs.indian[...] 1993
[10] 웹사이트 Revised6 Report on the Algorithmic Language Scheme (R6RS) http://www.r6rs.org Scheme Steering Committee 2007-08
[11] 논문 Hygienic macros through explicit renaming 1991
[12] 문서 Metaprogramming in Nemerle http://nemerle.org/m[...]
[13] 웹사이트 Macros http://elixir-lang.o[...]
[14] 웹사이트 Metaprogramming: the Julia Language http://docs.julialan[...]
[15] 웹사이트 Synopsis 6: Subroutines http://perlcabal.org[...]
[16] 문서 Let Over Lambda—50 Years of Lisp https://letoverlambd[...]
[17] 문서 Fear of Macros https://www.greghend[...]
[18] 논문 Revised5 Report on the Algorithmic Language Scheme http://www.schemers.[...] 1998-08
[19] 논문 Revised5 Report on the Algorithmic Language Scheme http://www.schemers.[...] 1998-08
[20] 문서 Kohlbecker
[21] 논문 Revised5 Report on the Algorithmic Language Scheme http://www.schemers.[...] 1998-08
[22] 서적 Dylan programming: an object-oriented and dynamic language Addison Wesley Longman Publishing Co., Inc. 1997
[23] 학술대회 Hygienic Macro Expansion http://www.cs.indian[...] 1986



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com