这在F#中意味着什么?"int -> int -> int"

14
我不知道这在 F# 中是什么意思。
“一个接受整数的函数,返回一个接受整数并返回整数的函数。”
但我没有很好地理解这个问题。
有人能够清楚地解释一下吗?

[更新]:

> let f1 x y = x+y ;;

 val f1 : int -> int -> int

这是什么意思?


你的新代码片段只是一个简单的函数,它接受两个整数值并返回一个新的整数值,该值为它们的和。因此,f1 5 4 将等于 9。"val f1: int -> int -> int" 部分只是 F# 告诉你刚刚在上一行声明的函数的结果。 - Matt Warren
相关问题:https://dev59.com/HXVD5IYBdhLWcg3wDXJ3 - Benjol
11个回答

58

F#类型

让我们从头开始。

F#使用冒号(:)符号来表示事物的类型。比如说你定义了一个int类型的值:

let myNumber = 5

F#交互式会理解myNumber是一个整数,并通过以下方式告诉您:

myNumber : int

该语句读作:

myNumber 的类型是 int

F# 函数式类型

到此为止,一切都很好。现在,让我们介绍一些新的概念,函数式类型。函数式类型简单来说就是一个函数的类型。F# 使用 -> 表示函数式类型。这个箭头符号表示左侧的内容被转化成右侧的内容。

让我们考虑一个简单的函数,它接收一个参数并将其转换为一个输出。这样一个函数的例子可以是:

isEven : int -> bool

这里介绍了函数的名称(在:左边)和其类型。这句话可以用英语表示为:

isEven 是一个将 int 转换为 bool 的函数类型。

请注意,为了正确理解所说的内容,在“is of type”部分之后应短暂停顿,然后一口气读完剩下的句子,不要停顿。

F#中的函数是值

在F#中,函数与普通类型相比(几乎)没有太多特殊之处。它们是可以传递给函数、从函数返回的东西,就像布尔值、整数或字符串一样。

因此,如果你有:

myNumber : int
isEven : int -> bool
你应该将intint -> bool视为同一种类型的两个实体。在这里,myNumberint类型的值,而isEvenint -> bool类型的值(当我在上面谈到短暂的停顿时,这就是我想象的东西)。

函数应用

包含->的类型的值也被称为函数,并具有特殊的能力:您可以将一个函数应用于一个值。例如:
isEven myNumber

这意味着你正在将函数isEven应用于值myNumber。通过检查isEven的类型,您可以预期它将返回一个布尔值。如果您正确实现了isEven,那么它显然会返回false

返回函数类型值的函数

让我们定义一个通用函数来确定整数是否是其他整数的倍数。我们可以想象我们函数的类型将是(括号在这里是为了帮助你理解,它们可能存在或不存在,有特殊含义):

isMultipleOf : int -> (int -> bool)

正如你可以猜测的那样,这段话的意思是:

isMultipleOf 是一种类型为(PAUSE)将 int 转换为函数,函数将 int 转换为 bool

(这里的(PAUSE)表示朗读时的暂停)。

我们稍后会定义这个函数。在此之前,让我们看看如何使用它:

let isEven = isMultipleOf 2

F#交互式窗口会回答:

isEven : int -> bool

这里提到的isEven的类型是int -> bool,表示我们将值2 (int) 传递给 isMultipleOf 函数后,它将返回一个新函数,即int -> bool

我们可以将isMultipleOf函数视为一种创建函数的工具。

isMultipleOf函数的定义

现在,让我们来定义一下这个神秘的函数创建函数。

let isMultipleOf n x =
    (x % n) = 0

很简单吧?

如果您将此输入到 F# 交互式中,它将回答:

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),它们更加明确地返回函数。

希望这可以帮到你。


5
这真是一个非常有帮助、深入详尽的答案!! - Matt Warren
4
你写了一章节,等整本书出版之后再加上去吧 ;) - ali62b
1
这是我迄今为止读过的关于函数式类型主题最好的解释。 - Pepor
1
热狗,那是一个完整的答案。+1 - Paul Nathan
现在一切都有意义了。Bruno,请帮我生孩子! - user1072706
显示剩余2条评论

9
这个的经典案例可能是“加法器创建器” - 一个函数,它给定一个数字(例如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;
}

这有帮助吗?

编辑:如Bruno所指出的那样,你在问题中给出的示例正是我为C#代码提供的示例,因此上述伪代码将变为:

let x = f1 3
x 5 // Result: 8
x 10 // Result: 13
f1 20 30 // Result: 50

