Композиция функций VS Функция Применение - PullRequest
6 голосов
/ 25 июня 2010
  1. Кто-нибудь может привести пример композиции функции?
  2. Это определение оператора композиции функции?

    (.) :: (b -> c) -> (a -> b) -> a -> c
    f . g = \x -> f (g x)
    

Это показываетчто он принимает две функции и возвращает функцию, но я помню, что кто-то выразил логику на английском языке как

мальчик - человек -> али - мальчик -> али - человек

  1. Что этологика, связанная с композицией функций?
  2. Что означает сильная привязка применения функции и композиции и какая из них более сильна, чем другая?

Пожалуйста, помогите.

Спасибо.

1 Ответ

20 голосов
/ 25 июня 2010

( Редактировать 1: Я пропустил пару компонентов вашего вопроса в первый раз; см. Нижнюю часть моего ответа.)

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

  1. Если будет солнечно, мне станет жарко.
  2. Если мне станет жарко, я пойду купаться.
  3. Поэтому, , если будет солнечно, я пойду поплавать.

Это называется гипотетическим силлогизмом . В логических терминах мы бы написали это следующим образом: пусть S означает предложение "солнечно", пусть H означает предложение "Я буду жарко", и пусть W означает предложение "Я пойду купаться". Запись α β для " α подразумевает β " и запись ∴ для "следовательно", мы можем перевести вышеприведенное в:

  1. S H
  2. В Ш
  3. S W

Конечно, это работает, если мы заменим S , H и W на любые произвольные α , β и γ . Теперь это должно выглядеть знакомо. Если мы поменяем стрелку импликации → на стрелку функции ->, это станет

  1. a -> b
  2. b -> c
  3. a -> c

И вот, у нас есть три компонента типа оператора композиции! Чтобы думать об этом как о логическом силлогизме, вы можете рассмотреть следующее:

  1. Если у меня есть значение типа a, я могу получить значение типа b.
  2. Если у меня есть значение типа b, я могу получить значение типа c.
  3. Следовательно, , если у меня есть значение типа a, я могу получить значение типа c.

Это должно иметь смысл: в f . g существование функции g :: a -> b говорит вам, что предпосылка 1 верна, а f :: b -> c говорит вам, что предпосылка 2 верна. Таким образом, вы можете заключить последнее утверждение, для которого функция f . g :: a -> c является свидетелем.

Я не совсем уверен, что переводит твой силлогизм. Это почти экземпляр modus ponens , но не совсем. Аргументы Modus Ponens имеют следующую форму:

  1. Если идет дождь, тогда я промокну.
  2. Идет дождь.
  3. Следовательно, Я промокну.

Запись R для "идет дождь" и W для "Я промокну", это дает нам логическую форму

  1. R W
  2. Ш

Замена стрелки импликации на стрелку функции дает нам следующее:

  1. a -> b
  2. a
  3. b

И это просто функциональное приложение, как мы видим из типа ($) :: (a -> b) -> a -> b. Если вы хотите рассматривать это как логический аргумент, он может иметь форму

  1. Если у меня есть значение типа a, я могу получить значение типа b.
  2. У меня есть значение типа a.
  3. Следовательно, Я могу получить значение типа b.

Здесь рассмотрим выражение f x. Функция f :: a -> b является свидетельством истинности суждения 1; значение x :: a является свидетельством истинности предложения 2; и, следовательно, результат может быть типа b, что доказывает заключение. Это именно то, что мы нашли из доказательства.

Теперь ваш оригинальный силлогизм принимает следующую форму:

  1. Все мальчики - люди.
  2. Али - мальчик.
  3. Следовательно, Али - это человек.

Давайте переведемэто к символам. Bx будет обозначать, что x мальчик; Hx будет означать, что x является человеком; a будет обозначать Али; и 12 x . φ говорит, что φ верно для всех значений x . Тогда у нас есть

  1. х . Bx Hx
  2. Ba
  3. Ха

