2025년 7월 12일 토요일

Generic interfaces 요점

 https://go.dev/blog/generic-interfaces

 Generic interface를 정의할 때 최소한의 제약만을 정의하고 실제 구현체들이 자신만의 필요한 제약을 추가할 수 있도록 하는 것이 좋다.
pointer receiver를 사용해야 하는 복잡도가 증가하는 상황일 때 한번 더 생각해보자. 반드시 필요한 작업인지.

Do not over-engineer things.

 

2025년 7월 4일 금요일

Building Replication-Safe LSM Trees in Postgres 요점

 https://www.paradedb.com/blog/lsm_trees_in_postgres

 

 pg_search에 LSM 트리를 사용. 문제는 postgres의 replication 사용시 LSM 트리가 안전하지 않음
primary에서 VACUUM 동작시 에서 사용중인 튜플도 삭제할 가능성이 있음.
standby에서 주기적으로 사용중인 최저 트랜잭션 ID를 primary에게 알려주는 hot_standby_feedback을 사용하여 문제 해결

 

Using Rust async for Query Execution and Cancelling Long-Running Queries 요점

 https://datafusion.apache.org/blog/2025/06/30/cancellation/

 

tokio의 특성(Cooperative)상 poll이 제어권을 넘겨야 런타임이 다음 과정을 진행할 수 있다.
따라서 poll 안에서 pending이 될 때까지 무한 루프를 도는 경우에 문제가 발생할 수 있다.
이에 대한 해결을 위해 tokio 전체에 대해 budget을 두고 이 값이 0이 되면 무조건 pending을 발생시키는 기능이 tokio에 들어가 있다.

  

2022년 2월 9일 수요일

Building asynchronous views in SwiftUI 정리

self loading views
View model 사용하기
Combine을 사용한 AnyPublisher
refreshable with error : async/await
publisher에 의한 data를 감지하고 싶을 때 onReceive를 사용할 수 있다.

2022년 2월 4일 금요일

State management in SwiftUI 정리

State는 View의 내부 정보를 저장하고 싶을 때, State의 값이 변경되었을 때 View를 즉시 업데이트 하고 싶을 때 사용한다.
Binding은 State로부터 값을 받고, 이 값을 바꾸었을 때 기존 State도 같이 바뀌도록 하고 싶을 때 사용한다.

위 State와 Binding은 View 내에서 사용한다. 외부에서 같은 동작이 일어나도록 하고 싶으면 ObservableObject와 ObservedObject를 사용한다.

ObservableObject내에 notification을 받고 싶은 property를 Published로 선언한다. 또한, 주의해야 할점은 ObservedObject의 초기화가 View의 initializer에서 일어나야 한다는 것이다. 그렇게 하지 않으면 rerendering이 일어날 때마다 매번 새로 초기화될 수 있다. 이에 대한 해결로 iOS14에서 StateObject가 새로 나왔다. StateObject는 rerendering이 일어날 때 새로 초기화되지 않는다.

Parent와 바로 아래 children의 관계라면  Binding을 사용하는 것이 보통이다. 그러나, 만약 한창 아래의 view 계층에 있는 view에 environment를 전달하고 싶으면 EnvironmentObject를 사용한다. 상위의 view에서 .environmentObject(value)를 호출해주면 그 view 이하의 view에서 EnvironmentObject의 선언을 통해 값을 사용할 수 있다.
또는, EnvironmentKey를 사용한다. custom EnvironmentKey를 선언하고 EnvironmentValues에 관련 값의 get/set을 구현한다. 이 값은 아래와 같은 식으로 사용한다.

// EnvironmentValue에 선언된 값이 theme인 경우
@Environment(\.theme)

EnvironmentObject는 상위 view에서 값을 지정해주지 않으면 런타임에 크래시가 일어나고 EnvironmentKey는 컴파일 타임에 초기값을 지정해 주어야 하는 차이가 있다.