1
Jon,F#版本正是OP在他的问题中所称的“f1”。使用它:let x = f1 3x 5x 10f1 20 30,根据你的例子。 - Bruno Reis
2
@ali62b:基本上是的。如果你把函数看作值(函数式语言中的基本元素),这就有意义了。假设f1函数带一个参数的返回值只是另一个函数(在C#中委托的方式)。 - Mehrdad Afshari
1
@ali62b:我不确定是否应该说它是“转换”-那只是你的函数声明的意思。但如果将其理解为转换更简单,那可能对你没有任何坏处。 - Jon Skeet
1
@Zakalwe,实际上这与纯函数无关。您可以在这样的函数中添加任何您想要的副作用。 - Bruno Reis
2
当然,在像F#这样的柯里化语言中,我们可以简单地将create_adder定义为create_adder = (+) - Chuck
显示剩余4条评论

7

这是一个接受整数并返回接受整数并返回整数的函数。

这等效于接受两个整数并返回整数的函数。在函数式语言中,处理接受多个参数的函数的方式很常见,这使得对值进行部分应用变得容易。

例如,假设有一个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函数即可。

让我们来解释一下这段代码的含义。代码中定义了一个函数 f1,它接受两个整数参数 x 和 y,并返回它们的和。函数的类型声明表明 f1 接受两个整数作为输入,并返回一个整数作为输出。所以,当你调用 f1 函数并传入两个整数时,它将返回这两个整数的和。希望这样能够帮助你理解这段代码的含义。 - ali62b
我更新了我的问题,你能解释一下这段代码吗? - ali62b
1
在 F# 中,函数不使用括号来声明它们的参数。基本上,C# 中的 f(int x, int y) 变成了 let f x y = ... - Mehrdad Afshari
你的评论并不完全正确。有时候你可以(而且需要)声明接受元组作为参数的函数。 - Bruno Reis
1
@布鲁诺:在元组的情况下,括号用于"元组声明",而不是参数声明。let f (x,y)接受一个单一类型的元组作为参数。 - Mehrdad Afshari

6

通常你会将它解释为一个接受两个整数并返回一个整数的函数。 你应该了解柯里化


2
一个接受整数的函数,返回一个接受整数并返回整数的函数。
其中的最后一部分:
一个接受整数并返回整数的函数
应该很简单,以下是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
在F#(以及许多其他函数式语言)中,有一个叫做柯里化函数的概念。这就是你所看到的。实际上,每个函数都只接受一个参数并返回一个值,这一点起初可能有些令人困惑。
因为你可以编写let add x y = x + y,它似乎添加了两个参数。但实际上,原始的add函数只接受参数x。当你应用它时,它会返回一个函数,该函数接受一个参数(y),并已经填充了x值。然后,当你应用那个函数时,它会返回所需的整数。
这在类型签名中得到了体现。将类型签名中的箭头视为“获取我左侧的东西并返回我右侧的东西”的意思。在类型int -> int -> int中,这意味着它接受类型为int的参数——一个整数,并返回类型为int -> int的函数——一个接受整数并返回整数的函数。你会注意到,这恰好与上面介绍的柯里化函数的工作方式相匹配。

0

这是我的两分钱。默认情况下,F#函数启用部分应用或柯里化。这意味着当您定义以下内容时:

let adder a b = a + b;;

您正在定义一个函数,该函数接受一个整数并返回一个接受整数并返回整数的函数或int -> int -> int。然后,柯里化允许您部分应用函数以创建另一个函数:

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

上述代码将a预定义为2,因此每当您调用twoadder 3时,它将简单地将2添加到参数。

函数参数由空格分隔的语法等同于此lambda语法:

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

哪种方法更易读,以确定这两个函数实际上是链接在一起的。

0

这个概念被称为高阶函数,在函数式编程中非常常见。

函数本身只是另一种数据类型。因此,您可以编写返回其他函数的函数。当然,您仍然可以有一个以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#)


Python也是一种函数式编程语言吗? - ali62b
不是特别的,但你可以用它做很多函数式编程。 - tback

0

这里已经有很多答案了,但是我想提供另一种看法。有时候用很多不同的方式解释同样的事情可以帮助你更好地理解。

我喜欢把函数想象成“你给我什么,我就会还给你什么”

所以一个 Func<int, string> 表示“你给我一个 int ,我就会还给你一个 string”。

我也发现,从“稍后”的角度来思考更容易: “ 你给我一个 int 的时候,我会还给你一个 string ”。当你看到像 myfunc = x => y => x + y 这样的代码的时候,这点尤其重要(“ 你给 curried 一个参数 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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接