Синтаксис вызова функции F # - PullRequest
21 голосов
/ 28 апреля 2010

У меня есть код:

links
    |> Seq.map (fun x -> x.GetAttributeValue ("href", "no url"))

Который я хотел переписать:

links
    |> Seq.map (fun x -> (x.GetAttributeValue "href" "no url"))

Но компилятору F # это не нравится. У меня сложилось впечатление, что эти два вызова функций взаимозаменяемы:

f (a, b)
(f a b)

Я получаю ошибку:

Член или конструктор объекта 'GetAttributeValue', принимающий 2 аргумента, не доступны из этого расположения кода. Все доступные версии метода GetAttributeValue принимают 2 аргумента.

Что кажется забавным, поскольку, похоже, указывает на то, что ему нужно то, что я ему даю. Что мне здесь не хватает?

Ответы [ 3 ]

58 голосов
/ 28 апреля 2010

Обычный вызов функции в F # пишется без скобок, а параметры разделяются пробелами. Простой способ определить функцию нескольких параметров - записать это:

let add a b = a + b

Как отметил Паскаль, этот способ задания параметров называется каррированием - идея состоит в том, что функция принимает только один параметр, а в результате получается функция, которая принимает второй параметр и возвращает фактический результат (или другую функцию). При вызове простой функции, подобной этой, вы должны написать add 10 5, а компилятор (в принципе) интерпретирует это как ((add 10) 5). Это имеет некоторые приятные преимущества - например, оно позволяет вам использовать приложение с частичной функцией , где вы указываете только первые несколько аргументов функции:

let addTen = add 10 // declares function that adds 10 to any argument
addTen 5  // returns 15
addTen 9  // returns 19

Эта функция практически полезна, например, при обработке списков:

// The code using explicit lambda functions..
[ 1 .. 10 ] |> List.map (fun x -> add 10 x) 

// Can be rewritten using partial function application:
[ 1 .. 10 ] |> List.map (add 10) 

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

let tup = (10, "ten")    // creating a tuple
let (n, s) = tup         // extracting elements of a tuple using pattern 
printfn "n=%d s=%s" n s  // prints "n=10 s=ten"

Когда вы пишете функцию, которая принимает параметры в скобках, разделенных запятой, вы фактически пишете функцию, которая принимает один параметр, который является кортежем:

// The following function:
let add (a, b) = a * b

// ...means exactly the same thing as:
let add tup = 
  let (a, b) = tup  // extract elements of a tuple
  a * b

// You can call the function by creating tuple inline:
add (10, 5)
// .. or by creating tuple in advance
let t = (10, 5)
add t

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

В F # ситуация немного сложнее - методы .NET отображаются в виде методов, которые принимают кортеж в качестве параметра (так что вы можете вызывать их с записью в скобках), но они несколько ограничены (например, вы не можете сначала создайте кортеж, а затем вызовите метод, предоставив ему только кортеж). Кроме того, скомпилированный код F # на самом деле не создает методы в форме с карри (поэтому вы не можете использовать приложение с частичной функцией непосредственно из C #). Это связано с производительностью - в большинстве случаев вы указываете все аргументы, и это может быть реализовано более эффективно.

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

3 голосов
/ 28 апреля 2010

В f (a,b), f должна быть функция, которая принимает один аргумент (который является парой).

В f a b, что является сокращением для (f a) b, f, когдаapply to a возвращает функцию, которая применяется к b.

Оба являются почти эквивалентными способами передачи аргументов функции, но вы не можете использовать функцию, предназначенную для одного стиля, с другим.Второй стиль называется " curry ".Он имеет преимущество, заключающееся в том, что некоторые вычисления могут выполняться сразу после передачи a, особенно если вы собираетесь использовать один и тот же a с разными b с.В этом случае вы можете написать:

let f_a = f a (* computations happen now that a is available *)
in
f_a b1 .... f_a b2 ....
2 голосов
/ 28 апреля 2010

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

let getAttrVal (x:TypeOfX) key default = x.GetAttributeValue(key, default)

//usage
links |> Seq.map (fun x -> getAttrVal x "href", "no url"))

и в зависимости от того, как вы хотите его использовать, может быть более полезно замерить его «назад»:

let getAttrVal key default (x:TypeOfX) = x.GetAttributeValue(key, default)

//partial application
let getHRef = getAttrVal "href" "no url"

//usage
links |> Seq.map (fun x -> getHRef x)

//or, same thing:
links |> Seq.map getHRef
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...