(해당 강의노트는 Michael L. Scott의 [Programming Language Pragmatics: 4th edition] 책을 기반으로 작성되었습니다)
[ 03 Names, Scopes, and Bindings(3)] 3.4~3.7
Table of Contents
3.4 Implementing Scope
- 3.4.1 Symbol Tables
- 3.4.2 Association Lists and Central Reference Tables
3.5 The Meaning of Names within a Scope
- 3.5.1 Aliases
- 3.5.2 Overloading
- 3.5.3 Polymorphism and Related Concepts
3.6 The Binding of Referencing Environments
- 3.6.1 Subroutine Closures
- 3.6.2 First-Class Values and Unlimited Extent
- 3.6.3 Object Closures
3.7 Macro Expansion
+ 번외: 자바에서의 람다 표현식
3.4 Implementing Scope
1. statically scoped program에서 이름을 추척하는 컴파일러는 symbol table이라고 부르는 데이터 추상화에 의존한다.
2. 본질적으로 symbol table은 사전 형태임
: 컴파일러가 알고 있는 정보에 이름을 mapping함
3. symbol table의 기본 연산자
- 새로운 mapping 삽입(추가) : a name-to-object binding
- 지정된 이름을 위한 정보 조회 : look up the information
4. Static scope rules은 다른 정보를 담고 있는 다른 객체와 지정된 이름과 상응하는 것을 허용함으로써 복잡성을 높인다
5. dynamic scoping을 사용하는 언어에서, 인터프리터는 반드시 런타임 시, symbol table insert와 조회와 유사한 작업을 반드시 수행해야 한다.
6. Stack 을 만들어서 enter_scope(push), leave_scope(pop)하여 LIFO style symbol table을 만들기
3.5 The meaning of Names within a Scope
1. 지금까지 배운 건 프로그램 상의 특정 시점에서의 이름과 오브젝트간의 관계는 일대일 매핑이였음
: 지금부터는 그렇지 않은 경우를 보도록 하자
2. aliases
: 두 개 또는 그 이상의 이름이 프로그램의 같은 시점에서 같은 오브젝트를 나타낼 경우
3. 한 개의 이름이 여러 개의 오브젝트를 나타내는 경우를 overloaded라고 함 (<->앨리어스와는 반대 개념)
- Overloading은 polymorphism과 관련이 있음
: 다형성은 함수 혹은 다른 프로그램의 fragment가 해당 argument들의 타입에 의존하여 다른 방식으로 행동하게 하는 것을 허락
- 자바에서의 오버로딩 : 같은 이름의 메서드 여러개를 가지면서 매개변수의 유형과 개수를 다르게 하여 다형성 실현
3.5.1 Alias
- In C, union / In Pascal, variant records
// In C
typedef union {
int mem1;
int mem2;
double mem3;
} UBox;
- 많은 언어에서 앨리어스 생성하는 방식은 서브루틴에 변수에 직접적으로 접근이 가능한 참조에 의한 값 전달방식이다.
// In C++
double sum, sumOfSquares;
…
void accumulate(double& x) {
sum += x;
sumOfSquares += (x * x);
}
…
accumulate(sum);
3.5.2 Overloading
- 대부분의 프로그래밍 언어들은 제한적이긴 하나 최소한의 오버로딩을 제공해줌
- In C, + 연산자가 몇몇의 다양한 함수들로 사용됨
: signed and unsigned integer and floating-point addition
- 오버로딩의 좀 더 정교한 형태는 Ada의 enumeration constant에서 나타남
// In Ada,
declare
type month is (jan, feb, mar, apr, may, jun,
jul, aug, sep, oct, nov, dec);
type print_base is (dec, bin, oct, hex);
mo : month;
pb : print_base;
begin
mo := dec; -- the month dec
pb := oct; -- print_base oct
print(oct); -- error! insufficient context
symbol table에서 lookup 함수를 사용하여 특정 이름을 search 할 수 있음
- semantic analyzer는 리스트 요소들 사이에서 반드시 선택해야 함
- 이전 코드에서의 불충분한 context는 에러를 발생시킬 수 있음
- 많은 종류의 언어들이 context를 더 명확하게 구별할 수 있도록 프로그래머들에게 enumeration constant 사용을 허용
- e.g., in Ada : print(month'(oct));
모듈러-3, C#의 경우는 enumeration constant 사용시 모호성이 발생하지 않더라도 반드시 타입의 이름을 prefixed 해야 함
mo := month.dec; (* Modula-3 *)
pb = print_base.oct; // C#
기존의 C, C++, standard Pascal의 경우에서는, enumeration constant를 오버로드하는 개념 자체가 없음
- 모든 constan가 주어진 스코프 내에서 반드시 구별되어야 함
// In C
typedef enum { SUNDAY, MONDAY } DAYS;
/* not allowed
typedef enum { SUNDAY, MONDAY } DAYS2;
*/
int main(void) {
DAYS d = SUNDAY;
printf("SUNDAY: %d\n", d2);
{
typedef enum { SUNDAY, MONDAY } DAYS2;
DAYS2 d2 = MONDAY;
printf("SUNDAY: %d\n", d2);
}
return 0;
}
- In C++ 11, 프로그래머에게 아래의 behavior을 전반적으로 컨트롤할 수 있도록 새로운 syntax를 제공함
1. enum constant는 반드시 구별되어야 함
2. enum class constant는 반드시 클래스 선언을 붙여줘야 함
// cannot compile
enum month { JAN, FEB, MAR, APR, MAY, JUN,
JUL, AUG, SEP, OCT, NOV, DEC };
enum print_base { DEC, BIN, OCT, HEX };
// can be compiled in C++11
enum class month { JAN, FEB, MAR, APR, MAY, JUN,
JUL, AUG, SEP, OCT, NOV, DEC };
enum class print_base { DEC, BIN, OCT, HEX };
- Ada, C++는 서브루틴의 이름도 오버로드할 수 잇음
1. C++의 많은 기능들이 이후 등장한 Java, C#이 이어 받음 (등장 순서 : C++(1980s) < Java(1990s) < C#(2000s))
2. 지정된 이름은 같은 스코프 내의 서브루틴의 arbitrary number을 참조할 수 있음
// In C++
struct complex { double real, imaginary; };
enum base { dec, bin, oct, hex };
int i;
complex x;
void print_num(int n) { … }
void print_num(int n, base b) { … }
void print_num(complex c) { … }
print_num(i);
print_num(i, hex);
print_num(x);
Built-in 연산자의 재정의
1. 많은 언어들에서 built-in arithmetic operator(+,-,*, etc)가 user-defined function들로 오버로드되는 것을 허락함
- Ada, C++, C#, Fortran 90, Haskell, and so on
2. Ada, C++, C#에서는 사용자 정의 함수로 재정의 하는 것을 각 연산자의 형태를 alternate prefix form 방식을 사용함
- infix form이란? A + B 일 때, 두 숫자 가운데에 있는 '+' 연산자를 말함
- prefix form을 위해 abbreviation하는 infix form 정의 가능
- abbreviation이란? 약어
- 예 : C++,C# 에서는 A.operator+(B)보다 A + B하는 것이 훨씬 간단
- 이 경우 A는 operator+ 함수에 정의되어 있는 클래스의 인스턴스임
- In Haskell, 내 맘대로 연산자 가능
: let a @@ b = a * 2 + b
3.5.3 Polymorphism and Realted Concepts
Related Concepts
: 오버로딩과 관련된 개념에는 자동형 변환, 다형성이 있는데 이 세 가지 모두 다 특정 상황에서 여러가지 타입의 변수 반환이 가능
단. 함수의 이름이 같은 경우에만 나타남
- Corceion : 자동형 변환으로, 컴파일러가 자동적으로 형을 변환해줌
- Polymorphism : 다형성으로, 하나의 서브루틴에 다양한 인자 타입을 허락함
Overloading, Coercion, Polymorphism
- 구문상 유사하나, semantic, pragmatic상에서 중요한 차이점을 보인다
1. Overloading은 같은 이름의 여러개의 객체를 프로그래머에게 허용하며, 문맥에 근거하여 그들을 명확히 구분 짓는다
: 서브루틴의 경우 argument의 타입과 개수를 다르게 지정하여 구분
2. Coecion은 컴파일러가 예측되어지는 타입으로 자동 변환하는 것을 허용한다
3. Polymorphism은 한 개의 루틴이 여러 개의 타입의 argument을 가지는 것을 허용한다.
- Ada에서 정수와 실수 min계산을 위한 함수는 아래와 같이 오버로딩하여 작성 가능
// In Ada
function min(a, b : integer) return integer is ...
function min(x, y : real) return real is ...
- 반면 C에서는 아래와 같이 실수 타입의 함수 하나만 작성하여도 이후 정수로 인자가 넘어올 경우 자동형변환이 일어나게 됨
//In C
double min(double x, double y){ ...
3.6 The Binding on Referencing Environments
- 스코프 룰은 프로그램 내에서 지정된 statement의 referencing environment에 의해 결정됨
: 프로그램을 실행시키다가 어느 한 순간에 멈췄을 때 그 때 유효한 함수 및 변수 즉, 바인딩을 referencing environment라고 함
이는 아래의 두가지 룰로 결정됨
1. Static Scope rules
: 프로그램을 실행하지 않아도 알 수 있음
2. Dynamic Scope rules
: 프로그램을 시켜봐야 알 수 있음
- 아직 다루지 않았던 부분 중에서 생각해야 할 부분들
1. 몇몇 언어들에서는 서브루틴에 대한 참조 생성을 허락 함
: 예를 들어, 파라미터 값으로 패스될 때
2. 다음과 같은 서브루틴에서는 scope rules이 적용되어야 함
: 참조가 처음 생성되거나 루틴이 마지막으로 호출될 때
- Shallow binding에서는 referencing environment는 서브루틴이 호출될 때 bound 됨
- 가장 최근에 호출된 곳에서 값을 가져와 사용함
- Deep binding에서는 referencing environment는 reference가 만들어질 때 bound 됨
- 구조적으로 가장 상위의 것을 가져와 사용함
// In c++
typedef struct {
...
int age;
...
} person;
int threshold;
database people;
int older_than_threshold(person p) {
return p.age >= threshold;
}
void print_person(person p) {
/* call appropriate I/O routines to print record
on standard output.
Make use of nonlocal variable line_length to
format data in columns. */
...
}
void print_selected_records(database db,
function predicate, function print_routine)
{
int line_length;
if (device_type(stdout) == terminal)
line_length = 80
else /* stdout is a file or printer */
line_length = 132
/* iterating over these may actually be
a lot more complicated than a 'for' loop */
foreach record r in db {
if predicate(r)
print_routine(r);
}
}
int main(void) {
...
threshold = 35;
print_selected_records(people,
older_than_threshold, print_person);
}
1. print_selected_record가 record안을 순회하는 범용 목적의 함수라고 가정하자
- dynamic scoping일 경우, 절차적으로 print_selected_record가 선언되고 초기화 되는 것이 자연스럽다.
- 이 작업에서, print_routine의 referencing environment은 루틴이 실제로 print_selected_record에서 호출되지 않는 한 생성되지 않음
- Shallow binding : 서브루틴의 referencing environment의 late binding은 파라미터로 넘겨지게 된다
- 대게 언어들의 디폴트는 dynamic scoping임
2. 반면에 order_than_threshold는 shallow binding이 잘 동작하지 않을 것임
3. deep binding vs. shallow binding
- older_than_threshold 함수의 environment를 위해 deep binding이 적합
- print_person 절차의 environment를 위해 shallow binding이 적합
3.6.1 Subroutine Closures
- Deep binding 은 referencing environment의 명백한 표현의 생성에 의해 구현되며 서브루틴을 참조하는 것과 함께 bundling됨
- The bundle as a whole is referred to as a closure
- Closure : 배낭을 매고 있는 함수
- 함수에 대한 참조와 referencing environment에 대한 representation의 combination
- clouser 안에서 대게 서브루틴은 그 코드에서 포인터로 가리켜져 있음
- dynamic scoping을 사용한 Lisp의 early 버전
1. deep binding 사용 시, 빌트인 함수를 통해 사용 가능.
- 인자를 함수에 전달해서 입력으로 주어진 함수와, 호출 당시 reference environment 를 합쳐서 클로저를 만들어 이를 반환
- 이후 클로저는 다른 함수의 인자로 전달 가능
- static scoping 과 shallow binding의 혼합은 어떠한 언어에서도 허용하지 않음
- static scoping에서 바인딩 룰은, 로컬이나 글로벌이 아닌 오브젝트에 접근시 중요함(단, 이들 모두 nesting의 중간 단계에 정의되어 있음)
- 만약 오브젝트가 현재 실행 중인 서브루틴의 로컬일 경우, 서브루틴이 직접적으로 호출되었는지 혹은 클로저를 통해서 호출되었는지는 중요한 것이아님
- 두 경우 모두 다 로컬 오브젝트가 서브루틴이 시잘할 때 생성된 것이기 때문임
- 만약 오브젝트가 글로벌일 경우, 한 개 이상의 인스턴스는 프로그램의 main이 recursive하지 않으므로 절대 생성되지 않음
- 이러한 바인딩 룰과는 관련 없는 언어들은 아래와 같음
- C (no nested subroutines) , PL/I and Ada83은 서브루틴이 파라미터로 전달될 수 없음
- 코드 예시
- Deep binding : The program prints a 1
- Shallow binding : The program would print a 2
def A(I, P):
def B():
print(I)
# body of A:
if I > 1:
P()
else:
A(2, B)
def C():
pass # do nothing
A(1, C) # main program
3.6.2 First-Class Values and Unlimited Extent
- 일반적으로, 프로그래밍 언어에서 아래 세 조건을 만족할 경우 value는 first-class status(citizens)를 가짐
조건 1. 인자로 전달 받는게 가능
조건 2. 서브루틴으로부터 반환받기 가능
조건 3. 변수에 할당 가능
- 대부분의 프로그래밍 언어에서, Integer, character과 같이 단순한 타입들은 first-class value에 해당
- Second-class : 조건1은 만족하나, 조건 2 혹은 3이 불가
- Third-class : 조건 1이 불가
- 서브루틴은 대부분 변동 가능하다
1. 모든 함수형 프로그래밍 언어에서의 first-class value와 대부분의 스크립트 언어가 이에 해당
2. first-class in C#
3. 몇몇 절차적(imperative) 언어들에서는 제한 사항이 존재
: Fortan, Modula-2, Modula-3, Ada 95, C, C++ 등이 해당
4. nested scopes와 함께 언어 안의 first-class 서브루틴은 추가적인 복잡도 레벨을 생성시킴
: 서브루틴의 참조가 이미 선언된 루틴을 outlive 하게 하는 스코프의 실행 가능성을 높이게 됨
(define plus-x
(lambda (x)
(lambda (y) (+ x y))))
…
(let ((f (plus-x 2)))
(f 3)) ; returns 5
- f가 여섯 번째 줄에서 호출 시, 사실상 plus-x는 이미 반환되었는데도 불구하고 plus-x안의 x를 f의 referencing environment에서 참조하게 된다
- 따라서 x가 계속 사용 가능한 상태인가를 확인해야 함
- 자세한 사항은 아래의 원문 참고
- 만약 각 스코프의 실행 끝 부분에서 local objects가 파괴될 시, referecing environment는 허상 포인터(dangling reference)를 포함할 수 있는 long-lived closure을 잡는다(capture)
- 이러한 문제점들을 피하기 위해서, 대부분의 함수형 언어에서는 local objects가 가지는 unlimited extent를 구체화한다
- 이경우 lifetime 은 indefinitly하게 지속됨
- 가비지 콜랙션 시스템에서 더 이상 해당 객체가 사용되지 않는다고 판단되면 reclaim(->destroy) 가능
- 대부분의 절차지향 언어에서는 local objects는 limited extent를 가진다 : 스코프의 실행 끝에서 파괴됨
- limited extent에서의 지역 오브젝트를 위한 공간은 스택 위에 할당 됨
- unlimted extent에서의 지역 오브젝트를 위한 공간은 일반적으로 힙 위에 할당되어야 함
3.6.3 Object Closures
1. nested subroutine을 전달할 때 closure 안의 referencing environment는 복잡한 형태를 띄고 있다.
- 프로그래머가 작업 시, 함수 전달 혹은 반환 당시의 referencing environment(context)를 전달하는 것이 어려움
- nested subroutine이 아닌 언어일 경우, first-class 서브루틴의 구현은 복잡하지 않음
2. 객체지향 언어에서는, 비슷한 효과를 수행할 수 있는 대안책이 있다.
: 간단한 오브젝트의 메소드로 서브루틴을 캡슐화시키고, 해당 오브젝트의 멤버 변수에 메소드의 context를 포함시키도록 하면 된다
- 자바 코드의 예
// In Java
interface IntFunc { // defines a static type for objects enclosing a function from int to int
public int call(int i);
}
class PlusX implements IntFunc { // concrete implementation of IntFunc
final int x;
PlusX(int n) { x = n; }
public int call(int i) { return i + x; }
}
...
IntFunc f = new PlusX(2);
System.out.println(f.call(3)); // prints 5
- C++ 코드의 예
: 오버라이드한 클래스의 오브젝트를 마치 함수인것처럼 operator()을 부를 수 있음
class int_func {
public:
virtual int operator()(int i) = 0;
};
class plus_x : public int_func {
const int x;
public:
plus_x(int n) : x(n) { }
virtual int operator()(int i) { return i + x; }
};
...
plus_x f(2);
cout << f(3) << "\n"; // prints 5
3. 함수의 역할을 하는 오브젝트와, 해당 오브젝트의 referencing environment를 object enclosure, function object, functor 라고 부른다
- C#에서 first-class subroutine은 delegate type의 인스턴스임
- 이러한 타입은 구체화된 인자와 반환 타입이 매치되는 모든 서브루틴을 대표할 수 있음 : 아마 static이거나 어떤 객체의 메소드임
static int Plus2(int i) { return i + 2; }
…
IntFunc f = new IntFunc(Plus2);
Console.WriteLine(f(3)); // print 5
class PlusX {
int x;
public PlusX(int n) { x = n; }
public int call(int i) { return i + x; }
…
}
IntFunc g = new IntFunc(new PlusX(2).call());
Console.WriteLine(g(3)); // print 5
4. Object closures 은 몇몇 언어에서 special syntax를 지원하는 만큼 충분히 중요한 사항이다
- C# 2.0의 경우 일반적인 케이스에서 서브루틴에서의 nest를 허용하지 않으나 anonymous method를 통해 가능하게 함
: lambda syntax 를 사용하면 더 간단히 사용가능(3.6.4에서 다룰 내용)
3.6.4 Lambda Expression
- C#에서의 익명 delegate 사용 예시
1. 아래의 코드 처럼 lamda syntax를 간단히 사용 가능
static IntFunc PlusY(int y) {
return i => i + y;
}
- delegate 키워드는 바디로부터 익명 함수의 인자리스트를 분리하는 sign으로 대체될 수 있다.
- 하나 이상의 파라미터가 필요 : 괄호로 둘러싸여 있어야 함
- 복잡한 함수 필요 : 바디는 반드시 코드블럭이어야 함
2. "lambda expression"은 "lambda calculs"로부터 옴
- lambda syntax는 언어마다 다름
(lambda (i j) (> i j ) i j) ; Scheme
((lambda (i j) (> i j) i j) 5 8)
(int i, int j) => i > j ? i : j // C#
Func<int, int, int> m = (i, j) => i > j ? i : j;
Console.WriteLine(m.Invoke(5, 8));
fun i j -> if i > j then i else j (* Ocaml *)
(fun i j -> if i > j then i else j) 5 8
->(i, j){ i > j ? i : j } # Ruby
Print ->(i, j){ i > j ? i : j }.call(5, 8)
1. 함수형 프로그래밍 언어에서 Lambda Expression은 values로서 함수를 조작하는 것을 쉽게 만든다
- 이러한 조작은 절차지향 언어에서는 덜 흔하긴 하나 Lambda Expression은 코드 재사용성과 일반화를 돕는다
- 이는 다양한 방법으로 혼합하여 즉석에서 새로운 함수를 만들어낼 수 있음
- 하나의 흔한 idom을 살펴보면 call back이 있다
*call back이란? 서브루틴임. 이는 라이브러리로 전달되며 적합한 상황 시, 메인프로그램으로 라이브러리가 call back하는 것을 허용
- 비교 연산자는 sotring 루틴으로 전달됨
- predicate는 콜랙션의 요소를 filtering할 때 사용됨
- handler는 몇몇 미래 이벤트에 응답할 때 호출됨
2. Lambda Expression in C++
1. V가 정수 타입을 저장하는 벡터이며 50 이하의 원소들을 출력한다고 가정해보자.
- 이는 아래와 같이 for_each 문을 통해 간단한 코드로 작성할 수 있다
- for_each(V.begin(), V.end(), [](int e) {if (e < 50) cout << e << " ";}
- for each문은 처음 두 개의 파라미터에 의해 구체화되는 범위 내에서의 모든 요소에 세번째 인자의 내용을 적용하는 standard library routine임
2. lambda expression 의 스코프 바깥에 있는 변수 k라고 할 때, k보다 작은 모든 요소를 출력하는 경우를 보자
- C++에서는 두가지 옵션이 존재하게 됨
- 일반적인 함수와 같은 방법으로 for_each에 인자를 전달하는 object closure(C++에서는 fuction object에 해당)를 생성하옵션 존재
+ Arranges for a copy fo each captured variable : [=](int e) { if (e < k) cout << e << " ";}
+ Arranges for a reference to be placed instead : [&](ine e) { if (e <k) cout << e<< " ";}
3. Lambda Expression in JAVA(Since Java 8)
- java는 전통적으로 functional interface로 알려진 idiom에 의존하는 언어임
- Arrays.sort routine은 expects a parameter of type Comparator 타입의 파라미터로 예측됨
- 아래와 같이 나이에 따라 정렬되는 알고리즘이 있다고 하자
// In JAVA
class AgeComparator implements Comparaotr<Person> {
public int compare(Person p1, Person p2) {
return Integer.compare(p1.age, p2.age);
}
}
Person[] people = …
…
Arrays.sort(People, new AgeComparator());
JAVA 8버전에서의 lambda expression을 사용하면 아래와 같이 간단하게 AgeComparator를 작성 가능
Person[] people = …
…
Arrays.sort(People,
(p1, p2) -> Integer.compare(p1.age, p2.age));
- 변수 혹은 formal한 파라미터가 함수형 인터페이스 타입으로 선언될 경우, Java8에서는 파라미터와 리턴타입이 싱글 메소드와 일치하는 lambda expression을 대신 쓰는 것을 허용한다
- 자바에서 람다식을 사용하는 방식은 위와 같은 방식만이 가능
3.7 Macro Expansion
- 어셈블리 언어 프로그래머들은 자주 반복되는 코드를 잘 찾을 수 있게 매크로를 사용
: To ease the burden, 많은 어셈블러들이 정교한 Macro Expansion facilities를 제공함
- C언어가 만들어진 1970년대 초반에, 매크로 프로세싱 facility를 자연적으로 포함시킴
function macro ex1:) #define SWAP(a,b) {int t = (a); (a) = (b); (b) = t;}
constant macro ex2:) #define LINE_LEN 80
1. 상수 매크로 같은 경우에는 언어 자체에서 named constants를 지원하면 필요 없음
2. 파라미터를 넘겨주는 매크로는 동일한 C 함수보다 더 효과적임(속도면에서 월등)
3. 불행히도 C 매크로의 경우 몇몇 제한이 있어 고통받음 : 괄호 등을 제대로 명시해주지 않을 경우 의도와 다르게 컴파일러가 해석함
- 현대 언어들과 컴파일러들은 거의 매크로를 버렸다고 생각하면 됨(안쓰는 방향 지향)
1. Named constants가 매크로보다 더 type-safe하고 구현하기 쉬움
: In C++, const int a = 3;
2. 인라인 서브루틴은 파라미터를 넘겨주는 매크로(함수형 매크로)의 성능(속도)을 제한 없이 제공한다
: 몇몇 언어들은(Scheme, Common Lisp) 언어에서 매크로를 통합하여 더 안전하고 일관된 방법으로 제공한다
3. 단 MAX example과 같은 문제는 아직 해결하지 못함
+ 번외:) 자바에서의 람다 표현식
- 람다식을 이용해서 함수형 인터페이스의 코드를 줄이는 방법 확인
- 자바의 쓰레드를 사용하는 한 가지 방법은 Runnable을 구현하는 것
Runnable Interface
// In JAVA
interface Runnable{
void run();
}
- Runnable을 구현하는 클래스는 쓰레드 클래스의 생성자로 전달되어 실행 가능
클래스로 구현
// In Java
class ThreadRunnable implements Runnable{
@Override
public void run(){
System.out.println("Thread runs...");
}
}
Thread t = new Thread(new ThreadRunnable());
t.run(); // 쓰레드 실행
익명 클래스로 구현
// In Java
Thread t = new Thread(new Runnable(){
class ThreadRunnable implements Runnable{
@Override
public void run(){
System.out.println("Thread runs...");
}
} );
t.run(); // 쓰레드 실행
람다식으로 구현
Thread t = new Thread(() ->
System.out.println("Thread runs..."));
t.run(); // 쓰레드 실행
'Computer Science > 프로그래밍언어론' 카테고리의 다른 글
[Programming Language Pragmatics] 06 Control Flow(2) (2) | 2020.12.06 |
---|---|
[Programming Language Pragmatics] 06 Control Flow(1) (0) | 2020.10.25 |
[Programming Language Pragmatics] 01 Introduction (0) | 2020.10.19 |
[Programming Language Pragmatics] 03 Names, Scopes, and Bindings(2) (2) | 2020.09.18 |
[Programming Language Pragmatics] 03 Names, Scopes, and Bindings(1) (3) | 2020.09.11 |