Help us understand the problem. What is going on with this article?

TypeScriptでState Monadを実装する

More than 5 years have passed since last update.

HaskellとJavaScriptの学習を兼ねて、TypeScriptでState Monadを実装してみた。
Monad理解への第1歩は、State Monadに限る(ということにしよう)。

今回の目標

TypeScriptでState Monadを実装し、副作用のないスタックを実装する。

TypeScript

http://www.typescriptlang.org/

特徴

  • Microsoft製のJavaScriptのスーパーセット
  • 静的型付け言語(ジェネリックスがある!)
  • クラス
  • アロー関数式

今回は静的型付けが重要。
これがないと引数の意味が伝わらない。
今回初めて使用する。

State Monad(Haskell)

特徴

  • 状態付き計算を保管することで、副作用を無くす。

State Monad関連の実装する部分

Haskell
newtype State s a = State { runState :: (s -> (a,s)) } 
Haskell
instance Monad (State s) where 
    return a = State $ \s -> (a,s)
    m >>= k  = state $ \s -> let (a, s') = runState m s
                                             in runState (k a) s'
Haskell
evalState m s = fst ( runState m s )
execState m s = snd ( runState m s )
Haskell
instance MonadState (State s) s where 
    get   = State $ \s -> (s,s) 
    put s = State $ \_ -> ((),s) 

State Monad(TypeScript)

まずは、runStateが最終的に返す「値」と「状態」を保持するタプルを定義する。

TypeScript
interface Tuple2<A,B> {
  fst: A
  snd: B
}

TypeScriptにはinterfaceとジェネリックスがあるので、型安全なオブジェクトが簡単に定義できる。
これで型宣言が少し楽になる

次はStateクラスを定義

TypeScript
class State<T,X>{
  runState: (t:T) => Tuple2<X,T>
  evalState = (t:T):X => this.runState(t).fst
  execState = (t:T):T => this.runState(t).snd
  constructor(g:(t:T) => Tuple2<X,T>){
    this.runState = g
  }
  static returns<T,X>(v:X):State<T,X>{
    return new State<T,X>((t:T) => { return {fst:v, snd:t } })
  } 
  bind<B>(f: (X) => State<T,B>):State<T,B>{
    var g = (t:T) => {
      var st = this.runState(t)
      return f(st.fst).runState(st.snd)
    }
    return new State<T,B>(g)
  }
}

Haskell版State Monadのreturn>>=は、TypeScript版ではreturnsbindメソッドとした。
TypeScriptの静的型付けにより、引数に関数を指定しても分かりやすい。

続いて、MonadStateを実装する。
Haskell版MonadStateとほぼ一緒。。。null。。。が。。。

TypeScript
class MonadState {
  static get<T>():State<T,T> {
    var g = (t:T) => {
      return { fst: t, snd: t }
    }
    return new State<T,T>(g)
  }
  static put<T>(a:T):State<T,void> {
    var g = (t:T) => {
      return { fst: null,  snd: a }
    }
    return new State<T,void>(g)
  }
}

スタックの準備は完了。

スタックから取り出す関数を定義する。

TypeScript
var pop = <X>():State<X,X[]> =>
  MonadState.get<X[]>()
    .bind(e => { return MonadState.put<X[]>(e.slice(1))
                          .bind(k => State.returns(e[0])) })

現在の値を取得し、二番目以降のデータを保存し、先頭のデータを返す。

スタックに値を設定する関数を定義する。

TypeScript
var push = <X>(i:X):State<X,X[]> =>
  MonadState.get<X[]>()
    .bind(e => MonadState.put<X[]>([i].concat(e)))

テスト

TypeScript
var fnc = <X>():State<X,X[]> =>
  pop()
    .bind(e => pop())
    .bind(e => push(20))
    .bind(e => pop())
    .bind(e => push(e*5))

console.log(fnc().execState([1,2,3])) //=> [100,3]

期待した値が返った

まとめ

静的型付け(型推論)が良い(CoffeeScriptも静的型付けにならないものか)
TypeScriptは早期開発より、手堅さ優先の言語という印象
無駄な労力を使った気がしなくもないが、State Monadの理解が少し深まる。
しかし、let定義できないし、このままの実装では使えない(笑)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away