Подтвердить что ты не робот

Функция F # вызывающая синтаксическая путаница

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

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 аргумента.

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

4b9b3361

Ответ 1

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

let add a b = a + b

Как отметил Паскаль, этот способ указания параметров называется currying - идея состоит в том, что функция принимает только один параметр, а результат - это функция, которая принимает второй параметр и возвращает фактический результат (или другую функцию). При вызове простой функции, подобной этой, вы должны написать 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

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

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

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

Ответ 2

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

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

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

let f_a = f a (* computations happen now that a is available *)
in
f_a b1 .... f_a b2 ....

Ответ 3

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

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