В чем разница между функцией и функтором в Haskell? Только определение?

В Haskell при написании функции это означает, что мы сопоставляем что-то (ввод) с другим (выход). Я попытался LYAH понять определение Functor: похоже на обычный Functor.

  1. Есть ли какое-либо ограничение на то, что функцию можно назвать функтором?
  2. Разрешено ли Functor иметь ввод-вывод или любой другой побочный эффект?
  3. Если в Haskell «все является функцией», то какой смысл вводить понятие «функтор»? Ограниченная версия функции или расширенная версия функции?

Очень запутался, нужен ваш совет. Спасибо.


person vik santata    schedule 06.04.2016    source источник
comment
«Функтор: выглядит точно так же, как обычный функтор». - ну, кажется, логично! ... на самом деле даже это не совсем правильно: все Functor являются конкретно эндофункторами категории Hask.   -  person leftaroundabout    schedule 06.04.2016


Ответы (5)


Это помогает знать немного теории категорий. Категория — это просто набор объектов со стрелками между ними. Они могут моделировать многое в математике, но для наших целей нас интересует категория типа; Hask — это категория типов Haskell, где каждый тип является объектом в Hask, а каждая функция — стрелкой между типом аргумента и типом возвращаемого значения. Например, Int, Char, [Char] и Bool — это все объекты в Hask, а ord :: Char -> Int, odd :: Int -> Bool и repeat :: Char -> [Char] могут быть примерами стрелок в Hask.

Каждая категория имеет несколько свойств:

  1. Каждый объект имеет идентификационную стрелку.

  2. Стрелки составляют, так что если a -> b и b -> c стрелки, то и a -> c тоже.

  3. Стрелки идентичности — это как левые, так и правые идентичности для композиции.

  4. Композиция ассоциативна.

Причина, по которой Hask является категорией, заключается в том, что каждый тип имеет функцию идентификации, а функции составляют. То есть id :: Int -> Int и id :: Char -> Char являются стрелками-идентификаторами для категории, а odd . ord :: Char -> Bool — составными стрелками.

(Не обращайте внимания на то, что мы думаем, что id — это полиморфная функция с типом a -> a, а не набор отдельных функций с конкретными типами. Это демонстрирует концепцию теории категорий, называемую естественным преобразованием, которая вам не нужна. думать теперь)


В теории категорий функтор F — это отображение между двумя категориями; он сопоставляет каждый объект одной категории с объектом другой, а также также сопоставляет каждую стрелку одной категории со стрелкой другой. Если a является объектом одной категории, мы говорим, что F a является объектом другой категории. Мы также говорим, что если f — стрелка первой категории, то соответствующая стрелка другой категории, если F — f.

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

  1. F должен сопоставить стрелку идентичности объекта a с стрелкой идентичности объекта F a.
  2. F должен сохранять композицию. Это означает, что композиция двух стрелок в первой категории должна быть сопоставлена ​​с композицией соответствующих стрелок в другой категории. То есть, если h = g ∘ f находится в первой категории, то h сопоставляется с F h = F g ∘ F f в другой.

Наконец, эндофунктор — это специальное имя для функтора, который отображает одну категорию в саму себя. В Hask класс типов Functor отражает идею эндофунктора от Hask до Hask. Сам конструктор типов отображает типы, а fmap используется для отображения стрелок.


Возьмем, к примеру, Maybe. Конструктор типа Maybe является эндофунктором, поскольку он сопоставляет объекты в Hask (типы) с другими объектами в Hask (другие типы). (Этот момент немного скрыт, поскольку у нас нет новых имен для целевых типов, поэтому Maybe можно представить как сопоставление Int с типом Maybe Int.)

