티스토리 뷰

개요

 

reflection 이라는 개념이 처음에는 어려웠다.

Rob Pike 글을 따라가며 좀더 체계적으로 접근하며 이해해본다.

 

- 링크: https://blog.golang.org/laws-of-reflection

- 번역본 링크: https://johngrib.github.io/wiki/golang-the-laws-of-reflection/

 

Types and interfaces

 

예제 링크: https://play.golang.org/p/lcztLbvaUJj

 

간단히 몸을 풀어가자.

 

MyInt 타입이 실제 int 값을 담더라도 MyInt int 엄연히 다른 타입이다.

- 따라서 타입의 변수간에는 할당, 비교등의 연산이 불가능하다.

- type casting 해줘야만 연산이 가능해진다.

 

 

예제 링크: https://play.golang.org/p/TpR-X4Mt42E

 

인터페이스 타입은 매우 독특한 녀석이다.

 

1) 고정된 메쏘드들의 집합으로 표현된다.

Reader 인터페이스는 Read() 라는 메쏘드

Writer 인터페이스는 Write() 라는 메쏘드를 가지고 있다.

 

2) 인터페이스의 메쏘드들을 구현하기만 했다면 어떠한 concrete type 담을 있다

duck typing!

 

왼쪽 코드를 보자

 

1) r 변수의 타입은 io.Reader 이다.

- 말은 r 에는 메쏘드 Read() 구현된 타입의 값이라면 무엇이든 넣을 있다는 것이다.

 

2) r 다양한 타입을 넣어본다.

*os.File 타입

*bufio.Reader 타입

*bytes.Buffer 타입

헷갈리지는 말자. Go 정적 타입 (statically typed) 언어이다. 컴파일시에 타입이 정해진다는 말이다.

따라서 r 타입은 io.Reader 이다.

 

3) 타입 값들이 들어간다는 것은 타입들의 메쏘드 중에 Read() 있다 것을 의미한다.

예제링크: https://play.golang.org/p/Dkjg874ooH1

 

interface{} 타입

 

empty interface type 이다.

어떤 type 이건 0 이상의 메쏘드를 가지고 있으니, 타입에는 어떤 값이든 넣을 있다.

 

심지어는 인터페이스에 저장된 값의 타입이 바뀌더라도 문제없다
이런 것들을 명확해 해둬야 하는 이유는 reflection interface 밀접하게 연관되어 있기 때문이다.

 

인터페이스가 다양한 타입을 담을 있다고 동적 타입 (dynamically typed) 언어라고 헷갈리지 말자.

인터페이스를 예로 들면 다양한 타입의 값이 들어갈 있다는 것이지,

인터페이스 타입이라는 자체는 변하지 않는 것이다.

 

그렇기에 인터페이스 타입의 변수가 계속 다른 타입의 값을 받을 있는 것이다.

 

 

The representation of an interface

 


예제 링크: https://play.golang.org/p/rh_dbWVLMo5

 

 1) os.OpenFile() 리턴 타입은 *os.File error 이다.

- 타입은 엄청나게 많은 메쏘드를 가지고 있다.


 

2) 그런 tty r 이라는 io.Reader 타입에 넣으면

- r 사용할 있는 메쏘드는 Read() 뿐이다.

 

3) 하지만 r 담고 있는 실제 값인 *os.File 타입의 tty 훨씬 많은 메쏘드를 감추고 있는거다.

그래서 w 라는 io.Writer 타입에 r io.Writer type assertion 해서 넣으면

- w Write() 메쏘드를 있는 녀석이 된다.

 

4) empty interface type 넣을 수도 있다. 이때는 별도의 type assertion 필요없다.

 

가지 리플렉션의 법칙을 챙겨보며 리플렉션에 대한 개념을 단단히 세워보자

 

번째 리플렉션 법칙

리플렉션은 인터페이스 리플렉션 오브젝트 간다.

 

기본적으로 리플렉션은 인터페이스 안에 들어있는 타입과 값을 검사하는 메커니즘이다.

 

예제 코드:  https://play.golang.org/p/7ip6vIUbpnS

 

reflect 패키지에는 크게 개의 타입이 있다. Type Value

 

reflect.TypeOf() 먹여주면 reflect.Type 리턴되고 (interface type 가져온다고 보면 되려나)

reflect.ValueOf() 먹여주면 reflect.Value 리턴된다. (interface value 가져온다고 보면 되려나)

 

메쏘드 모두 파라미터로 interface{} 가진다.

따라서 무슨 값을 넣든 interface{} 타입 컨버젼이 일어나게 된다.

 

 reflect.Value.Type() 다시 reflect.Type 인건 안비밀

예제 코드: https://play.golang.org/p/t4MzxrQiQWk

 

reflect.TypeOf() 써보자

 

1) x 라는 float64 값을 reflect.TypeOf(x) 넣어서 출력해보면

2) float64 라는 타입의 값이 제대로 찍힌다.

