2013-03-21 5 views
3

У меня возникли проблемы с пониманием карьеристских и невостребованных функций. Все сайты, на которых я пытаюсь дать мне определение, были неясны для меня.Неуправляемые функции

В одном примере я нашел их о том, что

max 4 5 такая же, как (max 4) 5

Но я не понимаю, что они делают. Как у вас есть функция (max 4), когда max требует 2 параметра? Я полностью потерян.

+1

См. Также [В чем преимущество каррирования?] (Http://programmers.stackexchange.com/q/185585/61231). –

+0

Я думаю, что этот вопрос и мой ответ там могут помочь: http://stackoverflow.com/questions/8148253/how-are-functions-curried/8148957 – Ben

ответ

13

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

Haskell функции:

foo :: Int -> Int -> Int 
foo a b = a + b 

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

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

foo :: Int -> (Int -> Int) --In math speak: right associative 

и означают одно и то же самое.

Это на самом деле очень полезно, потому что теперь мы можем написать краткий код как:

foo1 :: Int -> Int 
foo1 = foo 1 

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

Если вы действительно действительно нуждаетесь в неосторожных функциях: используйте кортежи.

uncFoo :: (Int, Int) -> Int 
uncFoo (a, b) = a + b 

Редактировать

ИТАК, чтобы понять, что происходит с частичным применением рассмотреть bar

bar a b c = [a, b, c] 

Дело в том, что компилятор будет desugar то, что вы только что ввели в лямбды, как это

bar = \a -> 
     \b -> 
     \c -> 
      [a, b, c] 

Это использует преимущества замыканий (каждая внутренняя функция может «запоминать» аргументы предыдущих.

так, когда мы говорим bar 1, компилятор идет и смотрит на bar и видит крайнюю лямбда, и применяет это дает

bar 1 = \b -> 
     \c -> 
      [1, b, c] 

Если мы говорим bar 1 2

bar 1 2 = \c -> 
       [1, 2, c] 

Если то, что я имею в виду, когда Я говорю «применить», это туманно, тогда это может помочь узнать, что я действительно имею в виду beta reduction из лямбда-исчисления.

+0

Я понимаю, что он принимает один аргумент и возвращает другую функцию, которая принимает другой аргумент, но как он что-то вычисляет? Итак, в вашем случае, если бы у меня была функция 'foo a', как она должна делать что-либо только с одним аргументом? Я думаю, мне нужно более подробное объяснение того, как он работает. – dtgee

+0

Посмотрите на лямбды. Это то, что происходит на самом деле. Hang on, im edit, потому что это слишком мало – jozefg

+0

@ user1831442 Там вы – jozefg

4

В зависимости от вашего фона, эта бумага может быть просвечивающей: How to Make a Fast Curry: Push/Enter vs Eval Apply. Хотя верно, что функции с несколькими аргументами можно понимать как функции, которые связывают один параметр и возвращают другую функцию: max = (\a -> (\b -> if a > b then a else b)), фактическая реализация довольно эффективна.

Если компилятор знает статически, что max требует два аргумента, компилятор всегда переводит max 4 5, нажимая два аргумента в стек (или в регистры), а затем вызывая max. Это по существу то же самое, что и компилятор C будет переводить max(4, 5).

С другой стороны, если, например, max является аргументом функции более высокого порядка, компилятор может не знать статически, сколько аргументов принимает max. Возможно, в одном случае это занимает три, поэтому max 4 5 является частичным приложением, или, возможно, в другом требуется всего одно, а max 4 генерирует новую функцию, к которой применяется 5. В статье рассматриваются два общих подхода к рассмотрению случая, когда арность неизвестна статически.

0

относиться к собственному примеру ...

Предположат, что вы хотите функцию, которая дает максимум 4 и функцию аргумент. Вы могли бы реализовать это следующим образом:

max4 :: Integer -> Integer 
max4 x = max 4 x 

Что max 4 делает только вернуть функцию max4 созданную на лету.

1

Вы, вероятно, получил ответ на свой вопрос уже, но только повторить:

Если мы имеем

add x y = x + y 

, то можно сказать следующее:

add = \ x y -> x + y 
add 3 = \ y -> 3 + y 
add 3 5 = 3 + 5 = 8 

Вы спрашиваете «как можно max 3 вычислить что-нибудь? », и ответ« это не может ». Он просто дает вам еще одну функцию . Эта функция может что-то сделать, когда вы ее вызываете, но вы не «получите ответ» как таковой до все аргументы были предоставлены. До тех пор вы просто получаете функции.

В большинстве случаев это просто полезный синтаксический ярлык. Например, вы можете написать

uppercase :: String -> String 
uppercase = map toUpper 

вместо того, чтобы сказать

uppercase xs = map toUpper xs 

Обратите внимание, что если map были свои аргументы наоборот, мы не смогли бы сделать это (вы можете только проведите последний аргумент, а не _first), поэтому может быть важно подумать о том, какой порядок вы определяете аргументами своих функций.


Я говорю «большую часть времени», потому что это больше, чем синтаксический сахар.Есть несколько мест на языке, где вы можете обрабатывать функции с различным числом аргументов полиморфно из-за currying. Каждая функция возвращает ответ или другую функцию. Если вы думаете об этом как о связанном списке (который либо содержит следующий элемент данных, либо маркер конца списка), вы можете увидеть, как это позволяет рекурсивно обрабатывать функции.

Итак, что же я подразумеваю под этим? Например, QuickCheck может тестировать функции с любым количеством аргументов (при условии, что есть возможность автоматически генерировать тестовые данные для каждого аргумента). Это возможно, потому что типы функций являются точеными. Каждая функция возвращает либо другую функцию, либо результат. Если вы думаете об этом как о связанном списке, вы можете представить QuickCheck, рекурсивно повторяя функцию, пока не останется больше аргументов.

Следующий фрагмент кода может или не может объяснить идею:

class Arbitrary a where 
    autogenerate :: RandomGenerator -> a 

instance Arbitrary Int 
instance Arbitrary Char 
... 

class Testable t where 
    test t :: RandomGenerator -> Bool 

instance Testable Bool where 
    test rnd b = b 

instance (Arbitrary a, Testable t) => Testable (a -> t) where 
    test rnd f = test $ f (autogenerate rnd) 

Если мы имеем функцию foo :: Int -> Int -> Bool, то это Testable. Зачем? Хорошо, Bool можно проверить, поэтому так Int -> Bool, и поэтому так Int -> (Int -> Bool).

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