Типы в Хаскеле - PullRequest
       43

Типы в Хаскеле

9 голосов
/ 11 мая 2010

Я немного новичок в Хаскеле, и мне трудно понять, как предполагаемые типы и такие работают.

map :: (a -> b) -> [a] -> [b]
(.) :: (a -> b) -> (c -> a) -> c -> b

Что именно это означает?

foldr :: (a -> b -> b) -> b -> [a] -> b
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl1 :: (a -> a -> a) -> [a] -> a

В чем разница между ними?

И предполагаемый тип

карта сворачивания: [a] -> [a -> a] -> [a]

но почему это так?

СПАСИБО!

Ответы [ 4 ]

10 голосов
/ 11 мая 2010

Если вы берете пример

map :: (a -> b) -> [a] -> [b]

Это означает, что карта принимает 2 аргумента

  1. Функция от типа a до типа b
  2. Список типа а

И это возвращает

  1. Список б

Вы уже можете видеть образец здесь, но он станет более понятным, когда мы подставим 'a' и 'b'

  • a = String
  • b = Int

Таким образом, определение этого типа будет

map :: (String -> Int) -> [String] -> [Int]

так что теперь это функция, которая принимает String и возвращает Int, а также список Strings и возвращает список Ints.

Скажите, что наша функция принимает строку и возвращает значение Int read. read использует заданную вами строку, чтобы преобразовать ее во что-то другое. Поскольку мы поместили его здесь в контекст Int, он преобразует строку в int