예제 코드: https://play.golang.org/p/BLtf1rAD-6g

 

reflect.ValueOf() 먹여주면 뭐가 나올까?

 

v 출력한 값보다 코드에서는 v.String() 좀더 의미있는 값이랄 있겠다.

그냥 v 출력하면, fmt 패키지가 기를 쓰고 파고 들어가

진짜 (concrete value inside) 찾아내어 출력하기 때문이다

 

 

예제 코드: https://play.golang.org/p/Ya488QY6bhE

 

reflect.Type reflect.Value 메쏘드들

 

reflect.Type 이라는 타입과 reflect.Value 라는 타입은

타입이니깐 메쏘드를 가질 있고, 또한 가지고 있는데

메쏘드들을 이용하면 녀석들을 들여다보고, 조작할 있다.

 

1) reflect.Value Type() 이라는 메쏘드는 reflect.Type 리턴해준다. 요거 밑줄쫙

2) reflect.Type reflect.Value 라는 녀석 둘다 가지고 있는 Kind() 라는 메쏘드는

상수값 (constant) 리턴해주는데 다름아닌 어떤 녀석들이 들어있는지를 알려주는 값이다.

reflect.Uint, reflect.Float64, reflect.Slice 같은

3) reflect.Value Int(), Float() 같은 메쏘드를 가지는데 reflect.Value 안의 값을 꺼내올 있게 해준다.

예제에서는 float64 라는 알기에 Float() 이용하였다.

예제에서 Int() 사용하면 panic 발생한다.

 

Settability

 

SetInt SetFloat 같은 메쏘드들은 settability 개념을 알아야 하는데

요건 번째 법칙에서 살펴 보자

예제 코드: https://play.golang.org/p/wDZtDSBcDU9

 

Largest type

 

reflect.Value 값을 가져오거나, 값을 넣으면

getter 할수 있는 Int() 라면 int64, Uint() 라면 uint64

setter 있는 SetInt() 라면 int64 할당된다.

 

, 값을 담을 있는 같은 부류 중에서 제일 메모리가 타입으로 먹여진다.

 

옆의 예제를 보아도

1) 분명히 v.Kind() reflect.Uint8 인데,

2) v.Uint() 값을 읽어내면 uint64 이다.

조금 생각해보면 이렇게 구현되어 있는지 느낌이 오겠지만 일단 여기서는 생략

예제 코드: https://play.golang.org/p/7P3jRZF4ifw

 

Kind 내면을 본다

 

요걸 유심히 보자.

v Kind() MyInt 아니라 int 인게 중요한거다.

내면을 본다는 거창한 말을 썼지만 달리 말하자면 MyInt int 구분 못한다는 말이다.

 

 

번째 리플렉션 법칙

리플렉션은 리플렉션 오브젝트 인터페이스 으로도 간다.

 

reflection 반사, 반영이다. 따라서 반대로 리플렉션 오브젝트가 인터페이스 값으로 되기도 한다.

 


예제 코드: https://play.golang.org/p/wdM1YtFRCTD

 

Interface() 메쏘드

 

reflect.Value 라는 타입의 Interface() 메쏘드를 쓰면

reflect.Value 에서 interface{} 추출해낼 있다.

 

다시 이녀석을 type assertion 해주면 진짜 값을 얻어내게 된다.

 

fmt 패키지는 무시무시하게 만들어진 놈이라 알아서 concrete 값을 추출해내어 출력해준다.

심지어는 넘이 float64 경우 format 까지 맞춰 넣을 있다.

 

인터페이스 타입 값을 .(float64) type assertion 안해줘도 될까?

인터페이스 타입의 값이 concrete value 타입 정보를 내부에 가지고 있기 때문이다.

 

다시 복습하자.

 

reflect.Value 메쏘드인 Interface() 먹이면,

static type interface{} 타입. , 인터페이스 타입의 값이 리턴된다.

 

리플렉션 오브젝트 < - > 인터페이스

예제 코드: https://play.golang.org/p/vd6B6ppu_fB

 

Interface() 메쏘드의 리턴값

 

중복이 되겠지만 약간 장난을 쳐보았다.

 

1) x interface{} 타입이고

2) y float64 타입인 것이다.

 

따라서

x string "hello" 할당할 있지만

y string 할당하려 했다간 컴파일 에러가 난다.

 

번째 리플렉션 법칙

리플렉션 오브젝트를 수정하려면, 값은 settable 해야 한다.

 

번째 법칙은 헷갈릴 있겠지만 , 첫번째 원칙부터 차근히 보면 이해 못할것도 아니다.

 


 

예제코드: https://play.golang.org/p/F9lH1vLIYeZ

 

reflect.Value SetFloat() 메쏘드를 써보자

 

v 라는 reflect.Value 에다가

v.SetFloat() 라는 메쏘드로 값을 쓰려고 했더니 패닉이 났다.

