Как вы выполняете универсальное программирование на Haskell?

StackOverflow https://stackoverflow.com/questions/377093

  •  22-08-2019
  •  | 
  •  

Вопрос

Исходя из C ++, я нахожу универсальное программирование незаменимым.Интересно, как люди подходят к этому в Haskell?

Скажите, как написать универсальную функцию подкачки в Haskell?

Существует ли эквивалентная концепция частичной специализации в Haskell?

В C ++ я могу частично специализировать универсальную функцию подкачки специальной для универсального контейнера map / hash_map, у которого есть специальный метод подкачки для замены контейнера O (1).Как вы это делаете в Haskell или каков канонический пример универсального программирования в Haskell?

Это было полезно?

Решение

Это тесно связано с вашим другим вопросом о Haskell и quicksort.Я думаю, вам, вероятно, нужно прочитать хотя бы Введение из книги о Хаскелле.Звучит так, как будто вы еще не поняли ключевой момент в этом, который заключается в том, что он запрещает вам изменять значения существующих переменных.

Swap (как понимается и используется в C ++) по самой своей природе связан с изменением существующих значений.Это позволяет нам использовать имя для ссылки на контейнер и заменять этот контейнер совершенно другим содержимым, а также специализировать эту операцию на быстром выполнении (и отсутствии исключений) для конкретных контейнеров, что позволяет нам реализовать подход "изменять и публиковать" (критичный для написания кода, защищенного от исключений, или попытки написать код без блокировок).

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

Предположим, мы хотели "измерить" список в Haskell:

measure :: [a] -> Integer

Это объявление типа.Это означает, что функция measure принимает список всего , что угодно (a является параметром универсального типа, поскольку он начинается со строчной буквы) и возвращает целое число.Таким образом, это работает для списка любого типа элемента - это то, что называется шаблоном функции в C ++ или полиморфной функцией в Haskell (не то же самое, что полиморфный класс в C ++).

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

measure [] = 0

т. е.измерьте пустой список, и вы получите ноль.

Вот очень общее определение, которое охватывает все остальные случаи:

measure (h:r) = 1 + measure r

Бит в круглых скобках на LHS - это шаблон.Это означает:возьмите список, отломите начало и назовите его h, оставшуюся часть назовите r.Тогда эти имена будут параметрами, которые мы можем использовать.Это будет соответствовать любому списку, в котором есть хотя бы один элемент.

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

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

Как говорит Earwicker, этот пример не столь значим в Haskell.Если вы абсолютно хотите иметь это в любом случае, вот что-то похожее (замена двух частей пары), c & p из интерактивного сеанса:

GHCi, version 6.8.2: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
Prelude> let swap (a,b) = (b,a)
Prelude> swap("hello", "world")
("world","hello")
Prelude> swap(1,2)
(2,1)
Prelude> swap("hello",2)
(2,"hello")

В Haskell функции настолько универсальны (полиморфны), насколько это возможно - компилятор определит "Наиболее общий тип".Например, пример swap в TheMarko по умолчанию является полиморфным при отсутствии подписи типа:

*Главная> пусть поменяется местами (a,b) = (b,a)
*Главная> :подкачка t
поменять местами ::(t, t1) -> (t1, t)

Что касается частичной специализации, ghc имеет расширение, отличное от 98:
файл:///C:/ghc/ghc-6.10.1/doc/users_guide/pragmas.html#специализируюсь-pragma

Также обратите внимание, что существует несоответствие в терминологии.То, что называется универсальным в c ++, Java и C #, в Haskell называется полиморфным."Общий" в Haskell обычно означает политипичный: http://haskell.readscheme.org/generic.html
Но, прежде всего, я использую c ++ в значении generic.

В Haskell вы бы создали классы типов.Классы типов не похожи на классы в языках OO.Возьмем класс числового типа, в нем говорится, что все, что является экземпляром класса, может выполнять определенные операции (+ - * /), поэтому Integer является членом Numeric и предоставляет реализации функций, необходимых для того, чтобы считаться числовыми, и может использоваться везде, где ожидается числовое значение.

Допустим, вы хотите иметь возможность выводить целые числа и строки.Затем вы бы объявили Int и String как экземпляры класса типа Foo.Теперь везде, где вы видите тип (Foo a), вы можете использовать Int или String .

Причина, по которой вы не можете добавлять целые числа и значения с плавающей точкой напрямую, заключается в том, что add имеет тип (числовой a) a -> a -> a a - это переменная типа, и, как и обычные переменные, ее можно привязать только один раз, поэтому, как только вы привязываете ее к Int, каждая a в списке должна быть Int.

Прочитав достаточно в книге Haskell, чтобы действительно понять ответ Earwicker, я бы посоветовал вам также прочитать о классах типов.Я не уверен, что означает “частичная специализация”, но, похоже, они могут быть близки к этому.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top