List에 Binding 사용하기
TabView와 NavigationView 에서 EnvironmetObject등 사용하기
View가 observe 하는 방식
Model - View 간의 연결에 Model을 변환해야 하는 경우
: View는 정렬이 된 item list가 필요하다.

1. item 리스트가 필요할 때마다 원 모델을 정렬한다.
 - 값이 필요할 때마다 정렬을 하게 된다.(불필요한 작업이 될 수 있다.)
2. View 생성시 정렬된 item 리스트를 가지고 있는다.
 - rerendering 마다 정렬이 새로 이루어진다.
3. ViewModel 안에 정렬된 item 리스트를 넣어둔다. 원 모델 변경시 마다 정렬된 item 리스트를 전달할 수 있다.
View 를 dismiss 하는 방법

1. @State와 @Binding 의 연결을 통한 isPresented 값 변경
2. @Environment(\.presentationMode) var presentationMode 의 사용
3. @Environment(\.dismiss) private var dismiss 의 사용
  - dismiss는 callAsFunction 이다.

2022년 1월 21일 금요일

[요약] Android Touch System — Part 1: Touch Functions and the View Hierarchy

https://proandroiddev.com/android-touch-system-part-1-touch-functions-and-the-view-hierarchy-1f6526e55d78

Android Touch System — Part 1: Touch Functions and the View Hierarchy


터치 스크린상에서의 모든 움직임은 MotionEvent로 알려진다.

MotionEvent는 다음의 값들을 가진다.

action : 수행한 action의 타입
x : 터치한 x좌표, view에 상대적인 위치
y : 터치한 y좌표,  view에 상대적인 위치
rawX : 터치한 절대 x좌표, 디바이스 화면에 상대적인 위치
rawY : 터치한 절대 y좌표, 디바이스 화면에 상대적인 위치
eventTime : 이벤트가 발생한 시간, SystemClock.uptimeMilllis()

안드로이드 화면은 좌상단이 (0, 0)이고 우하단이 (maxX, maxY)이다.

action은 다음의 값을 가진다.


ACTION_DOWN : 터치가 처음으로 일어났을 때
ACTION_UP : 터치를 화면에서 떼었을 때
ACTION_MOVE : ACTION_DOWN과 ACTION_UP 사이에 터치가 이동할 때
ACTION_CANCEL : 현재 터치가 취소될 때. parent view가 child view의 이벤트를 인터셉트할 때 발생한다.

이벤트의 흐름


motion event가 발생하면 root view(예: Activity)로부터 중간에 인터셉트당하지 않으면 가장 하위의 view까지 내려간다. 내려갈 때, view의 dispatchTouchEvent()가 호출된다. 여기 안에서 onInterceptTouchEvent()가 호출되는데 false를 리턴하면 아래로 계속 내려가지만 true를 리턴하면 더이상 아래로 내려가지 않는다.(이벤트는 여기서 소비된다.) 이벤트가 leaf view까지 내려가거나 onInterceptTouchEvent()가 true를 리턴해서 더이상 아래로 내려가지 않으면 다시 위로 올라가면서 onTouchEvent()가 호출된다. onTouchEvent()가 true를 리턴하면 이벤트는 여기서 멈춘다.

dispatchTouchEvent()

View.dispatchTouchEvent : view는 children을 갖지 않는다. 따라서, 구현은 간단하다. onTouchEvent()를 호출하고, touch listener를 view에 설정한다. 이 리스너중 하나라도 true를 리턴하면 true를 리턴한다.
custom view는 dispatchTouchEvent 보다는 onTouchEvent를 override 하는 것이 좋다.

ViewGroup.dispatchTouchEvent : onInterceptTouchEvent()를 호출한다. 여기서 false를 리턴하면 추가된 반대의 순서로 child view들을 순회한다. 이중 터치가 child view의 안에서 발생한 것이면 child.dispatchTouchEvent()를 호출한다. 여기서 false를 리턴하면 다음 child에서 child.dispatchTouchEvent()를 호출한다.
ViewGroup은 dispatchTouchEvent 보다는 onInterceptTouchEvent를 override 하는 것이 좋다.

