"int -> int -> int" Что это значит в F #? - PullRequest
14 голосов
/ 01 февраля 2010

Интересно, что это значит в F #.
«функция, принимающая целое число,
которая возвращает функцию, которая принимает целое число и возвращает целое число».

Но я не очень хорошо понимаю.
Кто-нибудь может объяснить это так ясно?

[Update]:

> let f1 x y = x+y ;;

 val f1 : int -> int -> int

Что это значит?

Ответы [ 11 ]

57 голосов
/ 01 февраля 2010

F # типов

Давайте начнем с самого начала.

F # использует двоеточие (:) для обозначения типов вещей. Допустим, вы определили значение типа int:

let myNumber = 5

F # Interactive поймет, что myNumber является целым числом, и сообщит вам следующее:

myNumber : int

который читается как

myNumber относится к типу int

F # функциональные типы

Пока все хорошо. Давайте введем что-то еще, функциональные типы . Функциональный тип - это просто тип функции . F # использует -> для обозначения функционального типа. Эта стрелка символизирует, что то, что написано на его левой стороне, превращается в то, что написано на его правой стороне.

Давайте рассмотрим простую функцию, которая принимает один аргумент и преобразует его в один вывод. Примером такой функции будет:

isEven : int -> bool

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

isEven имеет функцию типа, которая преобразует int в bool.

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

В F # функции имеют значения

В F # функции (почти) не более особенные, чем обычные типы. Это вещи, которые вы можете передавать функциям, возвращать из функций, как bools, int или strings.

Так что если у вас есть:

myNumber : int
isEven : int -> bool

Вы должны рассматривать int и int -> bool как две сущности одного типа: типы. Здесь myNumber - это значение типа int, а isEven - это значение типа int -> bool (это то, что я пытаюсь символизировать, когда говорю о короткой паузе выше ).

Применение функции

Значения типов, которые содержат ->, также называются функциями и имеют специальные полномочия: вы можете применять функцию к значению. Так, например,

isEven myNumber

означает, что вы применяете функцию с именем isEven к значению myNumber. Как вы можете ожидать, проверив тип isEven, он вернет логическое значение. Если вы правильно внедрили isEven, он, очевидно, вернет false.

Функция, которая возвращает значение функционального типа

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

isMultipleOf : int -> (int -> bool)

Как вы можете догадаться, это читается как:

isMultipleOf имеет функцию типа (PAUSE), которая преобразует int в (PAUSE) функцию, которая преобразует int в bool.

(здесь (ПАУЗА) обозначает паузы при чтении вслух).

Мы определим эту функцию позже. Перед этим посмотрим, как мы можем его использовать:

let isEven = isMultipleOf 2

F # Interactive ответит:

isEven : int -> bool

который читается как

isEven относится к типу int -> bool

Здесь isEven имеет тип int -> bool, поскольку мы только что дали значение 2 (int) isMultipleOf, которое, как мы уже видели, преобразует 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, которые несколько более явны в отношении возврата функций.

Надеюсь, это поможет.

9 голосов
/ 01 февраля 2010

Каноническим примером этого, вероятно, является «создатель сумматора» - функция, которая при заданном числе (например, 3) возвращает другую функцию, которая принимает целое число и добавляет к нему первое число.

Так, например, в псевдокоде

x = CreateAdder(3)
x(5) // returns 8
x(10) // returns 13
CreateAdder(20)(30) // returns 50

Я не достаточно удобен в F #, чтобы пытаться писать без проверки, но C # будет выглядеть примерно так:

public static Func<int, int> CreateAdder(int amountToAdd)
{
    return x => x + amountToAdd;
}

Это помогает?

РЕДАКТИРОВАТЬ: Как отметил Бруно, пример, который вы дали в своем вопросе, является именно тем примером, для которого я дал код C #, поэтому приведенный выше псевдокод будет выглядеть так:

let x = f1 3
x 5 // Result: 8
x 10 // Result: 13
f1 20 30 // Result: 50
7 голосов
/ 01 февраля 2010

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

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

Например, предположим, что есть функция добавления, которая принимает двацелые числа и складывает их вместе:

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.

6 голосов
/ 01 февраля 2010

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

2 голосов
/ 01 февраля 2010

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

Последняя часть этого:

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

должно быть довольно простым, пример C #:

public int Test(int takesAnInteger) { return 0; }

Итак, мы остались с

функция, принимающая целое число, которое возвращает (функция, подобная приведенной выше)

C # снова:

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;
    }
}
1 голос
/ 01 февраля 2010

В F # (и многих других функциональных языках) есть концепция, называемая каррированными функциями. Это то, что вы видите. По сути, каждая функция принимает один аргумент и возвращает одно значение.

Поначалу это немного сбивает с толку, потому что вы можете написать let add x y = x + y и добавить два аргумента. Но на самом деле оригинальная функция add принимает только аргумент x. Когда вы применяете его, он возвращает функцию, которая принимает один аргумент (y) и уже заполнена значением x. Когда вы затем применяете эту функцию, она возвращает желаемое целое число.

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

1 голос
/ 01 февраля 2010

Возможно, вы захотите прочитать

Типы функций F #: веселье с кортежами и карри

0 голосов
/ 10 февраля 2010

Вот мой 2 с. По умолчанию функции F # включают частичное применение или каррирование. Это означает, что когда вы определяете это:

let adder a b = a + b;;

Вы определяете функцию, которая принимает целое число и возвращает функцию, которая принимает целое число и возвращает целое число или int -> int -> int. Затем каррирование позволяет вам частично применить функцию для создания другой функции:

let twoadder = adder 2;;
//val it: int -> int

Приведенный выше код предопределил a к 2, так что всякий раз, когда вы вызываете twoadder 3, он просто добавляет два к аргументу.

Синтаксис, в котором параметры функции разделены пробелом, эквивалентен этому лямбда-синтаксису:

let adder = fun a -> fun b -> a + b;;

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

0 голосов
/ 04 февраля 2010

Здесь уже есть много ответов, но я хотел бы предложить еще один дубль. Иногда объяснение одной и той же вещи множеством разных способов помогает вам «впасть в это».

Мне нравится думать о функциях как о "ты дашь мне что-то, а я верну тебе кое-что еще"

Так что Func<int, string> говорит: "Вы дайте мне int, и я дам вам строку".

Мне также легче думать в терминах «позже»: « Когда вы дадите мне int, я дам вам строку». Это особенно важно, когда вы видите такие вещи, как myfunc = x => y => x + y Когда вы даете карри x, вы получаете что-то такое, что , когда вы дадите ему y, вернет x + y») ,

(Кстати, я предполагаю, что вы знакомы с C # здесь)

Чтобы мы могли выразить ваш int -> int -> int пример как Func<int, Func<int, int>>.

Другой способ, которым я смотрю на int -> int -> int, заключается в том, что вы удаляете каждый элемент слева, предоставляя аргумент соответствующего типа. А когда у вас больше нет ->, вы выходите за пределы «позже» и вы получаете значение.


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

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);
}
0 голосов
/ 01 февраля 2010

Концепция называется Функция высшего порядка и довольно распространена для функционального программирования.

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

...