Я знаю, что оператор точки (.) принимает две функции, которые принимают аргумент соответственно, и третий аргумент для второго аргумента.
Его тип (.) :: (b -> c) -> (a -> b) -> a -> c.
Например, take 1 . drop 2 $ [1,2,3,4,5].
Но как (fmap . const) может работать?
Функция fmap требует два аргумента, а функция const принимает два аргумента и затем возвращает первый аргумент.
Логично, что fmap должна принять выходные данные, которые являются первым аргументом, принимаемым функцией const.
Типы двух функций:
fmap :: Functor f => (a -> b) -> f a -> f b
const :: a -> b -> a
fmap в (fmap . const) 2 (Just 4) должен получить выходные данные 2, возвращаемые функцией const, поэтому это не сработает, но на самом деле это работает.
Что случилось?





Просто унифицируйте типы:
fmap :: Functor f => (a -> b) -> (f a -> f b)
const :: b -> (a -> b)
fmap . const :: Functor f => b -> (f a -> f b)
Итак, fmap . const берет x :: b и производит fmap (const x) :: f a -> f b.
В вашем примере
(fmap . const) 2 (Just 4)
= ((fmap . const) 2) (Just 4)
= fmap (const 2) (Just 4)
= Just (const 2 4)
= Just 4
Это непонятно, как это на самом деле работает, пожалуйста, взгляните на комментарии под ответом @willeM_Van Onsem. Итак, (a -> b) также может быть (a -> (x -> z)), или в случае fmap в операторе точки это также может быть Functor f => ((a -> b) -> (f a -> f b))? Как алгебраический тип может быть типом значения или типом функции?
Да, переменные типа, такие как b, обозначают любой (мономорфный) тип, а не только алгебраические типы данных. Вот почему они настолько эффективны: вы можете скомпоновать любые две функции, если их домен и кодомен совпадают.
Функции также являются значениями; можно сказать, что в этом весь смысл функционального программирования.
Кроме того, чтобы более полно понять всю картину, основываясь на полиморфной природе переменных типа и мономорфном выводе типа выражения (также применение функции, применение композиции функции), композиция fmap . const, ее тип такой: (.) :: Functor f => ((f b -> a) -> (f b -> f a)) -> (((a -> (f b -> a)) -> ((((a -> f b) -> f a))))),
где приложение (fmap . const) 2 (Just 4) выглядит так, что оператор точки принимает четыре аргумента, но на самом деле это не так, его результатом является функция, которая заботится о четвертом аргументе... Таким образом, выражение для приложения типа fmap . const $ 2 (Just 4) недопустимо, но должно быть (fmap . const) 2 (Just 4), или, кроме того, без упрощения скобок, которое разрешено компилятором, ((fmap . const) 2) (Just 4). Я думаю, это слишком много, это трудная часть, чтобы быть функциональным. @Наим Фавье, я правильно это понимаю?..
Не совсем: экземпляр (.), который мы здесь используем, имеет тип ((a -> b) -> (f a -> f b)) -> (b -> (a -> b)) -> (b -> (f a -> f b)).
Его отредактировали неправильно, даже для моего неправильного понимания, это должно было быть (.) :: Functor f => ((f b -> a) -> (f b -> f a)) -> ((a -> (f b -> a)) -> (f b -> f a), а не (.) :: Functor f => ((f b -> a) -> (f b -> f a)) -> (((a -> (f b -> a)) -> ((((a -> f b) -> f a))))). Я также попробовал вашу подпись типа (.) :: Functor f => ((a -> b) -> (f a -> f b)) -> (b -> (a -> b)) -> (b -> (f a -> f b)) (.) f g = \x -> f (g x) в Haskell, она действительно работает.
Функция
fmapтребует два аргумента, а функцияconstпринимает два аргумента и затем возвращает первый аргумент.
Нет, каждая функция принимает один параметр и иногда в результате выдает функцию. fmap :: Functor f => (a -> b) -> (f a -> f b), принимает одну функцию типа a -> b, а затем создает функцию, которая сопоставляет f a с f b.
Теперь тот же трюк необходим, когда мы работаем с const :: c -> d -> c, это сокращение от const :: c -> (d -> c), то есть снова функция, которая принимает значение, а затем создает функцию, которая сопоставляет любое значение с данным значением.
Итак, что же такое fmap . const? Это сокращение от \x -> fmap (const x). Таким образом, он запрашивает значение x, а затем выдает fmap (const x). Таким образом, это означает, что fmap сопоставит любое значение с x. Таким образом, это означает, что если мы работаем, например, с x = 42 и списком [1,4,2,5], мы получаем fmap (const 42) [1,4,2,5], а это значит, что мы сопоставляем любой элемент списка с 42, поэтому [42, 42, 42, 42].
«Так что же такое fmap . const? Это сокращение от \x -> fmap (const x)», на мой взгляд, это не является строго логическим выводом из сигнатуры типа (b -> c) -> (a -> b) -> a -> c. Мне все еще нужно изучить реализацию оператора точки, как объяснялось в других ответах.
Вот почему это сбивает с толку, я не могу понять, глядя непосредственно на сигнатуру типа оператора точки. Мне сказали, что сигнатура типа расскажет, как работает функция/оператор, но очевидно, что в некоторых подобных ситуациях это не так, даже с точки зрения каррирования. Это проблема. Меня смутила его подпись типа. Теперь, если посмотреть на то, как реализован оператор точки, становится очень ясно:
Или это может означать, что (a -> b) также, вероятно, будет обозначать, что он принимает аргумент и, вероятно, возвращает другую функцию, например (a -> (x -> z))., в fmap, принимаемом точкой в качестве первого аргумента, она будет (a -> b) равна Functor f => ((a -> b) -> (f a -> f b)), тогда какой смысл в ищем сигнатуры типов, если бы алгебраический тип мог означать не только тип значения, но и тип функции..
Да, b может быть любого типа, это никак не ограничено. Сюда входят и типы функций, потому что почему типы функций здесь могут быть разными?
@Akari, технически, этот тип расскажет вам все, что вам нужно знать, поскольку существует только одна (всего) функция с этой сигнатурой типа согласно параметрическим/свободным теоремам. Полагаю, это вопрос опыта.
@Akari Главное, к чему вам нужно привыкнуть, чтобы чувствовать себя комфортно в Haskell, — это перестать думать о функциях отдельно от значений. «Функция» описывает подкатегорию значений, а не что-то совсем другое. Функции — это значения. Точно так же, как «список» описывает подкатегорию значений, а не что-то еще. Поэтому, когда вы видите что-то, что применимо к любому типу значения (например, к переменной типа, такой как b), это включает в себя возможность (но не необходимость) применения этого к функциям. Так что на самом деле тип сказал вам, что это может быть функция; вот что означают переменные типа!
Функциональная композиция . определяется как:
f . g = \x -> f (g x)
Заменив . его определением в fmap . const, вы получите эквивалентное выражение:
\x -> fmap (const x)
Аналогично, если вы замените fmap . const в выражении (fmap . const) 2 его эквивалентом \x -> fmap (const x), вы получите:
(\x -> fmap (const x)) 2
Наконец, применив аргумент 2 выше:
fmap (const 2)
«
fmapв(fmap . const) 2должен получить выходные данные2, возвращенные функциейconst» — нет,fmapв(fmap . const) 2получает выходные данные\_ -> 2, возвращенные функциейconst.const x _ = xпо сути то же самое уравнение, что иconst x = \_ -> x.