ScrollView.dispatchTouchEvent : ViewGroup과 같다.

Activity.dispatchTouchEvent : 자식들의 dispatchTouchEvent()를 호출한다. 

onInterceptTouchEvent()


View.onInterceptTouchEvent : 없다.

ViewGroup.onInterceptTouchEvent : 기본 구현은 false를 리턴한다. 이 함수를 override 하는 주 목적은 특정한 타입의 터치 이벤트만 다루고 나머지는 자식들이 다루도록 하기 위함이다. 예를 들어 ScrollView는 스크롤은 직접 다루고, 클릭 같은 것은 자식이 다루도록 한다.

ScrollView.onIntreceptTouchEvent : 이벤트가 ACTION_MOVE이고, velocity가 충분하면 true를 리ㅣ턴한다. 그리고 자식들은 ACTION_CANCELLEDD를 받게 된다. 또한 requestDisallowInterceptTouchEvent를 호출한다.

Activity.onInterceptTouchEvent : 없다.

onTouchEvent()


View.onTouchEvent : view가 clickable 하면 기본은 true를 리턴한다. overriding할거면 super.onTouchEvent()를 호출해주는 것이 좋다. 또는 click gesture만 다룰거라면 performClick()를 override하는 것이 좋다.

ViewGroup.onTouchEvent : View의 onTouchEvent와 같다.

ScrollView.onTouchEvent : event의 정보를 통해 얼마나 스크롤했는지를 알아내서 스크롤을 수행한다. 스크롤과 관련된 애니메이션도 다룬다. 또한 requestDisallowInterceptTouchEvent()를 호출한다.

Activity.onTouchEventv : 기본 구현은 항상 false를 리턴한다.

requestDisallowInterceptTouchEvent()

ViewParent 인터페이스의 함수. parent나 ancestor view가 터치 이벤트를 인터셉트하지 못하도록 할 때 사용한다.

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 라고 한다.




2019년 12월 31일 화요일

Complex UI/Animations on Android 정리

https://proandroiddev.com/complex-ui-animation-on-android-8f7a46f4aec4

1. 스크롤 할 때 툴바에 애니메이션 효과 주기

AppBarLayout의 behavior에 내가 사용하려는 애니메이션 효과를 가지는 커스텀 CoordinatorLayout behavior을 지정해준다.

아래와 같은 클래스를 만들고 onStartNestedScroll(스크롤이 시작할 때 호출되는 함수)과 onNestedScroll(스크롤중일 때 호출되는 함수)을 구현한다.

class ToolbarBehavior : CoordinatorLayout behavior<AppBaLayoutr>() { ... }

- onStartNestedScroll

위 아래로의 스크롤(vertical)일 때 true를 리턴한다.

- onNestedScroll

dyConsumed의 값이 때라 scroll up인지 down인지 파악한다. dyConsumed가 양수이면 scroll down이고, dyConsumed가 음수이면 scroll up임을 의미한다.

height를 변경하고 나면 requestLayout을 호출해 주어야 한다.
위치를 바꿀 때 translationX와 translationY를 사용한다.
scale을 적용할 때 scaleX와 scaleY를 사용한다.

추가로 ToolbarBehavior의 효과를 적용할 때 관련 뷰들을 참조하고 있어야 한다. 따라서, onStartNestedScroll이나 onNestedScroll이 호출되기 전에 이러한 관련 값(Views, height, 최소 height등)들을 미리 설정해 놓고 있어야 한다.

2. RecyclerView의 item의 height 변경할 때 애니메이션 효과 주기

아이템의 평상시 height와 커졌을 때의 height를 계산해놓고 있다가 사용자가 아이템을 클릭할 때 이에 맞추어 애니메이션 효과를 주는 것이 필요하다.

- Adapter의 onViewAttachedToWindow에서 height를 계산한다.

