2020년 4월 11일 토요일

Cancellation and Exceptions in Coroutines 요약

1. Coroutines: First things first

- CoroutineScope :  launch나 async로 실행행된 코루틴을 참조. Job이 있는 경우 scope.cancel()을 통해 코루틴을 취소할 수 있다. 이때 관련된 모든 코루틴들(children)이 취소된다.

- Job : 코루틴에의 핸들. 코루틴을 생성하면 리턴되는 값. 코루틴의 lifecycle을 관리한다. 특정 코루틴만 취소하고 싶은 경우에는 코루틴이 리턴하는 Job의 cancel()을 통해 그 코루틴만 취소할 수 있다.

- CoroutineContext : Job, CoroutineDispatcher, CoroutineName, CoroutineExceptionHandler를 통해 코루틴의 행동을 정의하는 context로서 Job을 통해 코루틴의 lifecycle을 관리하고 CoroutineDispatcher를 통해 어떤 쓰레드에서 작업을 실행할지 지정하고 CoroutineName은 코루틴의 이름을 의미하고 CoroutineExceptionHandler를 통해 uncaught exception을 처리한다.

- Job lifecycle : 정상적인 경우 다음의 상태를 가진다. New -> Active -> Completing -> Completed. Active나 Completing에서 cancel이나 fail이 발생하면 Cancelling 상태로 변경되고, 이 Job과 관련된 모든 코루틴들의 작업이 끝나면 Cancelled로 변경된다.

- Parent context = Defaults + inherited CoutineContext + arguments
(Defaults : CoroutineDispatcher=Dispatchers.Default, CoroutineName="coroutine")

- New coroutine context = parent CoroutineContext + Job()

2. Cancellation in coroutines

- Job을 취소할 때 사용하는 함수인 cancel()에는 parameter로 CancellationException을 받을 수 있다. 취소에 대한 좀 더 자세한 정보를 알리고 싶을 때 사용한다.
Job이 취소되면  job은 자신의 parent에게 exception을 알린다. parent는 이 exception이 CancellationException이면 무시한다.

cancel() 함수를 호출한다고 해서 코루틴이 하던 작업이 바로 취소되는 것은 아니다.(cooperative) 따라서, 작업이 끝날때까지 무한 루프를 도는 코루틴이라면 중간에 취소 여부를 확인해주지 않으면 작업이 끝날때까지 동작하게 된다.

- 중간에 취소여부를 확인하기위해 다음의 4가지 방법을 사용할 수 있다. suspend function, job.isActive, ensureActive(), yield()
모든 suspend 함수는 cancellable하기 때문에 이 지점에서 취소 여부에 따라 중단될 수 있다.
if 문에서 isActive를 통해 상태를 확인할 수 있다. 이를 편하게 해주는 ensureActive() 함수를 제공하고 있다. yield는 코루틴의 상태가 cancelled나 completed이면 CancellationException을 throow한다.

- 코루틴으로부터 결과를 기다리는 두가지 방법 : Job.join(), Deferred.await()
Job.join() : 코루틴의 작업이 끝날때까지 기다린다.
Deferred.await() : async { ... }의 리턴에 대해 await를 호출하면 async의 작업이 끝날때까지 기다린다. cancell된 deferred에 대해 await를 호출하면 JobCancellatingException을 throw한다.

- 코루틴이 취소되었을 때 특정한 action을 실행하는 방법
- isActive를 통해 작업을 끝내고 clean up을 진행한다.
- 작업이 취소되면 CancellationException이 호출되므로 try catch finally를 사용한다.
=> clean up 할 때 suspend 함수를 사용하면 안된다.(suspend 함수는 cancelled 상태에서는 실행되지 않는다. 굳이 하고 싶으면 withContext(NonCancellable)을 사용한다.)

3. Exceptions in Coroutines

- Exception 전달 경로
child coroutine에서 exception이 발생하면 이 정보가 parent에게 전달된다. -> parent는 모든 children coroutine을 취소한다. -> exception가 다시 상위(parent의 parent)로 전달된다.

- 모든 children이 취소되지 않고 나 자신만 취소되게 하려면 SupervisorJob을 사용한다. 이 경우 취소된 child coroutine만 취소되고 다른 child들은 취소되지 않는다.

- Exception 처리하기

-- launch : exception이 발생하면 바로 throw된다. 따라서, 코드에서 try catch를 사용한다.
-- async

--- root coroutine일 경우에는 exception이 바로 throw 되지 않고 나중에 await를 실행할 때 throw 된다. 따라서, await() 주위에서 try catch를 사용한다. 또한 이때 SupervisorJob을 사용행야 한다. Job을 사용하면 exception이 parent로 전달되어 catch가 동작하지 않게 된다.
--- 다른 코루틴 안에서 async가 실행될 경우에는 exception이 바로 throw 된다.

- CoroutineExceptionHandler의 실행 조건(둘 다 성립해야 한다.)
-- exception을 throw하는 코루틴에서 실행된다.(work with launch, not async)
-- root coroutine이거나 CoroutineScope의 CoroutineContext안에 있는 경우

4. Coroutines & Patterns for work that should't be cancelled

- Coroutine vs WorkManager
process가 있는 동안만 동작해야 하는 경우에는 Coroutine을 사용하고 process와 상관없이 background에서 동작해야 하는 경우에는 WorkManager를 사용한다.

- 취소되지 않아야 하는 작업의 경우를 위해 Application 레벨에서의 CoroutineScope을 만들어서 사용한다.
- GlobalScope, ProcessLifecycleOwner, NonCancellable은 사용하지 않는다.

2020년 1월 18일 토요일

Crafting Interpreters - 13. Inheritance 를 읽고

http://craftinginterpreters.com/inheritance.html