map read ["1", "2", "112", 333"]

Это приведет к

[1, 2, 112, 333]

потому что map принимает вашу функцию read и отображает (применяет) ее к каждому элементу списка. Вам не нужно СКАЗАТЬ read это аргумент типа read "1" или read n, потому что map позаботится об этом за вас.

И это действительно все, что нужно. Тип функции говорит только о том, какие типы он принимает и какой тип возвращает. Конечно, есть и карри, но вы придете к этому позже нарочно или нет! По сути это означает, что функция не принимает много аргументов, а только one. Скажем, вы берете функцию +. Если вы оцениваете 1+2, он возвращает функцию, которая принимает другое число, добавленное к 1, и, поскольку здесь есть другое число, 2, оно будет использовать это. Вы могли бы оценить это как (1+) и передать его, возможно, добавив число через некоторое время. Это понятнее, когда у вас нет инфиксного синтаксиса +. Можно было бы написать (+) 1 2, поэтому сначала это утверждение становится (+) 1, которое имеет тип (упрощенно! Я не буду вводить здесь класс типов Num) Int -> Int. Так что (+) 1 (или (1+) в этом отношении) - это просто ДРУГАЯ функция, к которой вы можете применить значение, и в этот момент результат сможет вычисляться как 3.

Вот как это выглядит на практике: (Игнорируйте часть (Num a) =>, если она вас смущает, это концепция, о которой вы узнаете позже. Просто замените здесь типы a на Int, если хотите, и полностью игнорировать (Num a) => часть.)

 (+)  :: (Num a) => a -> a -> a
 (+2) :: (Num a) => a -> a
(1+)  :: (Num a) => a -> a
(1+2) :: (Num a) => a

Prelude> (+2) 5
7
Prelude> map (+3) [1,2,3]
[4,5,6]

И к вашему второму вопросу: вы вообще не определяете предполагаемые типы. Они называются «выведены», потому что компилятор / интерпретатор «выводит» (читай: вычисляет) сами типы, без явного присвоения им имен.

Об отличиях foldX: все они делают одно и то же: сводят список к одному значению. Разница между функциями является лишь внутренним определением. foldl сворачивает список слева, а foldr - справа.

Таким образом, чтобы подвести итог списка, вы можете использовать все из них, как это ...

foldl1 (+) [1,2,3] == 6
foldr (+) 0 [1,2,3] == 6
foldl (+) 0 [1,2,3] == 6

Видите ли, кроме foldl1, вы предоставляете функцию для складывания, начальное значение (аккумулятор) и список для складывания. fold1 имеет свой собственный аккумулятор, вы не отдаете его своему собственному.

На практике лучше использовать foldr, потому что в отличие от fold он подходит для БОЛЬШИХ списков без сбоев из-за переполнения стека (ти, хи). Исключением из этого правила является foldl' (обратите внимание на "'"!) В Data.Map - это строгая складка, которая также подходит для больших списков.

Если вы хотите сами придавать функциям их типы (если вы их написали), рассмотрите следующий пример:

double :: Int -> Int
double n = 2 * n

Или по-хетски

double :: Int -> Int
double = (*2)

Первая строка обоих примеров (double :: Int -> Int) - это то, что вы ищете. Это то, как вы можете заставить компилятор или интерпретатор идентифицировать функцию - тогда вы можете опустить ее, и компилятор / интерпретатор найдет их (и иногда даже лучше, чем можно было бы подумать)

3 голосов
/ 11 мая 2010

Функции в Haskell используют нотацию каррирования.Определение типа

plus :: Int -> Int -> Int

означает, что plus - это функция, которая принимает два Int с и возвращает значение самого правого типа (который снова равен Int).Более подробно, каррирование означает, что вы работаете с частично примененными функциями, которые позволяют писать код, подобный

plus1 :: Int -> Int
plus1 = plus 1

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

Определение

map :: (a -> b) -> [a] -> [b]

, следовательно, означает Для любых типов a и b, map требуетсяфункция от a до b и список a, из которого она создает список b * .

Давайте рассмотрим пример

map show [1, 2, 3]

Видим, данный список является списком Int, поэтому a = Int.Кроме того, map должно применяться show к каждому элементу, который возвращает String.Поскольку show должен соответствовать типу a -> b из определения, мы можем вывести, что b = String.

Наше выражение возвращает [b], поэтому результат равен [String].

Чтобы сделать такие сигнатуры еще более четкими, мы можем написать их, используя более подробный синтаксис

map :: forall a b . (a -> b) -> [a] -> [b]

Теперь это явно заявляет, что map должен работать со всеми типами a и b.


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

Пример: factorial n = foldl (*) 1 [1..n]

Подпись foldr map становится.

foldr map :: [a] -> [a -> a] -> [a]

Вы также можете получить их, просто набрав :t foldr map в интерпретаторе Haskell (GHCi).

1 голос
/ 11 мая 2010

Строчные буквы в объявлениях типа: переменные типа . Это означает, что при наличии a это должен быть тот же тип.

Подвыражения в скобках (например, (a -> b)) обычно означают, что функция принимает функцию в качестве одного из своих параметров.

Прямоугольные скобки указывают список чего-либо.

Итак, map принимает следующие аргументы:

  • (a -> b) функция, которая принимает один аргумент
  • [a] список, на который может воздействовать функция первого аргумента

и создает [b] список в результате.

Мы можем декодировать (.) таким же образом:

  • (a -> b) - это функция, которая принимает один аргумент
  • (c -> a) - это функция, которая выдает тот же тип, что и аргумент первой функции
  • c аргумент того же типа, что и вторая функция

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

Допустим, у нас есть 2 функции ord :: Char -> Int, которые принимают символ и возвращают его целочисленное представление, и add32 :: Int -> Int, который добавляет 32 к его аргументу.

Когда мы пишем upcaseVal = add32 . ord, функция upcaseVal теперь имеет сигнатуру upcaseVal :: Char -> Int, поскольку функция (.) оставила бы ее с c -> b, и мы подставили их в определение функции.

Так что foldr map должно быть так:

  1. * Первый аргумент 1056 * - это функция, которая принимает 2 аргумента, которыми является map.
  2. Поскольку вывод функций должен быть того же типа, что и второй аргумент ((a -> b -> b), мы видим, что в map a = b

Таким образом, окончательный тип будет:

(b -> b) -> [b] -> [b] -> b -> [b] -> b

Но я думаю, это не должно вас сильно беспокоить, так как компилятор работает с такими вещами большую часть времени.

1 голос
/ 11 мая 2010

В Haskell переменная в нижнем регистре является переменной типа. Тогда как Char просто означает Char, a или b, может означать Char, или Bool, или Integer, или какой-либо другой тип. Ключ в том, что все a будут одного типа. И все b будут одного типа.

Например, map принимает в качестве первого аргумента функцию, которая принимает одну переменную типа a и возвращает другую типа b. Затем map возвращает новую функцию, которая принимает список типа a. Эта новая функция возвращает список типа b.

Предположим, у вас есть функция increment, которая добавляет одну к целому числу. map increment [1,2,3] в конечном итоге вернет список [2,3,4].

Предыдущее определение map с заменой переменных типа выглядело бы следующим образом:

map :: increment -> [1,2,3] -> [2,3,4]

и increment, кстати, будет выглядеть

increment :: Integer -> Integer

Причина, по которой это написано смешно, в том, что вы можете частично применять функции. Здесь я сделаю новую функцию, основанную на частичном применении карты:

incrementList = map increment

Тогда вы можете использовать

incrementList [1,2,3]

, который даст тот же результат

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...