Чтобы сопоставить стрелку a -> b с Maybe a -> Maybe b, мы предоставляем определение для fmap в экземпляре Maybe Int. Maybe также отображает функции, но вместо этого использует имя fmap. Законы функтора, которым он должен подчиняться, совпадают с двумя, перечисленными в определении функтора.

  1. fmap id = id (карты с id :: Int -> Int по id :: Maybe Int -> Maybe Int.
  2. fmap f . fmap g = fmap f . g (то есть fmap odd . fmap ord $ x должно возвращать то же значение, что и fmap (odd . ord) $ x для любого возможного значения x типа Maybe Int.

В качестве несвязанного тангенса другие указали, что некоторые вещи в Haskell не являются функциями, а именно литеральными значениями, такими как 4 и "hello". Хотя это верно для языка программирования (вы не можете, например, составить 4 с другой функцией, которая принимает Int в качестве значения), верно то, что в теории категорий вы можете заменить значения функциями от типа единицы () к типу значения. То есть буквальное значение 4 можно рассматривать как стрелку 4 :: () -> Int, которая при применении к (единственному) значению типа () возвращает значение типа Int, соответствующее целому числу 4. Эта стрелка будет составляться, как и любая другая; odd . 4 :: () -> Bool будет отображать значение из типа единицы измерения в логическое значение, указывающее, является ли целое число 4 нечетным или нет.

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

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

person chepner    schedule 06.04.2016
comment
«Каждая категория имеет два свойства:» 3. идентичность и состав подчиняются законам id ∘ f ≡ f ∘ id ≡ f и (f ∘ g) ∘ h ≡ f ∘ (g ∘ h). - person leftaroundabout; 06.04.2016
comment
Однако это не все эндофункторы на Hask. Семейство типов, сопоставленное с соответствующей функцией, может быть функтором, но не Functor. - person dfeuer; 06.04.2016
comment
Я еще не изучил семейства шрифтов, поэтому я не мог объяснить их в категорических терминах. - person chepner; 06.04.2016
comment
Что ж, семейство типов интересно в этом отношении, потому что оно потенциально может отображать два разных типа в один и потому что оно не помечает свои результаты конструктором типа. Таким образом, семейство типов+функция кажется более точным представлением в Haskell идеи эндофунктора, но оно не вписывается в систему классов типов почти так же точно, как Functor. - person dfeuer; 06.04.2016

Во-первых, это неправда, что в Haskell «все является функцией». Многие вещи не являются функциями, например 4. Или строка "vik santata".

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

Вот несколько примеров функций (цитата из сеанса GHCI):

λ: :t fst
fst :: (a, b) -> a
λ: :t even
even :: Integral a => a -> Bool

Вот несколько примеров вещей, которые не являются функциями:

  • (полиморфное) значение, которое может принимать любой тип a при условии, что этот тип является членом класса Num (например, Int будет допустимым типом). Точное значение будет выведено из того, как используется число.

    Обратите внимание, что в этом типе есть =>, что в целом отличается от ->. Он обозначает «ограничение класса».

    λ: :t 5
    5 :: Num a => a
    
  • Список функций. Обратите внимание, что у него есть -> в своем типе, но это не конструктор типа верхнего уровня (тип верхнего уровня — [], т. е. «список»):

    λ: :t [fst, snd]
    [fst, snd] :: [(a, a) -> a]
    

Функторы нельзя применять к значениям. Функторы — это типы, значения которых могут использоваться (и возвращаться) функцией fmap (при условии, что функция fmap соответствует определенным правилам, часто называемым «законами»). Вы можете найти основной список типов, которые являются частью Functor с помощью GHCI:

λ: :i Functor
[...]
instance Functor (Either a) -- Defined in ‘Data.Either’
instance Functor [] -- Defined in ‘GHC.Base’
instance Functor Maybe -- Defined in ‘GHC.Base’
[...]

Это означает, что вы можете применить fmap к спискам, или к Maybe значениям, или к Either значениям.

person Frerich Raabe    schedule 06.04.2016

Фактически, функтор – это две функции. , но только одна из них является функцией Haskell (и я не уверен, что это та функция, о которой вы подозреваете).

  1. Функция уровня типа. объекты категории Hask — это типы с вида *, и функтор отображает такие типы в другие типы. Вы можете увидеть этот аспект функторов в ghci, используя запрос :kind:

    Prelude> :k Maybe
    Maybe :: * -> *
    Prelude> :k []
    [] :: * -> *
    Prelude> :k IO
    IO :: * -> *
    

    То, что делают эти функции, довольно скучно: например, они отображают

    • Int to Maybe Int
    • () to IO ()
    • String to [[Char]].

    Под этим я не подразумеваю, что они отображают целые числа в возможно-целые числа и т. д., это более конкретная операция, возможная не для каждого функтора. Я просто имею в виду, что они сопоставляют тип Int как единое целое с типом Maybe Int.

  2. Функция уровня значения, которая отображает морфизмы (т. е. функции Haskell) в морфизмы. Целевой морфизм всегда сопоставляется между типами, полученными в результате применения функции уровня типа к домену и кодовому домену исходной функции.
    Эта функция — это то, что вы получаете с fmap:

    • fmap :: (Int -> Double) -> (Maybe Int -> Maybe Double)
    • fmap :: (() -> Bool) -> (IO () -> IO Bool)
    • fmap :: (Char -> String) -> String -> [String].
person leftaroundabout    schedule 06.04.2016

Чтобы что-то было функтором, вам нужны две вещи:

  • тип контейнера*
  • специальная функция, которая преобразует функцию из контейнеров в функцию, преобразующую контейнеры

первый зависит от вашего собственного определения, но второй был закодирован в «интерфейсе» под названием Functor, а функция преобразования была названа fmap.

таким образом, вы всегда начинаете с чего-то вроде

data Maybe a = Just a | Nothing

instance Functor Maybe where
    -- fmap :: (a -> b) -> Maybe a -> Maybe b
    fmap f (Just a) = Just (f a)
    fmap _ Nothing = Nothing

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

Более того, соглашение требует, чтобы Functor придерживался определенных законов, но это не может быть обеспечено компилятором/проверкой типов.

*: этот контейнер также может быть фантомным, например. data Proxy a = Proxy в этом случае имя контейнер спорно, но я бы все равно использовал это имя

person epsilonhalbe    schedule 06.04.2016
comment
instance Functor ((->)a) where fmap = (.) не имеет типа контейнера. - person Sassa NF; 06.07.2018

  1. Не все в Haskell является функцией. К нефункциям относятся "Hello World", (3 :: Int, 'a') и Just 'x'. Вещи, типы которых включают =>, также не обязательно являются функциями, хотя GHC (обычно) переводит их в функции в своем промежуточном представлении.

Что такое функтор? Для заданных категорий C и D функтор f из C в D состоит из отображения fo из объектов C в объекты D и отображения fm из морфизмов C в морфизмы D, таких что

  1. Если x и y — объекты в C и p — морфизм из x в y, то fm(p) — морфизм из fo(x) в fo(y).
  2. Если x — объект в C, а id — тождественный морфизм из x в x, то fm(id) — тождественный морфизм из fo(x) в fo(x).
  3. Если x, y и z — объекты в C, p — морфизм из y в z и q — морфизм из x в y, то fm(p . q) = fm(p).fm(q), где точка представляет композицию морфизмов.

Как это связано с Haskell? Нам нравится думать, что типы Haskell и функции Haskell между ними образуют категорию. Это верно лишь приблизительно по разным причинам, но это полезное руководство к интуиции. Затем класс Functor представляет инъективные эндофункторы из этой категории Hask самому себе. В частности, Functor состоит из отображения (в частности, конструктора типа или частичного применения конструктора типа) из типов в типы, а также отображения (функция fmap) из функций в функции, которые подчиняются вышеуказанным законам.

person dfeuer    schedule 06.04.2016