Подтвердить что ты не робот

Haskell, умножая Int и Float на функцию

Почему в ghci я могу ввести:

5.0 * (3 - 1)
> 10.0

Но если я попытаюсь создать функцию в файле .hs и загрузите ее в:

test :: Float -> Int -> Int -> Float
test a b c = a * (b - c)

У меня ошибка с ошибкой? "Не удалось совместить ожидаемый тип" Float "с предполагаемым типом" Int "? И как я могу написать функцию, которая принимает в одной точке с плавающей запятой и 2 целыми аргументами и выполняет над ними описанную выше операцию?

Я использую ghci v6.12.1, если это имеет значение...

4b9b3361

Ответ 1

Числовые литералы (т.е. просто ввод числа в коде Haskell) не являются фиксированным типом. Они полиморфны. Их нужно оценивать в определенном контексте, что требует, чтобы они имели конкретный тип.

Таким образом, выражение 5.0 * (3 - 1) не умножает Int на a Float. 5.0 должен иметь тип Fractional, 3 и 1 - каждый тип Num. 3 - 1 означает, что 3 и 1 оба должны быть одного и того же типа Num, но мы до сих пор еще не имеем никаких ограничений относительно того, какой он есть; результат вычитания является одним и тем же типом.

* означает, что оба аргумента должны быть одного и того же типа, и результат будет таким же. Так как 5.0 - это некоторый тип Fractional, то должен быть также (3 - 1). Мы уже знали, что 3, 1 и (3 - 1) должны быть некоторые Num, но все типы Fractional также являются Num, поэтому эти требования не конфликтуют.

Конечным результатом является то, что все выражение 5.0 * (3 - 1) - это некоторый тип Fractional, а 5.0, 3 и 1 - все те же типы. Вы можете использовать команду :t в GHCi, чтобы увидеть это:

Prelude> :t 5.0 * (3 - 1)
5.0 * (3 - 1) :: Fractional a => a

Но для фактического вычисления этого выражения нам нужно сделать это для определенного типа. Если бы мы оценивали это и передавали ему некоторую функцию, которая требовала бы Float, Double или какой-либо другой тип Fractional, тогда Haskell выбрал бы это. Если мы просто оцениваем выражение без какого-либо другого контекста, требующего, чтобы он был конкретным типом, Haskell имеет некоторые правила дефолта, чтобы автоматически выбрать один для вас (если правила по умолчанию не применяются, вместо этого вы получите ошибку типа о неоднозначных переменных типа).

Prelude> 5.0 * (3 - 1)
10.0
Prelude> :t it
it :: Double

Выше я оценил 5.0 * (3 - 1), а затем спросил тип магической переменной it, которую GHCi всегда связывает с последним оцениваемым ею значением. Это говорит мне о том, что GHCi по умолчанию присвоил моему типу Fractional a => a только Double, чтобы вычислить, что значение выражения было 10.0. При выполнении этой оценки он только когда-либо многократно (и вычитал) Double s, он никогда не умножал a Double на Int.


Теперь, что происходит, когда вы пытаетесь использовать несколько числовых литералов, которые выглядят так, как будто они могут быть разных типов. Но ваша функция test не умножает литералы, она умножает переменные определенных известных типов. В Haskell вы не можете умножить Int на Float, потому что оператор * имеет тип Num a => a -> a -> a - он принимает два значения одного и того же числового типа и дает результат, который является этим типом. Вы можете умножить Int на Int, чтобы получить Int или Float с помощью Float, чтобы получить Float. Вы не можете умножить Int на Float, чтобы получить ???.

Другие языки поддерживают эту операцию только путем неявной вставки вызовов в функции преобразования при некоторых обстоятельствах. Haskell никогда не подразумевает преобразования между типами, но имеет функции преобразования. Вам просто нужно вызвать их явно, если вы хотите, чтобы их вызывали. Это сделало бы трюк:

test :: Float -> Int -> Int -> Float
test a b c = a * fromIntegral (b - c)

Ответ 2

Попробуйте вместо этого:

test :: Float -> Int -> Int -> Float
test a b c = a * fromIntegral (b - c)

Почему это работает?

  • Так как b и c оба Int, выражение (b - c) также является Int.
  • Типичная подпись (*) равна Num a => a -> a -> a.
  • Поскольку a имеет тип Float, Haskell обновляет сигнатуру типа (a*) до Float -> Float.
  • Однако, поскольку (b - c) является Int, а не a Float, Haskell будет жаловаться, если вы попытаетесь сделать a * (b - c).
  • Типичная подпись fromIntegral равна (Integral a, Num b) => a -> b.
  • Следовательно, сигнатура типа fromIntegral (b - c) равна Num b => b.
  • Так как Float является экземпляром typeclass Num, вам разрешено делать a * fromIntegral (b - c), потому что подпись типа (*) равна Num a => a -> a -> a.
  • Результат от сигнатуры типа test оценивается до Float.

Надеюсь, что это помогло.

Ответ 3

Вам нужно использовать fromIntegral для целых чисел перед умножением на поплавки.

http://www.haskell.org/haskellwiki/Converting_numbers

В GHCI числа не считаются числами float или int, пока вы их не используете. (например, во время выполнения). Это лучше работает для разработки стиля REPL.

В самом компиляторе нет автоматического принуждения. Он it видит, что умножение предполагает, что два значения должны принадлежать типу-типу, который поддерживает умножение. например: умножение int или умножение поплавков. Поскольку вы не использовали какие-либо другие явно типизированные функции, он предполагал ints. Это предположение тогда отличается от вашей (необязательной) сигнатуры типа.

Ответ 4

Ваша функция test была более общей, прежде чем добавлять подпись:

> let test a b c = a * (b - c)
> :t test
  test :: Num a => a -> a -> a -> a

Вы можете ограничить его, но все типы должны быть одинаковыми:

test :: Fractional a => a -> a -> a -> a   -- some real types
test :: Integral   a => a -> a -> a -> a   -- all integer types
test :: Float -> Float -> Float -> Float  
test :: Int   -> Int   -> Int   -> Int   

test :: Int   -> Float -> Float -> Float  --wrong

Кстати, 2 не Int и 0.2 не Float, спросите gchi:

> :t 2
 2 :: Num a => a
> :t 0.2
 0.2 :: Fractional a => a