2011-01-22 3 views
12

Я новичок в Haskell и функциональном программировании. Я читаю через Real World Haskell, и я понял, что меня смущает несколько примеров.Условное обозначение типа-функции в Haskell

В частности, это в главе 9, в разделе «Язык, специфичный для домена для предикатов», примеры, которые имеют параметры w x y z.

Я варил вниз к этому:

Почему скомпилировать этот код?

f :: Int -> (Int -> Int) 
f x y = x+y 

main = do 
    let q = f 4 5 
    putStr (show (q)) 

По типу подписи, f явно принимая 1 параметр и возвращает функцию. Однако, похоже, я могу написать уравнение функции, поэтому он примет два параметра и вернет int. Почему это возможно? Означает ли это, что подпись типа игнорируется?

Это каррирование? Это что-то вроде закрытия? Если я правильно понимаю это http://www.haskell.org/haskellwiki/Currying, то он, кажется, несколько обратный к currying, как определено там - моя функция f принимает несколько аргументов вместо одного!

Кроме того, может ли кто-либо ответить, пожалуйста, указать ссылку на какую-либо документацию Haskell, где указано это свойство (если возможно вообще).

EDIT:

Подумав об этом в течение некоторого времени, что вы два, кажется, подразумевает, что:

1) Этот синтаксис синтаксический сахар, е всегда будет иметь один параметр, независимо от того, сколько параметров записано в уравнении

2) При применении функции f тело функции (всегда?) преобразуется в заглушку (фактически, возвращенную функцию), где x фиксируется с заданным параметром (4), а y - параметр.

3) Затем эта новая функция применяется к 5, которая заменяет y, а затем вычисляется функция +.

Меня действительно интересовало, где именно он говорит что-то вроде «в функциональном уравнении, если вы пишете несколько параметров, это действительно синтаксический сахар, и на самом деле это происходит ...», как я писал выше. Или это так очевидно для всех, кроме меня?

Edit II:

Реальное разоблачение ответа был в @luqui комментария ниже, к сожалению, я не думаю, что можно пометить комментарий как ответ.

Это тот факт, что FXY = ... фактически синтаксический сахар для: е = \ х -> \ у -> ...

И для меня, все остальное все ниже сказанного следует из этого.

Я нашел своего рода источник этого в «Нежном представлении» для Haskell, здесь: http://haskell.cs.yale.edu/tutorial/functions.html в разделе 3.1, называемом абстрактами лямбда.

В самом деле, уравнения:

вкл х = х + 1 добавить ху = х + у

действительно стенография для:

вкл = \ х -> х + 1 добавить = \ х -> х + у

Хотя он не использует фразу «синтаксический сахар», она использует более, эм, mathemati но в качестве программиста я прочитал это как «сахар» :-)

+2

Разумеется, это не очевидно, но это то, что нам нужно было привыкнуть довольно быстро, чтобы вытащить Haskell. Возможно, именно поэтому авторы забывают упомянуть об этом явно. Довольно уверен, что вы знаете, что Хаскелл описывает это. – luqui

+3

Ваша точка № 2 немного не работает. После определения вашей функции он принимает аргументы, которые он принимает. Когда вы называете это 'f 4 5', это анализирует как' (f 4) 5', и поэтому 'f' вызывается с' 4' в качестве аргумента и эффективно возвращает функцию '\ y -> 4 + y'. Тогда у вас есть '(\ y -> 4 + y) 5', который становится' 4 + 5', а затем '9'. Хитрость заключается в том, что тип функции и определение функции являются право-ассоциативными, а функциональное приложение остается ассоциативным, так что они хорошо выстраиваются. –

+0

@Antal s-z Спасибо, что исправил меня и указал на симметрию между типом функции и функцией. Вы могли бы сказать, что «ваша функция фактически не принимает никаких аргументов, это все лямбда, которые затем налиты». –

ответ

10

Это currying. Как говорит подпись типа, f использует только один параметр. Это будет 4. Затем он возвращает функцию, которая немедленно применяется к 5. На самом деле, эти подписи два типа:

Int -> Int -> Int

и

Int -> (Int -> Int)

эквивалентны в Haskell.

EDIT: Эта ссылка о Partial Application, которую я нашел на указанной странице, объясняет это.

РЕДАКТИРОВАТЬ 2: Вы задали вопрос о том, где определяется поведение каркаса Haskell. Я не знаю, если это то, что вы ищете: в Haskell 98 Revised Report, в разделе 3.3 Curried Applications and Lambda Abstractions, говорит:

Функция приложения написано e1 e2. Приложение связывает влево, поэтому круглые скобки могут быть опущены в (f x) y.

+0

Я до сих пор не совсем понимаю. Я знаю, что Int -> (Int -> Int) и Int -> Int -> Int эквивалентен, но, честно говоря, это тоже очень сбивает с толку, я не совсем понимаю это. Что это означает * на практике *? Также обратите внимание, что я спросил об этом типе, а не типе. Ссылка, которую вы указали, имеет форму частичного приложения, с которым я знаком, используя, казалось бы, «нормальную» функцию с двумя параметрами и фиксируя первую в * другой * функции. Однако в моем случае это единственная функция. Подумав об этом, у меня появилась идея, я напишу ее ниже. –

+0

По типу уравнения, я думаю, вы имеете в виду функциональное уравнение. Я добавил ссылку на пересмотренный отчет Haskell 98, надеюсь, это поможет прояснить ситуацию. Функциональное приложение лево-ассоциативное, поэтому f x y совпадает с (f x) y, а f x y z совпадает с ((f x) y) z и т. Д. Отчет Haskell называет это «карриное приложение». – carnieri