Это почти modus ponens, но оно требует создания экземпляра. Хотя логически верно, я не уверен, как интерпретировать это на уровне системы типов; если кто-то хочет помочь, я весь в ушах. Одно предположение было бы типом ранга 2 как (forall x. B x -> H x) -> B a -> H a, но я почти уверен, что это неправильно. Другим предположением может быть более простой тип, например (B x -> H x) -> B Int -> H Int, где Int обозначает Али, но, опять же, я почти уверен, что это неправильно. Опять же: если вы знаете, пожалуйста, дайте мне знать тоже!

И последнее замечание. Взгляд на вещи таким образом - следуя связи между доказательствами и программами - в конечном итоге приводит к глубокой магии изоморфизма Карри-Ховарда , но это более сложная тема. (Хотя это действительно круто!)


Редактировать 1: Вы также запросили пример композиции функции. Вот один пример. Предположим, у меня есть список отчеств людей. Мне нужно составить список всех средних инициалов, но для этого мне сначала нужно исключить все несуществующие вторые имена. Легко исключить всех, чье второе имя пусто; мы просто включаем всех, чье отчество не null с filter (\mn -> not $ null mn) middleNames. Точно так же мы можем легко получить чью-то среднюю букву с head, и поэтому нам просто нужно map head filteredMiddleNames, чтобы получить список. Другими словами, у нас есть следующий код:

allMiddleInitials :: [Char]
allMiddleInitials = map head $ filter (\mn -> not $ null mn) middleNames

Но это раздражающе специфично; мы действительно хотим функцию генерации среднего начального значения. Итак, давайте изменим это на один:

getMiddleInitials :: [String] -> [Char]
getMiddleInitials middleNames = map head $ filter (\mn -> not $ null mn) middleNames

Теперь давайте посмотрим на что-нибудь интересное. Функция map имеет тип (a -> b) -> [a] -> [b], а поскольку head имеет тип [a] -> a, map head имеет тип [[a]] -> [a]. Аналогично, filter имеет тип (a -> Bool) -> [a] -> [a], и поэтому filter (\mn -> not $ null mn) имеет тип [a] -> [a]. Таким образом, мы можем избавиться от параметра и вместо этого написать

-- The type is also more general
getFirstElements :: [[a]] -> [a]
getFirstElements = map head . filter (not . null)

И вы видите, что у нас есть бонусный экземпляр композиции: not имеет тип Bool -> Bool, а null имеет тип [a] -> Bool, поэтому not . null имеет тип [a] -> Bool: он сначала проверяет, является ли данный список пуст, и затем возвращает, является ли это не . Это преобразование, кстати, изменило функцию в стиль без точек ; то есть результирующая функция не имеет явных переменных.

Вы также спрашивали о "сильной привязке". Я думаю, что вы имеете в виду приоритет операторов . и $ (и, возможно, приложения-функции). В Haskell, как и в арифметике, некоторые операторы имеют более высокий приоритет, чем другие, и, следовательно, связываются более тесно. Например, в выражении 1 + 2 * 3 это анализируется как 1 + (2 * 3). Это связано с тем, что в Haskell действуют следующие декларации:

infixl 6 +
infixl 7 *

Чем выше число (уровень приоритета), тем быстрее вызывается этот оператор, и, следовательно, чем теснее оператор связывается. Применение функций фактически имеет бесконечный приоритет, поэтому оно связывается наиболее плотно: выражение f x % g y будет анализироваться как (f x) % (g y) для любого оператора %. Операторы . (состав) и $ (приложение) имеют следующие объявления исправления:

infixr 9 .
infixr 0 $

Уровни приоритета варьируются от нуля до девяти, поэтому здесь говорится, что оператор . связывает больше плотно, чем любой другой (кроме приложения функции), а $ связывает меньше плотно. Таким образом, выражение f . g $ h будет анализироваться как (f . g) $ h; и фактически для большинства операторов f . g % h будет (f . g) % h, а f % g $ h будет f % (g $ h). (Единственными исключениями являются несколько редких других операторов с нулевым или девятью приоритетами.)

...