Функция signum
- это реализация обычного mathematical definition of sign, который условно возвращает значение в {-1,0,1}. Это теоретическое определение и, как таковое, оно не учитывает вычислительные затраты на операции или тип данных данных, поэтому умножение на (-1) представляет собой теоретический способ изменения стоимости с нулевой стоимостью. Из-за этого это не самая полезная обработка знака для программирования.Используемые случаи «signum» для упорядоченных числовых типов в Haskell
Корпус signum a == 0
не очень полезен, так как вы всегда можете напрямую протестировать a == 0
, без дополнительной оплаты signum a
. Что касается двух других ценностей, я думаю, что они используются всего три общих способов:
либо вы проверить, является ли значение положительным или отрицательным, чтобы запустить другой код условно, так как в:
f x y | signum x == -1 = h x y | otherwise = g x y
или умножить что-то на
1
или-1
, прежде чем работать с ним, как:f x y = g x (y * b) where b = signum x
или вы объявления d
1
или-1
к чему-то, прежде чем работать с ним, как:f x y = g x (y + b) where b = signum x
Во всех случаях было бы лучше Bool
значение знака. Таким образом, нам просто нужны функции для декомпозиции Num
в абсолютное значение и логический знак и обратная функция, которая меняет знак значения в зависимости от логического условия (которое представляет знак). Эта функция эквивалентна умножению числа 1
или -1
на число, поэтому мы определяем его как оператор, аналогичный (*)
. :
sgn a = a >= 0
(*.) a True = a
(*.) a _ = -a
abs a = a *. sgn a
signum1 a = 1 *. sgn a
Я добавил дихотомический вариант signum
, который может возвращать только '{-1,1}'. Обратите внимание, что перед ним с signum 0 = 0
мы получим обычную функцию signum
, но этот третий случай - это то, что, как мне кажется, обычно не полезно.
Мы можем закодировать так же на добавление оператора, потому что это очень частый случай, чтобы добавить 1
или -1
в зависимости от знака чего-то (вы можете видеть, что эти операторы просто лечить True
как 1
и False
в -1
):
(+.) a b = a + 1 *. b
(-.) a b = a - 1 *. b
Мы можем даже приложить объявления в классе Signed
, для удобства использования, включая правильные подписи и фиксацию.
Таким образом, приведенные выше общие примеры упростит не только код, но и время и пространство выполнения, потому что мы избегаем умножения (вместо этого, используя (*.)
), мы избегаем дополнительного сравнения, если у нас есть Bool
, мы можем получить знак из одного типа данных и использовать его для другого типа без преобразования типа, и мы используем короткий тип Bool
вместо потенциально длинного типа класса Num
. Но мы получаем большую гибкость, одновременно позволяя оптимизировать код и типы данных.
Таким образом, мой вопрос заключается в том, что существуют случаи, отличные от трех распространенных случаев использования, например, случаев, которые нелегко охватить этим подходом, случаи, для которых текущая функция signum
выгодна в отношении подхода к знаку Bool. Точнее, могу ли я полностью избежать использования текущей функции signum
без потери эффективности или четкости кода?
Edit: Я изменил первый абзац более "нейтральной" моды, следуя Reid Barton комментарий.
обновление Прогресса: код этого подхода был значительно улучшен для простоты и ясности, с большой помощью текущих ответов и комментариев.
Полагает, что ноль - это просто другой случай. Вы не можете сказать, что знак * * нуля равен «+». Это одновременно «+» и «-», начиная с «-0 == + 0». (Нет, я не dv). –
Я думаю, что этот вопрос может быть поставлен более «нейтральным» способом. –
В отчете Haskell 2010 требуется только 'abs x * signum x == x'; он ничего не говорит о размере codomain 'signum'. – chepner