Как определить тип, если он неоднозначный?

Я новичок в Haskell, и у меня есть некоторые проблемы с пониманием этого. У меня есть 2 функции, определенные в таком файле:

expr n x = (x ^ n)/(fact n)


fact n
  | n == 0 = 1
  | otherwise = n * fact (n - 1)

Но когда я пытаюсь запустить say expr 3 2, я продолжаю получать такую ​​ошибку:

*Main> expr 3 2

<interactive>:31:1: error:
    • Ambiguous type variable ‘a0’ arising from a use of ‘print’
      prevents the constraint ‘(Show a0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘a0’ should be.
      These potential instances exist:
        instance Show Ordering -- Defined in ‘GHC.Show’
        instance Show Integer -- Defined in ‘GHC.Show’
        instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
        ...plus 22 others
        ...plus 19 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In a stmt of an interactive GHCi command: print it

Я проверил типы для различных выражений и вижу это:

*Main> :t (/)
(/) :: Fractional a => a -> a -> a
*Main> :t (^)
(^) :: (Integral b, Num a) => a -> b -> a
*Main> :t 3 ^ 6
3 ^ 6 :: Num a => a
*Main> :t fact
fact :: (Eq p, Num p) => p -> p
*Main> :t fact 3
fact 3 :: (Eq p, Num p) => p
*Main> :t 9 / 6
9 / 6 :: Fractional a => a

Но я не могу понять, какие типы дать моим функциям, чтобы избежать этого. Я попытался придать моей функции фактов такой тип, поскольку (/) ожидает Fractional:

fact :: (Fractional a) => Int -> a

Но если я попытаюсь указать свои типы функций, файл даже не загрузится в ghci. Я могу опубликовать сообщение об ошибке, которое я получаю при попытке загрузить мой файл с типами, если это поможет.

Если я попытаюсь добавить тип к показу, используемому при оценке выражения, это тоже не сработает:

*Main> expr 3 2 :: String

<interactive>:37:1: error:
    • No instance for (Fractional String) arising from a use of ‘expr’
    • In the expression: expr 3 2 :: String
      In an equation for ‘it’: it = expr 3 2 :: String

<interactive>:37:6: error:
    • No instance for (Num String) arising from the literal ‘3’
    • In the first argument of ‘expr’, namely ‘3’
      In the expression: expr 3 2 :: String
      In an equation for ‘it’: it = expr 3 2 :: String
*Main> expr 3 2 :: Integer

<interactive>:38:1: error:
    • No instance for (Fractional Integer) arising from a use of ‘expr’
    • In the expression: expr 3 2 :: Integer
      In an equation for ‘it’: it = expr 3 2 :: Integer

Это неявный вызов show, сделанный ghci, который не знает, какой экземпляр Show использовать для преобразования результата в String. Попробуйте expr 3 2 :: Integer, expr 3 2 :: Float и т. д., чтобы выбрать один.

chepner 11.12.2020 17:19

Я пробовал. Этот гам тоже работает. Я отредактирую свой вопрос, чтобы добавить ответ на него. Я пробовал, Integer, Double, Float.

Yathi 11.12.2020 18:00

Вы не можете выбрать какой-либо возвращаемый тип, только те, которые совместимы с ограничениями, накладываемыми используемыми вами операторами.

chepner 11.12.2020 18:03

Да. Вот что я пытаюсь выяснить. Какой здесь должен быть тип, потому что я не очень хорошо разбираюсь в том, как работают типы. Тип, возвращаемый (/), является правильным дробным? Так что мне делать expr 3 2 :: Fractional a или что-то в этом роде?

Yathi 11.12.2020 18:05

В Haskell арифметические операторы, за исключением (^), заставляют два своих операнда иметь один и тот же тип. Поэтому, если вам нужны значения Double, вам просто нужно указать, что знаменатель должен быть типа Double, например, так: expr n x = (x ^ n) / ((fromIntegral (fact n))::Double) ; это вытеснит неоднозначность типа и, таким образом, сделает выражение доступным для отображения.

jpmarinier 11.12.2020 18:11

Haskell выводит правильный тип для вас: :t expr вернет (Fractional a, Integral a) => a -> a -> a, потому что использование / требует, чтобы аргументы имели экземпляр Fractional, а использование ^ вызывает экземпляр Integral. Если вы хотите вызвать show на нем (как это пытается сделать ghci), a также должен иметь экземпляр Show.

chepner 11.12.2020 18:18
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
83
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как упоминалось в комментариях, оператор /, как и для + - *, заставляет два своих операнда иметь один и тот же тип (и это также тип результата). Оператор ^ является исключением, и его результат должен иметь тот же тип, что и его левый операнд. Обратите внимание, что для типов с плавающей запятой у вас также есть оператор возведения в степень ** в стиле Fortran.

Более подробно правила объясняются в этом туториале.

К сожалению, это означает, что смешанную арифметику в Haskell реализовать несколько сложнее, чем в некоторых известных императивных языках. При необходимости можно использовать функцию преобразования fromIntegral :: (Integral a, Num b) => a -> b. Это дает компилятору лицензию на вставку подходящего преобразования.

В данной ситуации, если желательна широкая подпись типа, это может быть достигнуто, например, следующим образом:

{-#  LANGUAGE  ScopedTypeVariables  #-}
{-#  LANGUAGE  ExplicitForAll       #-}

fact :: Integral nt => nt -> nt
fact n = if (n <= 0)  then  1  else  n * fact (n - 1)

expr :: forall nt ft.  (Integral nt, Fractional ft) => nt -> ft -> ft 
expr n x = (x ^ n) / ((fromIntegral (fact n)) :: ft)

Тестирование под ghci:

$ ghci
GHCi, version 8.6.5: http://www.haskell.org/ghc/  :? for help
 λ> 
 λ> :load q65254539.hs
...
Ok, one module loaded.
 λ> 
 λ> expr 2 3
 4.5
 λ> expr 2 pi
 4.934802200544679
 λ> 
 λ> expr 3 (4::Double)
 10.666666666666666
 λ> 

Это работает! Большое спасибо за объяснение. Я как бы понял это сейчас, но я собираюсь сделать еще немного чтения. Я никогда раньше не использовал fromIntegral.

Yathi 11.12.2020 22:03

Другие вопросы по теме