- panic: reflect: reflect.flag.mustBeAssignable using unaddressable value

- unaddressable value 값을 우겨 넣으려 해서 그렇단다

 

- v not settable 이다. 그래서 생긴 문제이다.

settability reflect.Value 속성이다.

그런데 모든 reflect.Value 속성을 가진건 아니다.

 

v := reflect.ValueOf(x) 라는 코드는 원래값 x copy 해서 reflect.Value v 만들어낸 것이다.

복사한 값을 변경해준다고 원래 값이 바뀔리가 없잖아?

 

v.SetFloat() 패닉을 발생시키지만, 이게 동작하게 만든다해도, 원래 값인 x 변경되는

따위는 절대 발생하지 않는다. 그래서 패닉을 일으키게 해둔거다.

멍충이가 v.SetFloat(7.1) 해놓고, x 업데이트 되었으려니 하지 않게 만든 것이다.

 

reflect.Value CanSet() 메쏘드를 써보자

 

v.CanSet() 이라고 물어보면 settable 할지 여부를 있다.

 

1. settability addressability 비슷하다.

어느 메모리에 저장되어 있는지 있다는 정도? 하지만 좀더 엄격하다

 

2. 원래 변수 (= 실제 값의 저장 공간)에서 우리는 reflection object  생성할 있다. 복제!

- reflect.ValueOf() 또는 reflect.TypeOf() 같은 걸로

- reflect.Value, reflect.Type 만들 있다는 것이다.

settability 원래 변수를 바꿀 있는가 여부인 것이다.

 

사실 함수를 생각해보면 이상한 것도 아니다.

- f(x)
값을 함수에 전달하면, 함수내에서 argument 변경한다고 하여 원래 값이 변경될 없지만

- f(&x) 레퍼런스 (=포인터) 전달하면 원래 값인 x 변경할 있는 것이다.

 


예제 코드: https://play.golang.org/p/tZ72mxpWXg2

 

실습을 해보자

 

변수 x 있다고 할때 변수의 reflection 만들어서 변수 값을 바꿔보자.

시점 궁금한건 이걸 어따쓰지?

타입을 모르는 녀석을 가져와서 값을 변경하려면?

 

1) float64 타입의 x 선언하고 초기값을 3.4 넣었다.

2) reflect.ValueOf(&x) 통해서 refelct.Value p 만들었다.

p.Type() 출력해보면 *float64 라고 제대로 나온다.  reflect.TypeOf(&X) 같은 결과였겠지?

p.CanSet() 결과가 중요하다. 어라! false 이다!

 

p 아니라 p.Elem() 이다.

 

p 자체는 set 없는 것이다.

p 가리키는 진짜 값이 set 있는 것이다.

 

p.Elem() 이다. 포인터로 치면 *p 되겠다.

 

드디어 v 값을 Set 해보자

 

v.SetFloat(7.1) 설정해주고 나면

- interface() 값이나

- 원래 변수인 x 값이 바뀐것을 있다.

 

Structs

 

예제의 v 포인터는 아니다. 포인터에서 끌어낸 무언가이다.

이런 식의 작업이 필요한 가장 대표적인 경우는 구조체의 필드값을 변경할 이다.

구조체의 주소값을 알고 있다면, 필드값을 변경할 있다.

 

예제 코드: https://play.golang.org/p/kIDgv0QJDrP

 

GET: 구조체 인스턴스를 reflect 받아서 필드명과 값을 읽어보자

 

1) struct 하나 선언하고

2) 23, "hello" 라는 초기값을 가지는 인스턴스 t 하나 만들었다.

 

3) ValueOf() Elem() 썼으니 s 바로 값을 있는 녀석이다. settable!

4) 원래의 값인 t Type s.Type() 으로 알아낼 있다.

- 타입을 안다는 , 그리고 타입이 구조체라는

- 구조체의 필드에 접근이 가능하다는 것이겠다

 

for 문을 돌면서 i 번째 필드를 f 라고 하면

- 필드의 이름은 typeOfT.Field(i).Name 되고

- f.Type() 해당 필드의 타입이 되며,

- f.Interface() 해당 필드의 값이 된다.

필드의 이름, 타입, 값을 알아낼 있게 된다.

 

SET: reflect 이용하여 구조체 인스턴스의 값을 바꿔보자

 

값을 넣어주는 어렵지 않다. 알아낸 필드의 타입을 기반으로 SetInt(), SetString() 등을 써주면 된다.

중요한 것은 필드명은 대문자로 시작해야 한다는 . Export 되어야만 settable 하다.

 

 

 

Conclusion

 

번째. 인터페이스 리플렉션 오브젝트로 reflection 가능하다

번째. 리플렉션 오브젝트 인터페이스로 값으로의 reflecton 가능하다

번째. 리플렉션 오브젝트의 값을 변경하려면 settable 해야 한다


반응형
반응형
잡학툰 뱃지
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함