LoginSignup
1
1

Haskellの「関数型オブジェクト指向プログラミング」が面白かったのでJavascriptに移植してみた

Last updated at Posted at 2024-02-22

「関数型オブジェクト指向プログラミング」と題した Haskell 入門本が面白かったので、Javascript に移植して見ました。

私は暇が有れば入門書の写経を繰り返しているのですが、翔泳社発行、Will Kurt著「入門 Haskell プログラミング」を再度おさらいをしています。前回サラッと流した「関数型オブジェクト指向プログラミング」が意外と面白い事に気が付いてしまいました。そこでそのコードを Javascript で書き換えられるかにチャレンジして見ることにしました。

Haskell の方も色々改造して色々試しているのですが、クロージャをオブジェクトとして扱い、引数として繋いで行くと GHC さんの行う型推論がとんでも無い事になり大変でした。初手で型チェックの行われない Javascript ではそういった意味では躓く事も無くすんなりと記述することができました。Haskell の方はというと絶賛魔改造中で、プロパティ要素と評価条件を増やしてプチ・バトルテックもどきにできないかと画策しているところです。

「関数型オブジェクト指向」と銘打っているのですが、この章はまだ型を学習する前なので凝った型システムは使えません。そこでクロージャーを使ってオブジェクトを実現し、内部にはプロパティーのみを保持させます。メソッドは外部から受け取る高階関数の様になっています。変数等は一切書き換えを起こさない純粋関数プログラミングです(print文を除く)。各関数(ラムダ式)はコンパクトで簡単なコードで記述されていますので雰囲気を掴むのに苦労は要らないと思います。

クラスやオブジェクトを使わず全ての変数がイミュータブルといった縛りのあるアプローチに成っていて面白かったです。メソッドチェーンは文字整形関数の一部で使っているだけで、関数合成 g ( f ( x ) ) 形式での記述に終始しています。

変数定義には node の Repl にコピペーしても文句を言われない var を一貫して使っています。又、文区切りの「;」も見た目がスッキリするのであえて記述をしませんでした。

Javascript robot.js
//
// Robot buttle simulator (Functional Programming)
//

// クロージャ・コンストラクタ
var robot     = (name, attack, hp) => {
  return (message => {
    return (message ([name, attack, hp]))
  })
}

// 格納位置別名
var name      = 0
var attack    = 1
var hp        = 2

// クロージャ内データ取得関数
var getName   = stat => { return stat[name  ] }
var getAttack = stat => { return stat[attack] }
var getHP     = stat => { return stat[hp    ] }
var getAll    = stat => { return stat         }

// クロージャ内データ設定関数
var setName   = (aRobot,newName  ) => {
  return robot (newName
              , aRobot (getAttack)
              , aRobot (getHP    ))
} 

var setAttack = (aRobot,newAttack) => {
  return robot (aRobot (getName  )
              , newAttack
              , aRobot (getHP    ))
} 

var setHP     = (aRobot,newHP    ) => {
  return robot (aRobot (getName  )
              , aRobot (getAttack)
              , newHP             )
} 

// 被害更新関数
var damage    = (atkDmg,aRobot   ) => {
  return aRobot (([n, a, h]) => {
    return robot ( n, a, Math.max( 0, h - atkDmg))
  })
}

// 攻撃関数
var fight     = (aRobot, defender) => {
  return damage (aRobot (getHP    ) > 10
               ? aRobot (getAttack)
               : 0
               , defender )
} 

// 二者対戦関数
var buttle    = ([roboL, roboR]  ) => {
  return ([fight (roboR, roboL)
         , fight (roboL, roboR)])
}

// 二者デスマッチ関数
var deathMatch = ([roboL, roboR] ) => {
  var subMatch = ([roboL, roboR] ) => {
      if (roboL (getHP) < 1 || roboR (getHP) < 1)
      return              ([roboL, roboR])
      subMatch (buttlePrt ([roboL, roboR]))
  }
  prtButtle        ([roboL, roboR])
  return subMatch  ([roboL, roboR])
}

// 状態表示関数
var fmtStat   = (aRobot          ) => {
  return (aRobot (getName  )          + ', '
  + lpad (aRobot (getAttack), 3, ' ') + ', '
  + lpad (aRobot (getHP    ), 3, ' '))
}

// 状態表示関数
var prtStat   = (aRobot          ) => {
  console.log ('status : ' + fmtStat (aRobot))
}

// 対戦結果表示関数
var prtButtle = ([roboL, roboR]  ) => {
  console.log (fmtStat (roboL) + " <=> "
            + (fmtStat (roboR)))
}

// 対戦・結果表示関数
var buttlePrt = ([roboL, roboR]  ) => {
  var res = buttle ([roboL, roboR])
  prtButtle (res)
  return    (res)
}

// 左詰め合わせ関数
var lpad      = (str, len, ch    ) => {
  if (str.length > len)
  return str
  return new Array(len - ('' + str).length + 1)
            .join(ch)
         + str
}

// 右詰め合わせ関数
var rpad      = (str, len, ch    ) => {
  if (str.length > len)
    return str
    return str
         + new Array(len - ('' + str).length + 1)
               .join(ch)
}

// 主関数
var main = () => {
  var attackerRobot = robot ('Attacker-1', 45, 200)
  var defenderRobot = robot ('Defender-2', 23, 400)
  var result        = deathMatch ([attackerRobot
                                 , defenderRobot])
  return 0
}

main()

// >node robot.js
// Attacker-1,  45, 200 <=> Defender-2,  23, 400
// Attacker-1,  45, 177 <=> Defender-2,  23, 355
// Attacker-1,  45, 154 <=> Defender-2,  23, 310
// Attacker-1,  45, 131 <=> Defender-2,  23, 265
// Attacker-1,  45, 108 <=> Defender-2,  23, 220
// Attacker-1,  45,  85 <=> Defender-2,  23, 175
// Attacker-1,  45,  62 <=> Defender-2,  23, 130
// Attacker-1,  45,  39 <=> Defender-2,  23,  85
// Attacker-1,  45,  16 <=> Defender-2,  23,  40
// Attacker-1,  45,   0 <=> Defender-2,  23,   0
1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1