- Superclass and Subclass
기존의 class에 상속을 위한 super class 관계 더하기
method를 찾을 때 현재 class에 없으면 super class에서 찾는다.

- super keyword
superclass에 있는 함수를 사용하려면? super.method()로 호출한다. 이 경우 어떤 superclass의 함수를 사용해야 할까? => super.method()를 실제로 부르는 함수의 superclass를 호출해야 한다.
이전에 어떤 environment에서의 값을 사용해야 하는지를 알기 위해 사용했던 방법이 무었이었지? superclass를 찾는 경우에도 마찬가지로 Resolver를 통해 필요한 위치를 기억해 놓도록 한 후, 나중에 함수가 실행 될 때 environment에 이 super를 넣어준다.

Crafting Interpreters - 12. Classes 를 읽고

 http://craftinginterpreters.com/classes.html

기존에 되어 있는 것에서 class를 추가만 하면 된다.
가장 먼저 class 선언 부분을 LoxClass를 통해 추가하고 instance를 생성하는 부분은 LoxInstance를 통해 생성한다.

- Instance의 property 찾기(get)
someObject.someProperty의 형태이므로 someObject를 찾고 여기의 someProperty를 찾는다.

- Instance의 property에 값 설정(set)
someObject.someProperty = value 의 형태이므로 위 get에서 한대로 someObject의 someProperty를 찾고 여기에 value를 설정한다.

- Instance에서 method 찾기
LoxClass에 method들을 저장해 놓고, LoxInstance에서 someObject.some 으로 찾을 때 get 함수에서 먼저 property가 있는지 확인해보고 없으면 method가 있는지 확인해 본다.
property는 LoxInstance에 저장되어 있고 method는 LoxClass에 저장되어 있다.

- This
Resolver에서 this를 찾을 위치를 저장해 놓는다.
LoxInstance에서 method를 찾아서 실행할 때 environment에 this도 있어야 하므로 새로운 LoxFunction을 만들면서 여기에 this를 포함하고 있는 environment를 넘겨준다.

- Constructor
init 함수를 constructor로 사용하자. Class에 선언된 함수중 init 함수가 있으면 이를 constructor로 사용한다. init 함수의 경우에는 return이 this가 되도록 한다.


2020년 1월 11일 토요일

Crafting Interpreters - 11. Resolving and Binding 을 읽고

http://craftinginterpreters.com/resolving-and-binding.html

지금까지의 내용을 가지고 다음의 코드가 어떤 결과를 표시할 지 생각해보자.

var a = "global";
{
  fun showA() {
    print a;
  }

  showA();
  var a = "block";
  showA();
}

결과는 다음과 같다.

global
block

뭐가 문제일까?

앞장에서 함수에 Environment를 적용할 때 함수가 실행될 때마다 Environment를 새로 생성했다. 그리고, 함수안에서 사용하는 변수의 값을 찾을 때 현재 Environment로부터 부모 Environment로 점차로 찾아가는 형태로 구현을 했다.

여기의 어떤 점이 문제였을까?

위의 코드에서 showA()를 선언했을 때의 Environment를 살펴보자.

global environment(a="global") <- block environment() <- showA environment()

따라서, 처음 showA(); 를 호출할 때는 global environment의 a가 찾아진다. 그런데 두번째 a인 var a = "block"; 가 실행되면서 block environment에 a="block" 가 추가된다. 이 다음에 showA() 를 호출하면 앞에서처럼 점진적으로 Environment를 찾아갈 텐데 block environment에 a가 추가되었기 때문에 여기의 a를 찾게 되고 이 a 의 값이 출력되게 된다.

위의 문제를 해결하려면?
아래와 같은 코드가 있을 때 1과 2에서의 scope가 실제로는 같지 않아야 함을 의미한다.
{
  var a;
  // 1.
  var b;
  // 2.
}

그렇다면, 이 문제를 어떻게 해결할 수 있을까?

첫번째로는 변수 선언이나 함수 선언의 경우마다 새로운 scope를 생성하는 것이다.(이전에는 기존의 Environment에 새로운 선언을 바로 추가했다.)

두번째로는 Semantic Analysis를 사용하는 것이다.

이 방법은 block과 function의 경우 새로운 scope를 생성하고, variable이나 assignment가 있는 경우 어떤 scope에 있는 값이 사용되어야 하는지를 미리 설정해 놓고 나중에 이 값을 사용한다.

우리는 두번째 방법으로 구현을 변경해 볼 것이다.
-> parser를 통해 나온 결과를 바로 interpreter에 보내지 않고 이 사이에 Resolver를 통해 Semantic Analysis를 한 후 interpreter에서 변수를 사용할 때 이 정보를 사용한다.

2020년 1월 8일 수요일

Crafting Interpreters - 10. Functions 를 읽고

http://craftinginterpreters.com/functions.html

함수에서의 return을 위해 exception을 사용한다.
함수 시작시 Environment를 새로 할당하여 시작한다.
함수가 선언되는 시점의 Environment를 저장하고 있다가 함수 시작시 새로 할당되는 Environment의 parent로 설정해준다.


Crafting Interpreters - 9. Control Flow 를 읽고

http://craftinginterpreters.com/control-flow.html

Turing Machines

if, logical operator(and와 or), while, for 를 추가



for 문 같은 경우 while 문으로 쉽게 변경이 가능하다. 따라서, 내부적으로는 for 를 while 로 변환해서 사용할 수 있다. 이러한 것을 syntactic sugar 라고 한다.




Generic interfaces 요점

 https://go.dev/blog/generic-interfaces  Generic interface를 정의할 때 최소한의 제약만을 정의하고 실제 구현체들이 자신만의 필요한 제약을 추가할 수 있도록 하는 것이 좋다. pointer receiver를...