Перегрузка сигнатур функций Haskell

Я получаю следующее сообщение об ошибке при компиляции:

Подпись повторяющегося типа:
weightedMedian.hs:71:0-39: findVal :: [ValPair] -> Double -> Double
weightedMedian.hs:68:0-36: findVal :: [ValPair] -> Целый -> Двойной

Мое решение состоит в том, чтобы иметь findValI и findValD. Однако findValI просто преобразует тип Int в Double и вызывает findValD.

Также я не могу сопоставлять шаблоны для типов Num (Int, Double), поэтому я не могу просто изменить сигнатуру типа на

findVal :: [ValPair] -> Num -> Double   

На многих языках мне не понадобятся разные имена. Зачем мне нужны разные имена в Haskell? Сложно ли будет добавить это в язык? Или там драконы?


person Tim Perry    schedule 25.05.2011    source источник
comment
Попробуйте удалить сигнатуру функции, загрузить ее в GHCI и ввести :t findVal, чтобы увидеть автоматически вычисляемую сигнатуру функции. Вероятно, у него не было бы ни Int, ни Double, а вместо этого он выглядел бы как Num a => [ValPair] -> a -> Double, а это именно то, что вам нужно.   -  person Anupam Jain    schedule 25.05.2011
comment
@Anupam Jain: На самом деле это настоящая версия [ValPair] -> Double -> Double. Я мог бы использовать технику Аугустса, чтобы обойти это, и тогда я действительно получил бы подпись, которую вы указали.   -  person Tim Perry    schedule 25.05.2011


Ответы (5)


Специальный полиморфизм (и перегрузка имен) обеспечивается в Haskell классами типов:

class CanFindVal a where
          findVal :: [ValPair] -> a -> Double

instance CanFindVal Double where
     findVal xs d = ...

instance CanFindVal Int where
     findVal xs d = findVal xs (fromIntegral d :: Double)

Обратите внимание, что в этом случае, поскольку findVal «действительно» нуждается в Double, я бы всегда использовал двойное число, а когда мне нужно было передать ему целое число, просто использовал fromIntegral на месте вызова. Обычно вам нужны классы типов, когда на самом деле задействовано другое поведение или логика, а не беспорядочно.

person sclv    schedule 25.05.2011
comment
Конечно, это работает, но в данном случае это не идиоматическое решение. CanFindVal не очень значимый класс типов. -1 - person luqui; 25.05.2011
comment
который sclv как бы сказал: обычно вам нужны классы типов, когда на самом деле задействовано другое поведение или логика, а не беспорядочно. - person MatrixFrog; 25.05.2011
comment
Спасибо... Наверное, я привык к неразборчивому характеру перегрузки С++. Как указывает Луки, я, вероятно, мог бы покончить с этой проблемой. - person Tim Perry; 25.05.2011
comment
Я знаю, что это почти десятилетие спустя, но я слышал, что классы типов реализованы как поиск по словарю. Нельзя ли это сделать во время компиляции? - person xilpex; 15.11.2020
comment
@xilpex, вы могли бы так подумать, но, насколько я помню, в некоторых случаях (например, полиморфная рекурсия) вы не можете статически разрешать каждый экземпляр, даже при компиляции всей программы. Насколько я знаю, нет никаких доказательств того, что это невозможно, но все известные мне попытки сделать это терпели неудачу. Если классы типов сделать несколько менее общими и мощными, тогда да :-) - person sclv; 10.12.2020

