typescriptで参照透過性を意識してみた

# typescriptで参照透過性を意識してみた

これまでは動けばOKの考えだったので、関数型プログラミングね、何かあるよね程度でした。
今回、typescriptを書く機会があり、せっかくなので、動けばOKコード => 参照透過性を意識したコードにリファクタリングしてみました。  
ちなみに参照透過性というワードも、今回この投稿を書いていくうちに知りました。

なお、最初におことわりしますが、私はjavascriptとかtypescriptは初心者に産毛が生えた程度です。
先輩諸兄がここから得られるものはないと思いますが、懐かしい気持ちになっていただければ幸いです。
あと、参照透過性についても誤解をしている可能性はあり、そこは突っ込んでください。

## 優秀な後輩が珍しく困っていた

「typescriptでExcelを扱うにあたって、列のアドレスを数字 => アルファベットにしたいんだけど、どうやればいいのか」  
普段私がアドバイスをもらうことが多い立場なので、ここはなんとか役に立ちたい！  
しかも、同じようなことを私も過去にやったことがありました。  
ただ、その時はExcelで完結する処理だからVBAで書いたし、そのコードもどこへやら状態なので、  
「再帰させたことは覚えている。あと、大きい桁から処理した。」  
という、おぼろげなことしか言えませんでした。

## よし、自分も作ってみよう。

ひとまず1時間くらいで、一応動くものはできました。  
この時点では参照透過性などは一切考慮してません。  

```typescript
function changeBase(number, base, multipler, digitsList){
  const divResult = Math.floor(number / (base ** multipler))
  const modResult = number % (base ** multipler)
  digitsList.push(divResult)
  multipler -= 1
  if(multipler > -1){
    return changeBase(modResult, base, multipler, digitsList)
  }
  return digitsList
}

function getDigits(number, base){
  let digits = 0
  
  while(number >= (base ** digits)){
    digits += 1
  }
  return digits - 1
}
```

現状使い方はこんな感じ。
Excelの最終列XFDを10進数にした16384を26進数で表現してみます。  

```typescript
const main = () => {
  const number = 16384
  const base = 26
  const multipler = getDigits(number, base)
  const baseChanged = changeBase(number, base, multipler, [])
  console.log(baseChanged)

  // [ 24, 6, 4 ]

}

main()
```

指定された数`number`を指定した進数`base`で表します。
まず桁数を調べるために、`getDigits`で`number`が`base`の何乗までに収まるかを割り出します。乗数が`multipler`です。  
そして、`changeBase`で上の桁から`base`の`multipler`乗で割っては余りを出し、その余りを`multipler - 1`乗で割って、、、という再帰計算をして、最終的に`multipler`が0になったら桁ごとの配列`digitsList`を返しています。

うん、ひとまず動きますね。  
26進数をアルファベットにするところは、まあなんとかなりそうだし、またあとで考えます。  
しかし、もっさいコードだな。

## もっとスマートにしたい

セルフレビューをします。
- `getDigits`も再帰処理にしたい。値を変更していくとバグを生みやすくてよくないって噂に聞くから（後々知ることになる参照透過性のこと）。
- 型を指定しないと危険ですよね。
- `getDigits`を`baseChanged`とは別で呼び出しているのがいけてない。  
=>`baseChanged`の引数の`multipler`と`digitsList`をoptionalにしてやって、初回呼び出しでは`number`と`base`だけ指定して、そこで`getDigits`を実行する形にすればいいのか？
- `digitsList`にpushしていくのも、どうにかならないものか。これもバグを生みやすくてよくないって噂に聞くから。でも、これは現時点でno idea。

とりあえず、3匹目のドラゴンまでは何とか倒せそう。

## 桁数取得を再帰処理してみました

これは、`number > (base ** multipler)` が`True`になるまで`multipler`を足していくという再帰処理をすればいいんだな。  
てことは、三項演算子の出番ですね。

```typescript
function getDigits(number, base, multipler){
  return(number >= (base ** multipler) ? getDigits(number, base, multipler + 1) : multipler - 1)
}
```

なんか、めっちゃいい感じになった気がする！
最初`return`をそれぞれの条件の側に入れて、なんかうまくいかないなーなんて思ってましたが、よく考えれば`return`するのは三項演算子の結果で良いわけですもんね。  
こういうところはやっぱり経験のなさが如実に出ますね。  

## 2つ目の型宣言ドラゴンを倒すときに気付いた過ち

では、型を付与していこう。変数の`number`は`int`型にしたいよね。
......ん？なんかvscodeが受け付けてくれてない気がする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985480/f995e809-dbb0-36b7-c60f-53ed9e713df4.png)

[ぐぐってみたら]("https://typescriptbook.jp/reference/values-types-variables/number#%E6%95%B0%E5%80%A4%E5%9E%8B%E3%81%AE%E5%9E%8B%E6%B3%A8%E9%87%88")、なるほど。`int`ではなく`number`型を指定するのか。  

どういう弊害があるのかは分かってないけど、なんとなく`number: number`は避けたいから変数名を`num`にでも変えることとします。    
ここでの変化は大したことないのでcodeは省略。

## 初回必要ない引数をoptionalにしてみました

