JS interop - 함수 바인딩
어제에 이어서 함수 바인딩에 대해서 끝까지 알아보겠습니다.
Variadic 함수 바인딩
- JS 함수중에서는 여러개의 argument를 인자로 받는 경우가 있습니다. ReScript에서도 그런 경우를 처리해주는데요, 이 때 각 인자들은 같은 타입이어야 한다는 조건이 붙습니다. 아래는 예시입니다.
@bs.module("path") @bs.variadic
external join: array<string> => string = "join"
let v = join(["a", "b"]) //a와 b가 같은 타입입니다. (string)
var Path = require("path");
var v = Path.join("a", "b");
폴리몰픽 함수 바인딩
- 하나의 함수가 여러 형태로 오버로딩(overloading)되는 경우는 어떻게 바인딩할까요? 여기엔 두가지 방법이 존재합니다. 첫째, 여러개의 externals를 사용하는 방법입니다.
@bs.module("MyGame") external drawCat: unit => unit = "draw"
@bs.module("MyGame") external drawDog: (~giveName: string) => unit = "draw"
@bs.module("MyGame") external draw: (string, ~useRandomAnimal: bool) => unit = "draw"
- 위의 경우는 draw 라는 함수가 각기 다른 인자를 가지도록 externals를 이용해 여러번 overloading 된 경우입니다. 예상을 벗어나지 않는 직관적 형태였는데요, 그럼 이번엔 아래와 같은 경우를 살펴봅시다.
function padLeft(value, padding) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
- padding의 타입이 정해져있지 않기 때문에 함수 안에서 직접 타입체킹을 한 후 값을 리턴하는 경우입니다. ReScript의 패턴매칭을 사용하고 싶은 욕구가 듭니다. 실제로 아래와 같이 바인딩할 수 있습니다.
@bs.val
external padLeft: (
string,
@bs.unwrap [
| #Str(string)
| #Int(int)
])
=> string = "padLeft"
padLeft("Hello World", #Int(4))
padLeft("Hello World", #Str("Message from ReScript: "))
- 큰 틀에서 살펴보자면,
- @bs.val 을 통해 JS에 존재하는 padLeft를 바인딩함을 알려줍니다.
- @bs.unwrap으로 두번째 인자의 타입에 대한 패턴매칭을 해줍니다. @bs.unwrap은 variant 생성자를 벗겨내서 실제 padding의 값으로 컴파일하는 역할을 합니다.
인자를 더 타이트하게 바인딩하기
- 인자의 타입을 string으로 제한함과 동시에, string에서 특정한 값들만 들어오게 하고 싶은 경우가 있을 수 있습니다. fs.readFileSync(*.text, utf8 | ascii)의 경우과 같이요. 그럴때는 아래와 같이 할 수 있습니다.
@bs.module("fs") external readFileSync: ( ~name: string, @bs.string [ // (1) | #utf8 | @bs.as("ascii") #useAscii // (2) ], ) => string = "fs"
- 우선 fs 안의 함수를 바인딩한 것이긱 때문에 @bs.module(“fs”)로 시작합니다.
- @bs.string 은 [] 안의 결과가 string임을 나타내고,
- @bs.as 는 #useAscii 가 매칭되었을 경우 ascii 값 @bs.as안의 값을 반환하도록 합니다. @bs.as는 여러 쓰임새가 있는데 한가지 예로는 record 타입의 대문자로 시작하는 키를 갖지 못하는 규칙 을 우회할 때도 사용할 수 있습니다.
type test = {
@bs.as("Capitalized")
unCapitalized: string
}
이렇게요.
- 놀랍지 않게도, string 뿐만 아니라 int도 타이트한 바인딩이 가능합니다. ```javascript @bs.val external testIntType: ( @bs.int [ | #onClosed | @bs.as(20) #onOpen | #inBinary | #inTertiary ]) => int = “testIntType”
testIntType(#inTertiary) //testIntType(22)
- 유추할 수 있듯이, onOpen이 20으로 세팅되고 난 이후의 옵션들에 대해서는 숫자각 1씩 커집니다.
## 이벤트 리스너
- 이벤트 리스터는 어떻게 바인딩할까요? 이것도 위의 방법들과 다르지 않습니다. 먼저 JS코드를 보면
```javascript
function register(rl) {
return rl
.on("close", function($$event) {})
.on("line", function(line) {
console.log(line);
});
}
- register라는 함수는 readline(rl)을 받아서 close 일때 실행하는 함수와 line일때 실행하는 함수가 각각 구현되어 있습니다. 이 ‘on’ 이라는 함수를 바인딩 할 때는
- string close 와 line에 대한 패턴매칭이 있어야하고
- rl의 인자와 리턴값에 대한 정의를 알아야 합니다.
- rl.on().on() 의 형식으로 체이닝이 되고 있으니 .on()의 리턴값은 rl임을 유추할 수 있습니다.
- 호출자는 rl 입니다.
let register = rl =>
rl
->on(#close(event => ()))
->on(#line(line => Js.log(line)));
- 위 처럼 rl을 인자로 받아 close 일때는 유닛=>유닛 을, line일 때는 스트링=>유닛 의 함수를 호출해주도록 하면 됩니다. on은 pipe operator로 연결됩니다. 위의 1,2,3,4를 유의해서 바인딩해보면,
@bs.send
external on: (
readline,
@bs.string [
| #close(unit => unit)
| #line(string => unit)
]
)
=> readline = "on"
- 호출자가 rl 이기 때문에 (readline, ()=>()) => readline 이 형식이며,
- 함수는 @bs.string으로 패턴 매칭을 해줬습니다.
- 상당히 어려운데, 많이 익숙해져야 할 부분같습니다 ^.^;;
this를 가진 콜백 바인딩하기
- 이런 함수가 있다고 가정합시다.
x.onload = function(v) { console.log(this.response + v) }
- 사실 이 함수는 이렇게 풀이될 수 있습니다.
x.onload = function (v) {
let o = this;
console.log((o.response + v) | 0);
};
- onload를 ReScript에서 사용하고 싶다고 할 때, 차근차근 바인딩 해야 할 부분을 살펴보자면,
- x가 onload를 호출자입니다. (첫번째 인자로 들어갑니다) a.b = c 오퍼레이션이 있기 때문에 @bs.set을 해줘야 할 것 같습니다.
- this 를 o에 할당합니다 //감이 안잡히는군요
- o.response는 o에서 response 값을 꺼내오는 것이기 때문에 @bs.get을 해줍니다. (response가 int라 가정합니다)
- 결과는 아래와 같습니다.
type x
@bs.val external x: x = "x"
@bs.set external setOnload: (x, @bs.this ((x, int) => unit)) => unit = "onload"
@bs.get external resp: x => int = "response"
setOnload(x, @bs.this ((o, v) => Js.log(resp(o) + v)))
- 먼저 x type을 지정해주고,
- x.onload = function에 해당하는 부분을 작성합니다. 이 때, console.log는 unit를 리턴하므로 크게 볼 때 @bs.this() => unit의 형태가 됩니다.
- @bs.this를 눈여겨 봅시다. 결국 x에 this가 바인딩하므로 this()의 첫번째 인자로 x가 옵니다. this(x)
- function(v) 에서 v가 정수이므로 this(x, int)가 되는데, 이 this 바인딩은 아무 리턴이 없으므로 unit을 리턴한다고 되어 있습니다.
- 마지막으로 큰 틀에서 보면 console.log 또한 리턴이 없으므로, unit을 리턴합니다.
- response는 x를 받아 int를 돌려주는 형태 (this.response) 임으로 @bs.get으로 처리해줍니다.
-
마지막으로 setOnload를 호출합니다.
- 으으.. 너무 복잡해서 한번에 잘 이해가 가지 않는데요,
내일의 TIL을 하며 내용을 보완해보겠습니다.보완해버렸습니다!!