エレファント・ビジュアライザー調査記録

ビジュアルプログラミングで数式の変形を表すことを考えていくブロクです。

人工知能的サーバー対ブラウザー(3)

もう一つの問題として、このプログラムを Haskell で書くとどうなるかということを考えます。Numbers クラスでは情報を保持するようになっていますが、Haskell では変数の値を変更することができないので、同じようにはできません。この問題をどうするかということを考えることによって、ユーザーの入力を取り込むにはどうすれば良いか、ということを考えていきます。無限個のプログラムを考えることによってできるのではないかと考えています。

ChatGPTで「Haskell で長い桁数の固定小数点の数値を使うにはどうすれば良いですか」と入力すると decimal ライブラリを使えば良いということがわかりました。

  • 「以下の Python の decimal モジュールを使って書かれた Python のクラスを、Haskell の decimal ライブラリを使って Haskell のコードに書き換えてください」と入力して Numbers クラス
  • 「さらに以下の Python のクラスを Haskell のコードに書き換えてください」と入力して NumbersGenerator クラス
  • 「さらに以下の Python のクラスを Haskell のコードに書き換えてください」と入力して NumbersServer クラス
  • 「さらに以下の、(以上のものを)まとめて実行するための Python のクラス(Calc)、それに必要な関数定義、および、結果を表示するためのコードを Haskell のコードに書き換えてください」と入力して残りの部分

を得ることができました。

{-# LANGUAGE OverloadedStrings #-}
import Data.Decimal
import Data.List (unfoldr, map)

-- 以前の Numbers クラスの定義
data Numbers = Numbers
  { number :: Decimal  -- 数値
  , squareDifference :: Decimal  -- 二乗の差
  , scale :: Int  -- スケール
  }

copy :: Numbers -> Numbers
copy nums = nums { number = number nums, squareDifference = squareDifference nums, scale = scale nums }

getNextDecimalDigit :: Numbers -> (Int, Numbers)
getNextDecimalDigit nums = loop 9
  where
    two = Decimal 0 2
    loop dd
      | dd < 0 = (0, nums)
      | otherwise = 
          let zd = Decimal (scale nums) dd
              numberSqDiff = number nums * zd * two + zd * zd
          in if numberSqDiff <= squareDifference nums
             then
               let newNumber = number nums + zd
                   newSquareDiff = squareDifference nums - numberSqDiff
                   newScale = scale nums + 1
               in (dd, newNumbers)
             else
               loop (dd - 1)

-- NumbersGenerator
data NumbersGenerator = NumbersGenerator
  { generator :: [Int]  -- 次の桁を生成するリスト
  }

initNumbersGenerator :: NumbersGenerator
initNumbersGenerator = NumbersGenerator (generateDecimalServer initialNumbers)

generateDecimalServer :: Numbers -> [Int]
generateDecimalServer nums = 
  let (digit, updatedNums) = getNextDecimalDigit nums
  in digit : generateDecimalServer updatedNums  -- 再帰的に生成

getNextDecimalDigitGen :: NumbersGenerator -> (Int, NumbersGenerator)
getNextDecimalDigitGen gen =
  case generator gen of
    (x:xs) -> (x, NumbersGenerator xs)  -- リストの先頭を取得
    _ -> error "Generator is empty"  -- 空の場合エラー

-- NumbersServer
data NumbersServer = NumbersServer
  { currentNumbers :: Numbers  -- 現在の Numbers インスタンス
  , generatorServer :: [Numbers]  -- ジェネレータ
  }

initNumbersServer :: NumbersServer
initNumbersServer = NumbersServer initialNumbers (generateDecimalServer initialNumbers)

getNumbers :: NumbersServer -> Numbers
getNumbers server = currentNumbers server

setNumbers :: NumbersServer -> Numbers -> NumbersServer
setNumbers server nums = server { currentNumbers = nums }

-- unfold で無限リストを生成
unfoldL :: (a -> Maybe (b, a)) -> a -> [b]
unfoldL next init = unfoldr next init

-- 次の桁と Numbers インスタンスを返す
nextDigitAndNumbers :: Numbers -> Maybe (Int, Numbers)
nextDigitAndNumbers nums = 
  let numsCopy = copy nums
      (dd, newNums) = getNextDecimalDigit numsCopy
  in Just (dd, newNums)

-- リストの最初の 'count' 要素を取得
takeFromSeq :: Int -> [a] -> [a]
takeFromSeq count seq = take count seq

-- 数値のリストを小数形式で表示
formatDecimal :: [Int] -> String
formatDecimal dec = show (head dec) ++ "." ++ concatMap show (tail dec)

-- Calc クラス
data Calc = Calc
  { count :: Int  -- 繰り返し回数
  , generator :: NumbersGenerator
  , generatorServer :: NumbersServer
  , resultNumber :: Decimal
  }

initCalc :: Int -> Calc
initCalc repeatCount = Calc repeatCount initNumbersGenerator initNumbersServer (Decimal 0 0)

repeatGenerator :: Calc -> String
repeatGenerator calc =
  let count = count calc
      gen = generator calc
      updatedCalc = foldl (\acc e -> 
        let (dd, newGen) = getNextDecimalDigitGen (generator acc)
            zd = Decimal (negate e) dd
        in acc { generator = newGen, resultNumber = resultNumber acc + zd }) calc [0..(count - 1)]
  in show (resultNumber updatedCalc)

repeatServer :: Calc -> String
repeatServer calc =
  let count = count calc
      server = generatorServer calc
      updatedCalc = foldl (\acc e ->
        let numbers = getNumbers (generatorServer acc)
            (dd, newNums) = getNextDecimalDigit numbers
            newServer = setNumbers (generatorServer acc) newNums
            zd = Decimal (negate e) dd
        in acc { generatorServer = newServer, resultNumber = resultNumber acc + zd }) calc [0..(count - 1)]
  in show (resultNumber updatedCalc)

iterateGenerator :: Calc -> String
iterateGenerator calc =
  let dec = takeFromSeq (count calc) (generator generator calc)
  in formatDecimal dec

iterateServer :: Calc -> String
iterateServer calc =
  let initialNumbers = Numbers (Decimal 0 0) (Decimal 0 3) 0
      dec = takeFromSeq (count calc) (unfoldL nextDigitAndNumbers initialNumbers)
  in formatDecimal(dec)

-- 出力例
main :: IO ()
main = do
  let calc = initCalc 21  -- 21回の繰り返し
  putStrLn (repeatGenerator calc)
  putStrLn (repeatServer calc)
  putStrLn (iterateGenerator calc)
  putStrLn (iterateServer calc)

それらしいプログラムができましたが、Numbers クラスの問題があるのでこれは正しく動かないと考えられます。今後問題点を調査していきたいと思います。