```typescript
const main = () => {
  
  const number = 16384
  const base = 26
  const baseChanged = changeBase(number, base)
  console.log(baseChanged)

  // [ 24, 6, 4 ]

}

function changeBase(num:number, base:number, multipler:number = getDigits(num, base), digitsList:number[] = []){

  const divResult = Math.floor(num / (base ** multipler))
  const modResult = num % (base ** multipler)
  digitsList.push(divResult)
  if(multipler > 0){
    multipler -= 1
    return changeBase(modResult, base, multipler, digitsList)
  }
  return digitsList
}

function getDigits(num:number, base:number, multipler:number = 0){
  return(num >= (base ** multipler) ? getDigits(num, base, multipler + 1) : multipler - 1)
}

main();

```

大きく変わった部分は`changeBase`の`multipler`のdefault引数を`getDigits`で割り出している点ですね。  
元々は、初回に桁数を決めるために`getDigits`を呼び出して、判明した桁数を`changeBase`に渡すという方法を取っていました。  
それを`changeBase`の`multipler`のdefault引数を`getDigits`にしたことで、入力は`changeBase`に対して`number`と`base`だけで済むようになりました。  
これが思いついた瞬間は自分が天才なんじゃないかと思いましたが、多分これ漸化式とか一般項とかそういうのが分かってる人からすると超普通のことなんでしょうね。  

## 最後のドラゴン「配列へのpush」

こんな感じになりました。

```typescript
function changeBase(num:number, base:number, multipler:number = getDigits(num, base), digitsList:number[] = []){

  const divResult = Math.floor(num / (base ** multipler))
  const modResult = num % (base ** multipler)
  const currentDigitsList = digitsList.concat([divResult])
  if(multipler > 0){
    multipler -= 1
    return changeBase(modResult, base, multipler, currentDigitsList)
  }
  return currentDigitsList
}
```

変更点は、再帰処理で都度やってくる`digitsList`とその回で判明した`divResult`を単純に`concat`して新たな配列`currentDigitsList`を作成してます。  
そして、`currentDigitsList`を次の回の`digitsList`に渡してやると。  
これなら、pythonでいうtupleみたいなimmutableな型でも使えるわけで、参照が透明といえるのではないでしょうか？（ちょっと自信がないので、間違っていたらツッコミお願いします！）

当初は、その回で判明した桁の数を配列にpushして、その配列を再帰処理に渡して、ということをやっていて、危険性は分かるんだけどそれ以外にやり方なんてある？ってくらいに考えが固まってました。  
一回ライブして、終電にギリギリ乗って、睡眠して、起きてやっと思いつきました。  
悩んだときに毎回このプロセスを経ていては体が持たないですね。

## アルファベットに直すところも考えました

asciiコードがどのくらいの不変なのかは分かってませんが、まあアルファベット順にコードが振られなくなるということはそうそうないかと思い、こんな感じにしました。

```typescript
function num2char(digitsList:number[]){
  return(digitsList.map((c: number) => (String.fromCharCode(c + ("A".charCodeAt(0) - 1)))).join(""))
}
```

26進数の桁ごとの配列を引数として受け取ったら、それぞれの桁の数に"A"のascii code -1を足しています。

```typescript
const main = () => {
 
  const num = 16384
  const base = 26
  const baseChanged = changeBase(num, base)
  console.log(baseChanged)
  // [ 24, 6, 4 ]

  console.log(num2char(baseChanged))
  // XFD

}

```

## IPアドレスも試してみよう

曲がりなりにもインフラエンジニアの端くれなので、IPアドレスで試してみます。
今回できたコード全部載せつつ。

```typescript
const main = () => {
  
  const ipAdderss = "192.168.0.1"
  const ipBin = ipAdderss.split(".").map((x: string) => (changeBase(Number(x), 2)))
  console.log(ipBin)

  // [[1, 1, 0, 0, 0, 0, 0, 0],[1, 0, 1, 0, 1, 0, 0, 0],[ 0 ],[ 1 ]]
}


function changeBase(num:number, base:number, multipler:number = getDigits(num, base), digitsList:number[] = []){

  const divResult = Math.floor(num / (base ** multipler))
  const modResult = num % (base ** multipler)
  const currentDigitsList = digitsList.concat([divResult])
  if(multipler > 0){
    multipler -= 1
    return changeBase(modResult, base, multipler, currentDigitsList)
  }
  return currentDigitsList
}

function getDigits(num:number, base:number, multipler:number = 0){
  return(num >= (base ** multipler) ? getDigits(num, base, multipler + 1) : multipler - 1)
}

// この例では以下は使ってません
function num2char(digitsList:number[]){
  return(digitsList.map((c: number) => (String.fromCharCode(c + ("A".charCodeAt(0) - 1)))).join(""))
}

main()

```

インフラエンジニアですが端くれなので、`[[1, 1, 0, 0, 0, 0, 0, 0],[1, 0, 1, 0, 1, 0, 0, 0],[ 0 ],[ 1 ]]`を見て即時に計算はできません。
[ここ]("https://www.browserling.com/tools/ip-to-bin")で調べてみたら、合ってました。
2進数は桁で区切られると中々可読性が落ちちゃいますが、10以上の進数を扱うにはこれがベストかと。

## おわりに

一番最初のとりあえず動くコードができた時点で優秀な後輩氏に「ひとまず書けましたけど、見ます？」と声を掛けたら、なんとすでに完成させたとのことでした。  
全く可愛げがないですね。  

週明け（この記事がアドベントカレンダーで公開される日）に、お互いが書いたものを見せ合うので、楽しみです。

## 参考
[MDN]("https://developer.mozilla.org/ja/")
[サバイバルTypeScript]("https://typescriptbook.jp/")
[TypeScript Deep Dive 日本語版]("https://typescript-jp.gitbook.io/deep-dive/")