Для поддержки как findVal :: [ValPair] -> Double -> Double, так и findVal :: [ValPair] -> Int -> Double требуется специальный полиморфизм (см. http://www.haskell.org/haskellwiki/Ad-hoc_polymorphism), что обычно опасно. Причина в том, что специальный полиморфизм позволяет изменять семантику с одним и тем же синтаксисом.

Haskell предпочитает так называемый параметрический полиморфизм. Вы видите это все время с сигнатурами типов, где у вас есть переменная типа.

Haskell поддерживает более безопасную версию специального полиморфизма через классы типов.

У вас есть три варианта.

  1. Продолжите то, что вы делаете, с явным именем функции. Это разумно, это даже используется некоторыми библиотеками c, например opengl.
  2. Используйте класс пользовательского типа. Это, вероятно, лучший способ, но он тяжелый и требует большого количества кода (по очень компактным стандартам haskell). Посмотрите на ответ sclv для кода.
  3. Попробуйте использовать существующий класс типов и (если вы используете GHC) получите производительность со специализациями.

Так:

findVal :: Num a => [ValPair] -> a -> Double
{-# SPECIALISE findVal :: [ValPair] -> Int -> Double #-}
{-# SPECIALISE findVal :: [ValPair] -> Double -> Double #-}
findVal = ...
person Philip JF    schedule 25.05.2011
comment
Я проверю специализацию. Это похоже на то, чтобы прокрасться через заднюю дверь, но я возьму это. - person Tim Perry; 25.05.2011

Haskell не поддерживает перегрузку в стиле C++ (ну, вроде как, с классами типов, но мы не используем их таким же образом). И да, есть некоторые драконы, связанные с его добавлением, в основном связанные с выводом типа (становится экспоненциальным временем или неразрешимым или что-то в этом роде). Однако увидеть такой «удобный» код довольно редко в Haskell. Какой из них, Int или Double? Поскольку ваш метод Int делегирует метод Double, я предполагаю, что Double является "правильным". Просто используйте тот. Из-за буквальной перегрузки вы все равно можете называть это так:

findVal whatever 42

И 42 будет рассматриваться как Double. Единственный случай, когда это нарушается, - это если вы где-то получили что-то, что фундаментально является Int, и вам нужно передать это как этот аргумент. Затем используйте fromIntegral. Но если вы стремитесь, чтобы ваш код везде использовал «правильный» тип, этот случай будет необычным (и когда вам все же придется конвертировать, на это стоит обратить внимание).

person luqui    schedule 25.05.2011
comment
Ваш вопрос, что это, Int или Double? в значительной степени на месте. Меня попросили написать функцию взвешенной медианы, очень похожую на функцию взвешенного среднего, которая принимает набор весов и значений и вычисляет, в данном случае, взвешенную медиану. Что очень хорошо, если ваши веса являются только натуральными числами. Однако у нас есть дробные веса, и я должен был аппроксимировать медиану. Что математически несколько бессмысленно, но полезно в контексте проекта. Итак, да, это должен быть Int или Integer, но я использую Doubles для весов. Это сделало мою голову больной! - person Tim Perry; 25.05.2011

В этом случае, я думаю, легко написать функцию, которая обрабатывает как Int, так и Double для второго аргумента. Просто напишите findVal, чтобы он вызывал realToFrac во втором аргументе. Это преобразует Int в Double и просто оставит Double в покое. Затем позвольте компилятору вывести тип за вас, если вы ленивы.

person augustss    schedule 25.05.2011
comment
RealToFrac определенно решает эту проблему. Я думаю, что большая проблема в том, что я думал как программист на C++..... - person Tim Perry; 25.05.2011
comment
@Tim Perry Иногда было бы неплохо, если бы Haskell имел тип перегрузки C ++, но часто есть хороший способ обойти это. - person augustss; 25.05.2011
comment
@augustss Под словом «иногда» вы имеете в виду всякий раз, когда вывод NP-полного типа звучит просто захватывающе? - person semicolon; 15.07.2016

Во многих других языках программирования вы можете объявлять (своего рода) функции, имеющие одно и то же имя, но разные другие вещи в их подписях, например, разные типы параметров. Это называется перегрузка и, безусловно, является наиболее популярным способом достижения специального полиморфизма.

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

person pangiole    schedule 02.08.2017