2008-12-18 3 views
8

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

Скажите, как написать общую функцию свопинга в Haskell?

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

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

+31

Это шутка, не так ли? Это похоже на то, как вы можете точно контролировать все в сборке: P – ShreevatsaR

+0

Ничего общего с сборкой. Просто поддерживайте один и тот же интерфейс с семейством алгоритмов. – obecalp

+3

Помимо странного вопроса о родовом программировании и частичной специализации (поиск currying), вопрос о «замене переменных» также странный: в Haskell нет такой вещи, как «замена содержимого двух ящиков», поскольку переменные в Haskell * не * ящики с данными в них. – ShreevatsaR

ответ

27

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

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

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

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

measure :: [a] -> Integer 

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

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

measure [] = 0 

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

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

measure (h:r) = 1 + measure r 

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

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

+0

swap - канонический пример C++ gp. Вы можете попытаться проиллюстрировать ту же концепцию каноническим примером Haskell. Я пересмотрел вопрос, чтобы отразить это. – obecalp

9

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

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") 
+4

Просьба иметь в виду, что это фактически не меняет значения, а возвращает новую пару (поскольку исходная пара неизменна). – TheMarko

+0

Это очень похоже на то, что делает ocaml. Что делать, если я хочу обменять две таблицы хэша или карту, как вы гарантируете, что значения не скопированы? – obecalp

+0

Если вы хотите работать на больших таблицах состояния, вы, вероятно, не хотите использовать функциональное программирование в первую очередь. Если вам абсолютно необходимо, есть IORef и государственная монада для выполнения расчетов, которые поддерживают состояние. – TheMarko

2

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

6

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

* Main> пусть свопа (а, Ь) = (Ь, а)
* Main>: т своп
своп: : (т, t1) -> (t1, т)

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

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

+1

Ваша ссылка мертва – Kos

+0

Спасибо, оба умерли, что вначале нет даже ссылки. Это первое: [link] http://lambda.haskell.org/platform/doc/current/ghc-doc/users_guide/pragmas.html#specialize-pragma, а второе можно найти на archive.org: [link] http://web.archive.org/web/20080511185727/http://haskell.readscheme.org/generic.html –

5

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

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

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

Смежные вопросы