onViewAttachedToWindow는 Adapter에서 만든 뷰가 RecyclerView에 연결될 때 호출되는 함수이다.

- 평상시 height와 커졌을 때의 height를 계산하는 방법은 다음과 같다.

doOnLayout에서 평상시 뷰의 height를 계사한다. -> expanded view를 visible로 설정한다. -> doOnNextLayout에서 expanded view의 height를 계산한다. -> expanded view를 hide한다.

위와 같이 하면 사용자에게 뷰가 커졌다 작아졌다 하는 것을 보여주지 않고 각각의 height를 알 수 있게 된다.(이유는? : 아마도 onDraw가 불리기 전에 관련 layout이 실행되는 동안 height를 알아내서 사용자에게는 안보이게 되는 것이 아닐까 추측. 정확하게는 모르겠네요.) 여기서 한가지 더 주의할 것은 expanded view의 hide를 호출하는 부분을 expandedView.post { ... } 안(또는 doOnPreDraw에서)에서 해주어야 한다는 점이다.(expandedView.visible = false는 layout이 일어나도록 하지 않는다.)(post나 doOnPreDraw를 사용하면 onDraw가 불리기 전에 layout이 다시 일어나게 되는 걸까요? 이부분도 잘 모르겠군요.)

아이템을 클릭하면 이전에 커진 뷰가 있다면 원래 크기로 돌려야 하고 새로 클릭된 뷰를 크기를 키워야 한다. RecyclerView의 findViewHolderForAdapterPosition를 통해 이전에 커진 뷰를 찾을 수 있다.

ValueAnimator를 사용해서 점진적으로 커지는 애니메이션 효과를 줄 수 있다.

3. ViewPager2의 위치가 변경될 때 관련 탭의 위치도 같이 변경하기

ViewPager의 onPageScrolled에서 position과 positionOffset을 받아서 탭의 위치를 변경한다.(이 문서에서는 RecyclerView의 scrollBy를 사용해서 탭의 위치를 변경했는데 RecyclerView의 layout manager가 LinearLayoutManager라면 LinearLayoutManager의 scrollToPositionWithOffset를 사용하는 방법도 있다. 이게 더 간단한 방법인듯 하다.)

4. Filter sheet animation

네 단계로 나누어진 애니메이션 효과를 준다. 각각의 애니메이션이 동작하는 순서를 위해 AnimationSet을 사용한다.

a. Fab 버튼의 path animation
b. RecyclerView의 아이템들을 shrink하는 scale down animation
c. Fab 버튼이 Filter sheet로 변경되는 animation
d. 최종 뷰들 보여주기

각각을 자세히 살펴보자.

a. Fab 버튼의 path animation

현재 위치에서 화면의 중간으로 이동한다.
애니메이션을 위해 ValueAnimator를 사용하는데 이때의 progress를 ArcMetric를 사용해서 변경된 형태로 뷰의 x와 y에 적용한다.

b. RecyclerView의 아이템들을 shrink하는 scale down animation

ValueAnimator를 써서 현재 화면에 보이는 아이템들을 scale down 한다.

c. Fab 버튼이 Filter sheet로 변경되는 animation

Fab 버튼의 크기를 최종 뷰의 크기로 변경한다. 이때 0 ~ 0.8 까지는 원의 형태로 커지다가 0.8 ~ 1 로는 사각형으로 커지게 한다.

d. 최종 뷰들 보여주기

개별 뷰들을 화면에 보여준다.

5. Filtering animation

여섯 단계로 나누어진 애니메이션이다.

a. Fab Collapse Animation
b. Bottom Bar Animation
c. Tabs and ViewPager fade out animation
d. Close Icon rotate animation to simulate loading

rotation값을 변경한다.

e. Fab Arc Path animation
f. RecyclerView items scale down animation

Crafting Interpreters - 8. Statements and State 를 읽고

http://craftinginterpreters.com/statements-and-state.html

Statement는 값을 내는 것이 아닌 다른 무언가(side effect - output을 내거나 state를 변경하거나 하는 것)를 하는 것이다.

