もう一つの問題として、このプログラムを 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 クラスの問題があるのでこれは正しく動かないと考えられます。今後問題点を調査していきたいと思います。