"int → int → int" Что это значит в F #?
Интересно, что это значит в F #.
", выполняющая целое число,
, которое возвращает функцию, которая принимает целое число и возвращает целое число. "
Но я не понимаю этого хорошо.
Может ли кто-нибудь объяснить это так ясно?
[Обновление]:
> let f1 x y = x+y ;;
val f1 : int -> int -> int
Что это значит?
Ответы
Ответ 1
Типы F #
Пусть начнется с самого начала.
F # использует двоеточие (:
) для обозначения типов вещей. Скажем, вы определяете значение типа int
:
let myNumber = 5
F # Interactive поймет, что myNumber
является целым числом и скажет вам следующее:
myNumber : int
который читается как
myNumber
имеет тип int
Функциональные типы F #
Пока все хорошо. Введем что-то еще, функциональные типы. Функциональный тип - это просто тип функции. F # использует ->
для обозначения функционального типа. Эта стрелка символизирует, что то, что написано в ее левой части, преобразуется в то, что записано в ее правую часть.
Рассмотрим простую функцию, которая принимает один аргумент и преобразует его в один вывод. Примером такой функции может быть:
isEven : int -> bool
Здесь вводится имя функции (слева от :
) и ее тип. Эта строка может быть прочитана на английском языке как:
isEven
имеет функцию типа, которая преобразует a int
в bool
.
Обратите внимание, что чтобы правильно интерпретировать сказанное, вы должны сделать короткую паузу сразу после того, как часть "имеет тип", а затем прочитайте остальную часть предложения сразу, без паузы.
В функциях F # есть значения
В F # функции (почти) не являются более особенными, чем обычные типы. Это те вещи, которые вы можете переходить к функциям, возвращаться от функций, точно так же как bools, ints или string.
Итак, если у вас есть:
myNumber : int
isEven : int -> bool
Вы должны рассматривать int
и int -> bool
как два объекта одного типа: types. Здесь myNumber
- значение типа int
, а isEven
- значение типа int -> bool
(это то, что я пытаюсь символизировать, когда говорю о короткой паузе выше).
Функциональное приложение
Значения типов, которые содержат ->
, также называются функциями и имеют особые полномочия: вы можете применить функцию к значению. Так, например,
isEven myNumber
означает, что вы применяете функцию с именем isEven
к значению myNumber
. Как вы можете ожидать, проверив тип isEven
, он вернет логическое значение. Если вы правильно выполнили isEven
, он, очевидно, вернет false
.
Функция, возвращающая значение функционального типа
Пусть определим общую функцию для определения целого числа, кратного некоторому другому целому числу. Мы можем представить, что наш тип функции будет (скобки здесь, чтобы помочь вам понять, они могут быть или не присутствовать, они имеют особое значение):
isMultipleOf : int -> (int -> bool)
Как вы можете догадаться, это читается как:
isMultipleOf
имеет функцию типа (PAUSE), которая преобразует функцию int
в (PAUSE), которая преобразует a int
в bool
.
(здесь (ПАУЗА) обозначают паузы при чтении вслух).
Мы определим эту функцию позже. До этого давайте посмотрим, как мы можем его использовать:
let isEven = isMultipleOf 2
F # interactive ответит:
isEven : int -> bool
который читается как
isEven
имеет тип int -> bool
Здесь isEven
имеет тип int -> bool
, так как мы только что дали значение 2 (int
) на isMultipleOf
, которое, как мы уже видели, преобразует a int
в int -> bool
.
Мы можем рассматривать эту функцию isMultipleOf
как своего рода создателя функции.
Определение isMultipleOf
Итак, теперь определим эту мистическую функцию, создающую функцию.
let isMultipleOf n x =
(x % n) = 0
Легко, да?
Если вы наберете это в F # Interactive, он ответит:
isMultipleOf : int -> int -> bool
Где скобки?
Обратите внимание, что круглых скобок нет. Сейчас это не особенно важно для вас. Просто помните, что стрелки являются правильными ассоциативными. То есть, если у вас есть
a -> b -> c
вы должны интерпретировать его как
a -> (b -> c)
Право в правом ассоциативном означает, что вы должны интерпретировать, как если бы были скобки вокруг самого правого оператора. Итак:
a -> b -> c -> d
следует интерпретировать как
a -> (b -> (c -> d))
Использование isMultipleOf
Итак, как вы видели, мы можем использовать isMultipleOf
для создания новых функций:
let isEven = isMultipleOf 2
let isOdd = not << isEven
let isMultipleOfThree = isMultipleOf 3
let endsWithZero = isMultipleOf 10
F # Interactive ответит:
isEven : int -> bool
isOdd : int -> bool
isMultipleOfThree : int -> bool
endsWithZero : int -> bool
Но вы можете использовать его по-другому. Если вы не хотите (или не должны) создавать новую функцию, вы можете использовать ее следующим образом:
isMultipleOf 10 150
Это вернет true
, поскольку 150 будет кратным 10. Это точно так же, как создать функцию endsWithZero
, а затем применить ее к значению 150.
Фактически, приложение функции остается ассоциативным, что означает, что указанную выше строку следует интерпретировать как:
(isMultipleOf 10) 150
То есть вы помещаете скобки вокруг самого левого приложения.
Теперь, если вы все это понимаете, ваш пример (канонический CreateAdder
) должен быть тривиальным!
Некоторое время назад кто-то спросил этот вопрос, который касается точно такой же концепции, но в Javascript. В моем ответе я даю два канонических примера (CreateAdder, CreateMultiplier) inf Javascript, которые несколько более явны о возвращении функций.
Надеюсь, это поможет.
Ответ 2
Канонический пример этого, вероятно, является "создателем сумматора" - функцией, которая, учитывая число (например, 3), возвращает другую функцию, которая принимает целое число и добавляет к ней первое число.
Итак, например, в псевдокоде
x = CreateAdder(3)
x(5) // returns 8
x(10) // returns 13
CreateAdder(20)(30) // returns 50
Мне не достаточно удобно в F #, чтобы попытаться написать его, не проверяя его, но С# будет примерно таким:
public static Func<int, int> CreateAdder(int amountToAdd)
{
return x => x + amountToAdd;
}
Помогает ли это?
EDIT: Как заметил Бруно, пример, который вы указали в своем вопросе, - это именно тот пример, который я дал для кода С#, поэтому приведенный выше псевдокод станет следующим:
let x = f1 3
x 5 // Result: 8
x 10 // Result: 13
f1 20 30 // Result: 50
Ответ 3
Это функция, которая принимает целое число и возвращает функцию, которая принимает целое число и возвращает целое число.
Это функционально эквивалентно функции, которая принимает два целых числа и возвращает целое число. Этот способ обработки функций, которые принимают несколько параметров, является общим в функциональных языках и облегчает частичное применение функции к значению.
Например, предположим, что есть функция add, которая принимает два целых числа и добавляет их вместе:
let add x y = x + y
У вас есть список, и вы хотите добавить 10 к каждому элементу. Вы частично применили бы функцию add
к значению 10
. Он привяжет один из параметров к 10 и оставит другой аргумент несвязанным.
let list = [1;2;3;4]
let listPlusTen = List.map (add 10)
Этот трюк делает композиции очень легкими и делает их очень многоразовыми. Как вы можете видеть, вам не нужно писать другую функцию, которая добавляет 10 к элементам списка, чтобы передать ее на map
. Вы только что повторно использовали функцию add
.
Ответ 4
Обычно вы интерпретируете это как функцию, которая принимает два целых числа и возвращает целое число.
Вы должны прочитать о currying.
Ответ 5
функция, берущая целое число, которое возвращает функцию, которая принимает целое число и возвращает целое число
Последняя часть этого:
- функция, которая принимает целое число и возвращает целое число
должен быть довольно простым, пример С#:
public int Test(int takesAnInteger) { return 0; }
Итак, мы остались с
функция, берущая целое число, которое возвращает (такая же функция, как выше)
С#:
public int Test(int takesAnInteger) { return 0; }
public int Test2(int takesAnInteger) { return 1; }
public Func<int,int> Test(int takesAnInteger) {
if(takesAnInteger == 0) {
return Test;
} else {
return Test2;
}
}
Ответ 6
Вы можете прочитать
Типы функций F #: забава с кортежами и каррингами
Ответ 7
В F # (и многих других функциональных языках) существует понятие, называемое карриными функциями. Это то, что вы видите. По сути, каждая функция принимает один аргумент и возвращает одно значение.
Вначале это кажется немного запутанным, потому что вы можете написать let add x y = x + y
и, похоже, добавить два аргумента. Но на самом деле исходная функция add
принимает только аргумент x
. Когда вы применяете его, он возвращает функцию, которая принимает один аргумент (y
) и имеет уже заполненное значение x
. Когда вы затем применяете эту функцию, она возвращает требуемое целое число.
Это показано в сигнатуре типа. Подумайте о стрелке в сигнатуре типа, которая означает "берет вещь на моей левой стороне и возвращает вещь на моей правой стороне". В типе int -> int -> int
это означает, что он принимает аргумент типа int
- целое число - и возвращает функцию типа int -> int
- функцию, которая принимает целое число и возвращает целое число. Вы заметите, что это точно соответствует описанию того, как работают карри-функции.
Ответ 8
Пример:
let f b a = pown a b //f a b = a^b
- это функция, которая принимает int (экспонента) и возвращает функцию, которая возвращает свой аргумент этому экспоненту, например
let sqr = f 2
или
let tothepowerofthree = f 3
так
sqr 5 = 25
tothepowerofthree 3 = 27
Ответ 9
Концепция называется Функция более высокого порядка и довольно обычна для функционального программирования.
Функции сами по себе являются еще одним типом данных. Следовательно, вы можете писать функции, возвращающие другие функции. Конечно, вы все равно можете иметь функцию, которая принимает параметр int как параметр и возвращает что-то еще. Объедините их и рассмотрите следующий пример (в python):
def mult_by(a):
def _mult_by(x):
return x*a
return mult_by
mult_by_3 = mult_by(3)
print mylt_by_3(3)
9
(извините за использование python, но я не знаю f #)
Ответ 10
Здесь уже много ответов, но я хотел бы предложить еще один прием. Иногда объяснение одной и той же вещи множеством разных способов помогает вам "заглянуть" в нее.
Мне нравится думать о функциях как "вы даете мне что-то, и я дам вам что-то еще"
Итак, Func<int, string>
говорит: "Вы даете мне int, и я дам вам строку".
Мне также легче думать в терминах "позже": "Когда вы дадите мне int, я дам вам строку". Это особенно важно, когда вы видите такие вещи, как myfunc = x => y => x + y
( "Когда вы даете curries x, вы возвращаете то, что, когда вы дадите ему y, вернет x + y" ).
(Кстати, я предполагаю, что вы знакомы с С# здесь)
Итак, мы могли бы выразить ваш пример int -> int -> int
как Func<int, Func<int, int>>
.
Другой способ, которым я смотрю int -> int -> int
, - это то, что вы очищаете каждый элемент слева, предоставляя аргумент соответствующего типа. И когда у вас больше нет ->
, у вас нет "laters", и вы получите значение.
(Просто для удовольствия), вы можете преобразовать функцию, которая принимает все ее аргументы в один проход в один, который принимает их "постепенно" (официальным термином для их применения постепенно является "частичное приложение" ), это называется "currying":
static void Main()
{
//define a simple add function
Func<int, int, int> add = (a, b) => a + b;
//curry so we can apply one parameter at a time
var curried = Curry(add);
//'build' an incrementer out of our add function
var inc = curried(1); // (var inc = Curry(add)(1) works here too)
Console.WriteLine(inc(5)); // returns 6
Console.ReadKey();
}
static Func<T, Func<T, T>> Curry<T>(Func<T, T, T> f)
{
return a => b => f(a, b);
}
Ответ 11
Вот мои 2 c. По умолчанию функции F # разрешают частичное приложение или каррирование. Это означает, что вы определяете это:
let adder a b = a + b;;
Вы определяете функцию, которая принимает и целое число и возвращает функцию, которая принимает целое число и возвращает целое число или int -> int -> int
. Затем Currying позволяет частично применить функцию для создания другой функции:
let twoadder = adder 2;;
//val it: int -> int
Вышеприведенный код предсказывал a до 2, так что всякий раз, когда вы вызываете twoadder 3
, он просто добавляет два аргумента.
Синтаксис, когда параметры функции разделены пробелом, эквивалентен синтаксису лямбда:
let adder = fun a -> fun b -> a + b;;
Это более читаемый способ выяснить, что обе функции фактически связаны цепью.