변수 선언을 하면 어딘가에 저장이 되어 있어야 나중에 이 변수의 값을 사용할 수 있습니다. 이를 위해 Environment라고 하는 것이 필요하게 됩니다.
Environment는 HashMap으로 (변수 이름, 값)을 내부에 저장하고 있습니다. 현재로서는 값을 저장할때는 define, 값을 꺼내올때는 get, 값을 재할당할때는 assign정도의 함수만 가지고 있으면 되겠습니다.

변수가 선언되고 사용되는 위치에 따라 scope가 지정됩니다. 이것은 block({})으로 지정합니다.  scope에는 lexical scope과 dynamic scope이 있는데 우리는 lexical scope를 사용합니다.
가장 상위를 global scope라고 하고 이후의 코드에서 별도의 scope를 가지고 싶으면 block을 사용합니다. 이러한 경우 block 내부의 변수가 외부의 변수를 가릴 수도 있고 값을 변경할 수도 있습니다. 이에 대한 처리를 위해 Environment에 parent Environment를 추가합니다. 어떤 값을 찾을 때 자기 자신이 가지고 있지 않으면 바로 에러를 발생시키지 않고 parent Environment에게 위임하는 거지요. parent에게서 변수가 찾아지면 그 값을 사용하면 되고 여전히 찾아지지 않으면 그의 parent에게 위임합니다. 이렇게 global environment까지 찾아 보게 합니다.

2019년 12월 26일 목요일

Crafting Interpreters - 7. Evaluating Expressions 를 읽고

http://craftinginterpreters.com/evaluating-expressions.html

앞장에서 파서를 만들어 봤다. 이번 장에서는 expression의 값을 구해보자.

Lox는 dynamically typed language이므로 런타임에 타입을 체크한다. 그러면 타입은 어떻게 알 수 있을까? JVM에서 Lox를 구현하고 있으므로 JVM의 도움으로 쉽게 체크할 수 있다.(자바는 instanceOf로 코틀린은 is로 타입을 확인한다.)

또한 Lox에서 사용하는 primitive type들은 자바에서의 타입과 쉽게 매치할 수 있다.

Lox typeJava representation
nilnull
BooleanBoolean
numberDouble
stringString

나중에 함수, 클래스, 인스턴스 등을 추가하면 좀 더 복잡해질 것이다.

런타임에 에러가 발생하면 어떻게 할까?(자바라면 ClassCastException을 던지고 stack trace를 보게 될 것이다.) 에러가 발생하면 에러의 위치(line number)와 에러 메시지를 화면에 보여주자.

2019년 12월 13일 금요일

Crafting Interpreters - 6. Parsing Expressions 를 읽고

http://craftinginterpreters.com/parsing-expressions.html

앞장에서 문법을 어떻게 표현할 것인지를 살펴보고 간단한 문법을 Context-free grammar로 표시해 봤습니다.

이러한 문법 표현에서 모호함(ambiguity)이 있을 수 있습니다. 예를 들면, 1 + 2 * 3 의 경우 결과가 어떻게 나와야 할까요? 앞에서 부터 계산을 하면 결과는 9가 될 것이고, 곱하기를 먼저 한다면(우리가 수학에서 배웠듯이) 결과가 7이 될 겁니다.

이러한 모호함을 우선 순위(precedence)와 결합 법칙(associativity)을 정해서 해결할 수 있습니다.(또는 먼저 계산되어야 하는 값에 괄호를 사용할 수도 있을 겁니다.)

