Если вы берете пример
map :: (a -> b) -> [a] -> [b]
Это означает, что карта принимает 2 аргумента
- Функция от типа a до типа b
- Список типа а
И это возвращает
- Список б
Вы уже можете видеть образец здесь, но он станет более понятным, когда мы подставим 'a' и 'b'
Таким образом, определение этого типа будет
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
) - это то, что вы ищете. Это то, как вы можете заставить компилятор или интерпретатор идентифицировать функцию - тогда вы можете опустить ее, и компилятор / интерпретатор найдет их (и иногда даже лучше, чем можно было бы подумать)