Haskell Fractional не смог вывести (RealFrac a), возникающий в результате использования «круглого»

#haskell

Вопрос:

Я не хочу выполнять целочисленное деление. Я хочу ввести дробный тип ввода, выполнить с ним деление, округлить результат до целого числа и вернуть его.

 piCalc :: (Fractional a, Ord a, Integral b) => a -> (a, b)
piCalc z = (piCalc' steps 1 0, steps)
    where steps = round $ (4-z)/(2*z)   1

piCalc' :: (Integral a, Ord a, Fractional b) => a -> a -> a -> b
piCalc' 0 s k = 0
piCalc' steps s k = (fromInteger s*4)/(fromInteger 2*k   1)   piCalc' (steps-1) ((-1)*s) (k 1)

main = do
    print (piCalc 0.001)
 

Я получаю следующую ошибку:

 Recursion.hs:59:19: error:
    * Could not deduce (RealFrac a) arising from a use of `round'
      from the context: (Fractional a, Ord a, Integral b)
        bound by the type signature for:
                   piCalc :: forall a b.
                             (Fractional a, Ord a, Integral b) =>
                             a -> (a, b)
        at Recursion.hs:57:1-58
      Possible fix:
        add (RealFrac a) to the context of
          the type signature for:
            piCalc :: forall a b.
                      (Fractional a, Ord a, Integral b) =>
                      a -> (a, b)
    * In the first argument of `($)', namely `round'
      In the expression: round $ (4 - z) / (2 * z)   1
      In an equation for `steps': steps = round $ (4 - z) / (2 * z)   1
   |
59 |     where steps = round $ (4-z)/(2*z)   1
   |                   ^^^^^

Recursion.hs:63:34: error:
    * Couldn't match expected type `Integer' with actual type `a'
      `a' is a rigid type variable bound by
        the type signature for:
          piCalc' :: forall a b.
                     (Integral a, Ord a, Fractional b) =>
                     a -> a -> a -> b
        at Recursion.hs:61:1-64
    * In the first argument of `fromInteger', namely `s'
      In the first argument of `(*)', namely `fromInteger s'
      In the first argument of `(/)', namely `(fromInteger s * 4)'
    * Relevant bindings include
        k :: a (bound at Recursion.hs:63:17)
        s :: a (bound at Recursion.hs:63:15)
        steps :: a (bound at Recursion.hs:63:9)
        piCalc' :: a -> a -> a -> b (bound at Recursion.hs:62:1)
   |
63 | piCalc' steps s k = (fromInteger s*4)/(fromInteger 2*k   1)   piCalc' (steps-1) ((-1)*s) (k 1)
   |                                  ^^^
 

Изменение дробного на RealFrac в piCalc (верхняя функция) исправило для меня первую ошибку, но я не понимаю, почему и не знаю, является ли это «хорошим» решением. Я едва мог найти примеры использования RealFrac при поиске в Google. Я не знаю, что вызывает вторую ошибку.

Ответ №1:

RealFrac Ограничение подразумевает как Real и Fractional . Real означает, что числа должны быть 1-мерными (в отличие, скажем, от 2-мерных комплексных чисел или векторов более высокой размерности). round требуется Real , потому что он должен отображать свои входные данные на 1-мерном наборе целых чисел. И round , очевидно , требует Fractional , иначе нет особого смысла округлять. Итак, round требуется a RealFrac в качестве входных данных. Изменение Fractional на RealFrac -это правильное решение первой ошибки.

Вторая ошибка заключается в том , что вы указываете, что ваши типы являются Integral , но затем применяете fromInteger функцию, которая ожидает конкретного Integer . Тебе следует переодеться fromInteger в fromIntegral .

Тогда вы все равно получите ошибку, потому что вы пишете fromInteger 2*k , что означает (fromInteger 2) * k на языке Хаскелл, но вы, вероятно, имели в виду fromInteger (2 * k) или 2 * fromInteger k . И это также следует изменить на fromIntegral .

Этот код компилируется без ошибок:

 piCalc :: (RealFrac a, Ord a, Integral b) => a -> (a, b)
piCalc z = (piCalc' steps 1 0, steps)
    where steps = round $ (4-z)/(2*z)   1

piCalc' :: (Integral a, Ord a, Fractional b) => a -> a -> a -> b
piCalc' 0 s k = 0
piCalc' steps s k = (fromIntegral s*4)/(2 * fromIntegral k   1)   piCalc' (steps-1) ((-1)*s) (k 1)

main = do
    print (piCalc 0.001)
 

Если вы просто заинтересованы в вычислении pi, тогда, возможно, имеет смысл выбрать более конкретные типы, например Double , или Rational вместо RealFrac a => a и Int или Integer вместо Integral a => a . Вы можете сохранить реализацию прежней и изменить только подписи:

 piCalc :: Double -> (Double, Int)
piCalc z = (piCalc' steps 1 0, steps)
    where steps = round $ (4-z)/(2*z)   1

piCalc' :: Int -> Int -> Int -> Double
piCalc' 0 s k = 0
piCalc' steps s k = (fromIntegral s*4)/(2 * fromIntegral k   1)   piCalc' (steps-1) ((-1)*s) (k 1)

main = do
    print (piCalc 0.001)