토큰을 분석(parsing)하는 방법에는 여러가지 방법이 있는데 여기서는 Recursive descent parsing을 살펴볼 겁니다. 파서를 만드는 가장 간단한 방법이지만 그렇다고 만만하게 볼 파서는 아닙니다. 실제로 GCC나 V8(JavaScript)이나 Roslyn(C#)등에서 사용되고 있거든요.

토큰을 분석하다 에러를 만나면 어떻게 하는 것이 좋을까요? 처음으로 만나는 에러에서 멈추고 그 부분을 표시해주는게 좋을까요? 이게 가장 간단한 방법이기도 하지요. 하지만, 사용자한테는 매번 에러가 나타날 때마다 바로바로 표시해 주는 것보다는 전체에서 발생할 수 있는 에러를 한번에 표시해주는게 좋을겁니다. 이걸 error recovery라고 부릅니다.

에러가 발생해도 계속 진행하면서 이후의 에러도 확인하려면 어떻게 해야 할까요? 에러가 발생한 지점부터 특정 지점까지는 무시했다가 그 다음부터 토큰 분석을 다시 시작해야 할겁니다. 안그러면 엄청나게 쓸데없는 에러가 표시되겠지요. 에러가 발생하면 파서는 바로 panic mode로 들어가고 다시 정상적으로 파싱을 시작할 수 있는 지점을 만나면 panic mode를 벗어나게 됩니다. 이러한 과정을 synchronizaion이라고 합니다.

2019년 12월 7일 토요일

Crafting Interpreters - 5. Representing Code 를 읽고

http://craftinginterpreters.com/representing-code.html

앞장에서 살펴본 내용은 단순한 토큰의 나열이었죠.

예를 들어 1 + 2 * 3 - 4 가 있다고 하면 1, +, 2, *, 3, -, 4 이렇게 개별 토큰의 나열을 만드는 것을 해봤습니다. 근데 이것만으로는 아무 의미가 없습니다. 계산을 한다고 할 때 앞에서 부터 계산을 하는데 중간에 곱하기가 있으면 곱하기를 먼저 한다던지 하는 어떤 법칙이 필요하죠. 이걸 표현할 필요가 있습니다. => 문법

문법을 Context-Free Grammar로 표현할 수 있고 이 문법을 통해 어떤 형태가 옳은지를 표현할 수 있습니다. 이에 맞지 않으면 틀린 표현이 되는 것이겠지요.

아래와 같이 문법에 맞는 rule들을 표시하는데 각각의 rule은 head와 body로 표시합니다.

A -> a
B -> b
(A는 a로 변환할 수 있고, B는 b로 변환할 수 있다는 의미입니다.)

컴퓨터 과학에서는 이것을 아래와 같이 Backus-Naur form의 형태로 많이 사용합니다.

expression → literal
           | unary
           | binary
           | grouping ;

literal    → NUMBER | STRING | "true" | "false" | "nil" ;
grouping   → "(" expression ")" ;
unary      → ( "-" | "!" ) expression ;
binary     → expression operator expression ;
operator   → "==" | "!=" | "<" | "<=" | ">" | ">=" 
| "+" | "-" | "*" | "/" ;

오른쪽에 있는 값들 중 "true"나 "==" 등은 더이상 쪼개 질 수 없기 때문에 terminal이라 부르고, expression이나 operator등은 다른 rule로 더 쪼개질 수 있기 때문에 nonterminal이라 부릅니다.

이 문법에 대한 data structure는 tree로 표현할 수 있습니다. 이것을 syntax tree라고 합니다.
(이처럼 보통 트리를 많이 사용합니다. 하지만, 다르게 표현할 수도 있는데요. 예를 들면, 바이트코드로 표현하는 방법이 있습니다.)

트리를 순회하면서 작업을 하려면 어떻게 하는게 좋을까요? 이와 관련하여 Expression problem이라는 것이 있습니다.
(이와 관련한 내용은 다음의 링크를 참고: http://www.haruair.com/blog/3338)

syntax tree를 보기 좋게 표현할 수 있으면 좋겠죠?
트리를 텍스트로 바꾸어 보기 좋게 표현해 주는 것을 pretty printer라고 부릅니다.
(이와 관련한 내용은 다음의 링크를 참고: https://jooyunghan.gitbooks.io/a-prettier-printer-kr/content/chapter1.html#sec1)



Crafting Interpreters - 4. Scanning 을 읽고

http://craftinginterpreters.com/scanning.html

인터프리터의 간단한 동작 단계는 scanning -> parsing -> evaluating code 로 말 할 수 있습니다. 4장에서는 이중 scanning에 대해 설명합니다.

다음과 같은 코드가 있다고 하면,

var language = "lox";

이 코드는 다음의 5개의 토큰으로 분할됩니다.

var, language, =, "lox", ;

이러한 개별 토큰들의 타입을 TokenType으로 분류합니다. 분류된 토큰들은 이후에 parsing 단계에서 사용되어야 하는데 그 때 필요한 정보들을 저장하고 있어야 하겠죠.

그래서,  Token class를 만듭니다. 이 class는 다음의 정보들을 저장하고 있습니다.

type: 토큰의 타입(TokenType)
lexeme: 토큰 string
literal: 토큰의 값
line: 토큰이 있는 소스의 라인값으로 에러 표시에 사용합니다.

lexeme과 literal의 차이에 대한 예

"lox"의 경우 lexeme은 "lox"이고, literal은 "를 제외한 lox 이다.
1234 의 경우 lexeme은 1234이고, literal은 double값인 1234.0이다.

토큰을 나눌 때 주의사항

orchid의 경우 or과 chid로 분리할 것인가 orchid로 분리할 것인가: 긴 토큰으로 분리하는 형태로 한다. - maximal munch

2019년 11월 25일 월요일

The Mystery of Mutable Kotlin Collections 를 읽고

https://proandroiddev.com/the-mystery-of-mutable-kotlin-collections-e82cbf5d781

Kotlin에서 MutableList는 List는 아래와 같다.

public interface Mutable:ist<E> : List<E>, MutableCollection<E> {}
public interface List<out E> : Collection<E> {}

List: read-only access
MutableList: read/write access

- MutableListOf(...)나 listOf(...)를 통해서 만들어진 리스트는 MutableList로 인식된다.
- List 인터페이스를 Kotlin으로 직접 구현하면 MutableList로 인식하지 않는다.

위처럼 되는 이유는?
Kotlin에서의 List는 mock interface이기 때문이다.
Kotlin에서의 List는 컴파일시 사라지고 Java에서의 List로 변환된다.

2019년 11월 22일 금요일

Public API challenges in Kotlin 요약

https://jakewharton.com/public-api-challenges-in-kotlin/

코틀린의 data class 만이들 사용하실 겁니다. 자바에서 일일이 코딩해야 되는 내용을 매우 간단하게 만들어 주니까요. 그런데
이 data class를 쓰는 경우 바이너리 호환에 주의해야 합니다. 클래스의 내부 필드에 변경이 있는 경우 바이너리 호환이 안될
수가 있기 때문입니다.

1. 새로운 필드를 중간에 추가하는 경우 코틀린에서 제공하는 ComponentN()
함수가 맞지 않게 됩니다. 따라서, 새로운 필드를 추가하는 경우에는 항상 마지막에 추가해야 합니다.
2. data class의 경우 copy 함수를 자동으로 생성해 줍니다. 그런데 필드가 새로 추가되는 경우 copy()의 signature가 바뀌어
버리는 문제가 발생할 수 있습니다. 결국 바이너리 호환이 필요한 경우라면 data class를 쓰기보다는 직접 관련 함수 구현을 다
해주는 것이 좋겠습니다.
3. class 생성시 자유도를 주기위해 builder 패턴을 많이 사용합니다. 이 경우 필드에 @set:JvmSynthetic 를 사용해서 get은
노출하지만 set은 노출하지 않을 수 있습니다.
코틀린의 경우에는 top-level function이나 DSL을 통해 인스턴스를 생성하는 방법을 많이 사용합니다. 이 경우 자바에서는
바이너리 호환 문제가 발생할 수 있으므로 @JvmSynthetic을 사용해서 자바쪽에 노출이 되지 않도록 해줍니다.

Generic interfaces 요점

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