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

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

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

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

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


person obecalp    schedule 18.12.2008    source источник
comment
Ничего общего со сборкой, на самом деле. Просто поддерживайте тот же интерфейс с семейством алгоритмов.   -  person obecalp    schedule 18.12.2008
comment
Помимо странного вопроса об универсальном программировании и частичной специализации (поищите каррирование), вопрос о замене переменных также странен: в Haskell нет такой вещи, как замена содержимого двух блоков, потому что переменные в Haskell не поля с данными в них.   -  person ShreevatsaR    schedule 18.12.2008
comment
Я знаком с каррированием, но это не специализация, а скорее тип. Я пересмотрел вопрос для канонического примера GP в Haskell.   -  person obecalp    schedule 18.12.2008
comment
Я все еще изо всех сил пытаюсь понять этот вопрос, потому что вся прелесть Haskell в том, что он позволяет вам писать функции наиболее общим способом... возможно, вам следует прочитать хотя бы статью «Почему функциональное программирование имеет значение»? md.chalmers.se/~rjmh/Papers/whyfp.html   -  person ShreevatsaR    schedule 18.12.2008
comment
@ShreevatsaR: В целом верно, но не совсем. Одной из форм универсального программирования, которую вы не можете сделать в (стандартном) Haskell, но можете сделать в C++, является создание типов, параметризованных числами. C++ позволяет параметризовать типы целочисленными константными выражениями, которые могут быть рекурсивными и, таким образом, разрешать произвольные вычисления.   -  person j_random_hacker    schedule 05.07.2010
comment
@ShreevatsaR: Так, например, в Haskell вы не можете создавать матричные и векторные типы, которые кодируют свои размеры в своем типе. Это, в свою очередь, означает, что вы не можете написать программу на Haskell, которая статически проверяет, что все операнды матричных операций имеют согласованные размеры. Основное ограничение заключается в том, что в Haskell типы не могут зависеть от результатов функций. (Я полагаю, что причина этого в том, что его разрешение сделало бы неразрешимым автоматический вывод типов.)   -  person j_random_hacker    schedule 06.07.2010
comment
Есть много подходов к общему программированию на Haskell. Вы можете начать здесь: haskell.org/haskellwiki/Research_papers/Generics   -  person Steven Shaw    schedule 25.06.2012
comment
Я думаю, что люди неправильно поняли вопрос. Я не читал книгу по Haskell, но использую функциональное программирование на С++. мой более конкретный вопрос: как вы параметризуете функциональную область для равностороннего треугольника и квадрата. в С++ у вас есть area(triangle_type t) {return t.val*t.val/2;} и area(square_type t) { return t.val*t.val;}. моя глупая догадка для функционального языка заключалась бы в том, что area() в area(triangle(double)) и area(square(double)) должны быть специализированными.   -  person kirill_igum    schedule 11.09.2013
comment
В Haskell это будет: data Polygon x = Square x | Triangle x x | Rectangle x x, а функция area :: Polygon -> x может быть определена как: area (Square s) = s*s, area (Triangle h b) = h * b/2 и area(Rectangle l w) = l * w.   -  person recursion.ninja    schedule 20.01.2014
comment
@j_random_hacker Я считаю, что в стандартном Haskell вы можете кодировать числа как типы, используя типы как числа Пеано, например. data Zero; data Succ a, чтобы вы могли затем определить вектор как data Vector len = ... и иметь функцию, которая работает только с векторами размером три f :: Vector (Succ (Succ (Succ Z))) -> .... Если вы используете GHC, вы можете делать то же самое, что и в C++ (т. е. использовать целочисленные литералы), используя литералы уровня типа.   -  person Frerich Raabe    schedule 03.08.2017


Ответы (5)


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

Обмен (как он понимается и используется в 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 это работает во время выполнения (специализация функции для определенных значений или шаблонов значений).

person Daniel Earwicker    schedule 18.12.2008
comment
swap - это канонический пример gp c++. Вы можете попытаться проиллюстрировать ту же концепцию на каноническом примере Haskell. Я пересмотрел вопрос, чтобы отразить это. - person obecalp; 18.12.2008

Как говорит Эрвикер, в 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")
person TheMarko    schedule 18.12.2008
comment
Пожалуйста, имейте в виду, что на самом деле это не меняет местами значения, а возвращает новую пару (поскольку исходная пара неизменна). - person TheMarko; 18.12.2008
comment
Это очень похоже на то, что делает ocaml. Что, если я хочу поменять местами две хеш-таблицы или карту, как вы гарантируете, что значения не будут скопированы? - person obecalp; 18.12.2008
comment
Если вы хотите работать с большими таблицами состояний, вы, вероятно, вообще не захотите использовать функциональное программирование. Если вам абсолютно необходимо, есть IORef и монада состояния для выполнения вычислений, которые поддерживают состояние. - person TheMarko; 18.12.2008
comment
Ну, вопрос, который я задаю, касается частичной специализации в том смысле, что функция может поддерживать альтернативные реализации для разных типов одного и того же интерфейса. - person ididak; 19.12.2008
comment
@obecalp: вы можете создать новую карту, в которой два значения поменялись местами. Эта новая карта будет иметь большую часть структуры старой, поэтому это не операция копирования O (N), а просто O (log N). - person yairchu; 02.09.2009
comment
@obecalp: что значит поменять местами 2 карты? вы бы просто сделали swap (map1, map2), где map1 и map2 — переменные, содержащие карты. значения не будут скопированы, вы просто возвращаете те же значения в другом порядке. - person Claudiu; 15.08.2010

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

*Main> пусть swap (a,b) = (b,a)
*Main> :t swap
swap :: (t, t1) -> (t1, t)

Что касается частичной специализации, у 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
Но, прежде чем я использую C++ значение универсального.

person ja.    schedule 19.12.2008
comment
Спасибо, оба мертвы, на первый даже ссылки нет. Первым должен быть: [ссылка] lambda.haskell.org/platform/doc/current/ghc-doc/users_guide/, а второй можно найти на archive.org: [ссылка] http://web.archive.org/web/20080511185727/http://haskell.readscheme.org/generic.html - person ja.; 24.11.2012

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

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

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

person stonemetal    schedule 01.09.2009

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

person Magnus    schedule 19.12.2008