Ответ 1
Хорошо. Всякий раз, когда у вас есть проблемы с типом, это лучший способ начать с предоставления явным аннотациям типа компилятору. Поскольку day
, month
и year
, вероятно, не слишком большие, рекомендуется сделать их Int
s. Вы также явно пропустили скобу, я исправил это для вас:
day_of_year :: Int -> Int -> Int -> Int
day_of_year year month day = n1 - (n2 * n3) + day - 30
where
n1 = floor(275 * fromIntegral month / 9)
n2 = floor((month + 9) / 12)
n3 = 1 + floor((year - 4 * floor(fromIntegral year / 4) + 2) / 3)
Когда я пытаюсь скомпилировать это, GHC выплескивает это довольно длинное сообщение об ошибке:
bar.hs:8:16: No instance for (RealFrac Int) arising from a use of `floor' Possible fix: add an instance declaration for (RealFrac Int) In the second argument of `(+)', namely `floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)' In the expression: 1 + floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3) In an equation for `n3': n3 = 1 + floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3) bar.hs:8:68: No instance for (Fractional Int) arising from a use of `/' Possible fix: add an instance declaration for (Fractional Int) In the first argument of `floor', namely `((year - 4 * floor (fromIntegral year / 4) + 2) / 3)' In the second argument of `(+)', namely `floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)' In the expression: 1 + floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)
Вторая ошибка - важная ошибка, первая - продолжение. По существу, он говорит: Int
не выполняет деление не floor
. В Haskell интегральное деление использует другую функцию (div
или quot
), но здесь вы хотите иметь плавающее деление. Поскольку year
закрепляется как Int
, вычитаемый 4 * floor(fromIntegral year / 4) + 2
также закрепляется как Int
. Затем вы делитесь на 3, но, как говорилось ранее, вы не можете использовать плавающее подразделение. Пусть исправить это, "отбрасывая" весь термин другому типу с помощью fromIntegral
перед делением (как и раньше).
fromIntegral
имеет подпись (Integral a, Num b) => a -> b
. Это означает: fromIntegral
принимает переменную интегрального типа (например, Int
или Integer
) и возвращает переменную любого числового типа.
Попробуйте скомпилировать обновленный код. Аналогичная ошибка появляется при определении n2
, я также исправил ее:
day_of_year :: Int -> Int -> Int -> Int
day_of_year year month day = n1 - (n2 * n3) + day - 30
where
n1 = floor(275 * fromIntegral month / 9)
n2 = floor((fromIntegral month + 9) / 12)
n3 = 1 + floor(fromIntegral (year - 4 * floor(fromIntegral year / 4) + 2) / 3)
Этот код компилируется и работает отлично (на моей машине). Haskell имеет определенные правила по умолчанию, заставляя компилятор выбрать Double
как тип для всех плавающих делений.
Собственно, вы можете сделать лучше. Как насчет использования целочисленного деления вместо повторных конверсий с плавающей точкой?
day_of_year :: Int -> Int -> Int -> Int
day_of_year year month day = n1 - (n2 * n3) + day - 30
where
n1 = 275 * month `quot` 9
n2 = (month + 9) `quot` 12
n3 = 1 + (year - 4 * (year `quot` 4) + 2) `quot` 3
Этот алгоритм всегда должен давать тот же результат, что и версия с плавающей запятой. Это примерно в десять раз быстрее. Выходы позволяют мне использовать функцию (quot
) в качестве оператора.
О вашей шестой точке: Да, было бы довольно легко сделать это. Просто поставьте fromEnum
перед year
, month
и day
. Функция fromEnum :: Enum a => a -> Int
преобразует любой тип перечисления в Int
. Все доступные числовые типы в Haskell (кроме сложных iirc) являются членами класса Enum
. Это не очень хорошая идея, хотя, как правило, у вас есть аргументы Int
, а лишние вызовы функций могут замедлить работу вашей программы. Улучшите преобразование явно, за исключением случаев, когда предполагается, что ваша функция будет использоваться со многими различными типами. Собственно, не беспокойтесь о микро-оптимизации слишком много. ghc имеет сложную и несколько скрытую инфраструктуру оптимизации, которая ускоряет работу большинства программ.
Изменение
Последующие действия 1, 2 и 3
Да, ваши рассуждения верны.
Последующее наблюдение 4
Если вы не даете вариант с плавающей запятой day_of_year
подписи типа, его тип по умолчанию равен day_of_year :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2
. Это по существу означает: day
, month
и year
могут быть произвольного типа, который реализует класс Integral
. Функция возвращает значение того же типа, что и day
. В этом случае a
, a1
и a2
- это просто переменные переменные типа - да, Haskell также имеет переменные на уровне типа (а также на уровне вида [который является типом типа], но эта другая история ) - это может быть выполнено любым типом. Поэтому, если у вас есть
day_of_year (2012 :: Int16) (5 :: Int8) (1 :: Integer)
Переменная a
получает instaniated до Int16
, a1
становится Int8
и a2
становится Integer
. Итак, какой тип возврата в этом случае?
Это
Integer
, посмотрите на подпись типа!
Последующее наблюдение 5
Фактически вы являетесь и не являетесь одновременно. Создание типа как можно более общего, безусловно, имеет свои преимущества, но при этом он смущает typechecker, потому что, когда типы, связанные с термином без явной аннотации типа, являются слишком общими, компилятор может обнаружить, что существует более одного возможного тип для срока. Это может привести к тому, что компилятор может выбрать тип с помощью некоторых стандартизированных, хотя и несколько неинтуитивных правил, или просто приветствует вас странной ошибкой.
Если вам действительно нужен общий тип, старайтесь что-то вроде
day_of_year :: Integral a => a -> a -> a -> a
То есть: аргументы могут иметь произвольный тип Integral
, но все аргументы должны иметь один и тот же тип.
Всегда помните, что Haskell никогда не применяет типы. Практически невозможно полностью выводить типы, когда есть (автоматическое) литье. Вы выполняете только вручную. Некоторые люди могут теперь рассказать вам о функции unsafeCoerce
в модуле Unsafe.Coerce
, который имеет тип a -> b
, но вы на самом деле не хотите знать. Вероятно, это не так, как вы думаете.
Последующее наблюдение 6
В div
нет ничего плохого. Разница начинает появляться, когда задействованы отрицательные числа. Современные процессоры (например, Intel, AMD и ARM) реализуют quot
и rem
в оборудовании. div
также использует эти операции, но делает некоторые трюки, чтобы получить другое поведение. Это лишний раз замедляет вычисления, когда вы действительно не зависите от точного поведения относительно отрицательных чисел. (На самом деле есть несколько машин, которые реализуют div
, но не quot
в аппаратном обеспечении. Единственное, что я могу запомнить сейчас, - mmix, хотя)