4

Оператор -> является правоассоциативным, т.е. t -> t -> t таким же, как t -> (t -> t).

Если вы переписываете

f x y = x+y 

в эквивалентной форме

f x = \y -> x + y 

это conncetion должен стать очевидным.

+0

Но * почему * это эквивалентная форма? –

+2

@Or Когда это просто обозначение. 'f x y z = blah' является сокращением для' f = \ x -> \ y -> \ z -> blah'. – luqui

+0

@luqui - Большое спасибо, это был ответ, который я искал. –

2

Это определенно немного карри. Хотя я не могу сразу найти, где в документации явно указывается это поведение, все, что нам нужно сделать, это немного проверить нашу математику о карри.

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

f :: Int -> (Int -> Int) 
f x y = x+y 

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

Проще говоря, каррирование - right-associative. Другими словами, Int -> (Int -> Int) такое же, как Int->Int->Int, только мы добавили скобки, чтобы сделать его более очевидным, что:

f 3 

Не хватает аргументов, но на самом деле возвращает функцию типа Int->Int.

Это похоже на математику, когда вы изучаете дополнение associative property.

3 + 2 + 1 = (3 + 2) + 1 = 3 + (2 + 1) 

Результат тот же, независимо от того, как мы размещаем наши круглые скобки.

+0

Что меня смутило не в функции * application *, с которой я в порядке, это была функция * equation *. Мне просто нужно было увидеть деталь реализации, fxy = .... переводится как f = \ x -> \ y -> .... (который * является синтаксическим сахаром IMO) –

+0

@Or Когда: Ах, хорошо, Я неправильно понял: -p –

2

На самом деле это не синтаксический сахар, а просто как приложение функции работает в Haskell.

Рассмотрим:

f :: Int -> Int -> Int -> Int 
f x y z = x + y + z 

g = f 4 
h = g 4 5 

f 4 4 5 -- 13 
g 4 5 -- 13 
g 6  -- 13 

Вы можете играть с этим в GHCI для подтверждения. g - частичное применение функции f - ее тип g :: Int -> Int -> Int.

Также можно написать:

((f 4) 4) 5 -- 13 

В этом случае (f 4) возвращает частично применяется функция, которая принимает два дополнительных аргумента, ((f 4) 4) возвращает частично применяется функция, которая принимает один аргумент, и все выражение сводится к 13.

+0

Часть «мы могли бы просто предоставить оба из инт ...» звучит немного как магия. Как это связано с тем, что функции Haskell принимают только один аргумент? Отображение фактической «реализации», то есть f x y = .... на самом деле f = \ x -> \ y -> .... помогло бы. –

+0

Опечатка: Должно быть: h 6 - 13, а не g 6 –

2

Подумав еще немного об этом, я думаю, что полное объяснение должно быть что-то вдоль линий:

функция

Haskell может принимать только один argum ent и возвращает один параметр. Haskell позволяет нам притворяться, что передано несколько аргументов, но эта форма рассматривается как серия вложенных лямбда-функций.

f x y = x + y 

рассматривается как

(1) f = \x -> \y -> x + y 

Эта процедура относится к лямбда-функциям, а \ х -> х + у рассматривается как \ х -> \ у -> х + у

Это позволяет рассматривать объявление типа, как левоассоциативной, то есть: е :: Int -> Int -> Int фактически е :: (Int -> (Int -> Int)) , который точно соответствует (1): f не имеет аргументов, но возвращает функцию, которая принимает Int. Эта функция, в свою очередь, возвращает функцию, которая принимает другой Int, и возвращает Int.

Это означает, что если мы хотим вернуть функцию из функции, нам не нужно делать ничего особенного, так как это режим «по умолчанию» Haskell.

Это также означает, что с учетом объявления типа f :: Int -> Int -> Int Мы можем написать f-реализацию («уравнение») с параметрами 0, 1 или 2. Если один или два параметра указаны, компилятор будет генерировать необходимые лямбды для соблюдения вида е :: (Int -> (Int -> Int))

f = \x -> \y -> x + y 

f x = \y -> x + y -- \x -> is generated 

f x y = x + y -- \x -> \y is generated 

Но в каждом из этих случаев, приложение функции, принимающее два параметра, будет скомпилировано успешно, так как оно всегда будет переведено в первую форму, например

f 4 5 --> (\x -> (\y -> x + y) 5) 4 

В случае, если внутреннее приложение, функция возвращает Curried вид (х + 5)

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

Кроме того, изменение приоритета в типе функции:

f'' :: (Int -> Int) -> Int 

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

f'' 4 5 

не компилируется.

Edit:

Кроме того, даже если последние аргументы в круглых скобках, или объявление типа, это верно.

Например, последняя пара скобок не является обязательной, так как если бы их там не было, компилятор мог бы поместить их в форму «lambda'd».

t4 :: (Int -> Int) -> (Int -> Int) 
t4 f i = f i + i 

Может применяться таким образом:

t4 (\x -> x*10) 5 

Кроме того, учитывая:

type MyIntInt = Int -> Int 

Тогда:

t5 :: MyIntInt -> MyIntInt 

эквивалентно t4, но сбивает с толку, так как значение MyIntInt отличается в обоих местах. Первый - это тип первого параметра.
Второй «расширен» в Int -> Int (возможно, из-за правильной ассоциативности оператора), что означает, что t5 может принимать от 0 до 2 параметров в функциональном уравнении и приложении функции (хотя на самом деле всегда принимая 0 и возвращая встроенные lambdas, как я подробно описал выше).

E.g. мы можем написать t5 точно так же, как t4:

t5 f i = f i + i 
Смежные вопросы