f#解释签名匹配

f#解释签名匹配,f#,F#,在很多情况下,我在F#方面遇到了困难。我相信我没有掌握一些基本概念。我希望有人能追踪我的推理,找出我遗漏的东西(可能很多) 假设我在使用Xunit。我想做的是,提供两个列表,成对应用Assert.Equal方法。例如: Open Xunit let test1 = [1;2;3] let test2 = [1;2;4] List.map2 Assert.Equal test1 test2 编译器抱怨函数Equal没有接受一个参数。就我所知,map2不应该提供它2个参数吗 作为健全性检查,我在f

在很多情况下,我在F#方面遇到了困难。我相信我没有掌握一些基本概念。我希望有人能追踪我的推理,找出我遗漏的东西(可能很多)

假设我在使用Xunit。我想做的是,提供两个列表,成对应用
Assert.Equal
方法。例如:

Open Xunit
let test1 = [1;2;3]
let test2 = [1;2;4]
List.map2 Assert.Equal test1 test2
编译器抱怨函数
Equal
没有接受一个参数。就我所知,
map2
不应该提供它2个参数吗

作为健全性检查,我在f#immediate中使用以下代码:

let doequal = fun x y -> printf "result: %b\n" (x = y)
let test1 = [1;2;3]
let test2 = [1;2;4]
List.map2 doequal test1 test2;;
这似乎是相同的
doequal
是一个lambda,它接受两个通用参数并返回单位
List.map2
将每个参数两两交给lambda,我得到了我期望的输出:

result: true
result: true
result: false
那么是什么原因呢?源代码显示
Xunit.Equal
具有签名
public static void Equal(预期为T,实际为T)
。为什么我的参数不能映射到方法签名上

编辑一个 我认为两个变量x和y与一个元组(x,y)可以互换地构造和解构。所以我尝试了两种选择,得到了不同的结果。看来第二条可能比第一条走得更远

List.map2 Assert.Equal(test1,test2)
编译器现在抱怨“连续参数应该是分隔的空格或元组”

List.map2(Assert.Equal(test1,test2))

编译器现在抱怨“无法确定唯一的重载方法…”。。。可能需要一个类型注释“

根据它的签名
Xunit.Assert.Equal()
接受一个2值元组参数我认为部分问题来自混合方法(OO样式)和函数(FP样式)

public static void Equal<T>(T expected, T actual)
  • FP样式函数具有多个由空格分隔的参数
  • OO风格的方法具有由逗号分隔的参数和参数
  • 其他.NET库中的方法总是使用“tuple”语法调用(实际上与tuple有细微的不同),tuple被视为一个参数
F#编译器尝试处理这两种方法,但偶尔需要一些帮助

一种方法是用FP函数“包装”OO方法

// wrap method call with function
let assertEqual x y = Assert.Equal(x,y)

// all FP-style functions
List.map2 assertEqual test1 test2
如果不创建帮助器函数,则在使用lambda调用方法“inline”时,通常需要将多个函数参数转换为一个元组:

List.map2 (fun x y -> Assert.Equal(x,y)) test1 test2
在一行中混合使用方法和函数时,通常会出现“应分隔连续参数”错误

这说明编译器有问题,需要一些帮助

您可以通过方法调用周围的额外参数来解决此问题:

printfn "%s" ("hello".ToUpper())  // ok
或者有时,使用反向管道:

printfn "%s" <| "hello".ToUpper() // ok

请注意,在最后一行中,我必须使用parens将
包含的“a”优先于
列表。filter

都在类型签名中

Assert.Equals
的签名类似于
'a*'a->unit
<代码>列表.map2
需要一个
'a->'b->'c

他们只是不适合在一起

List.map2(fun x y->Assert.Equal(x,y))test1 test2
-工作,因为lambda包装
Equals
具有预期的签名

List.zip test1 test2 |>List.map Assert.Equal
-之所以有效,是因为现在只有一个元组列表,而且
List.map
需要一个
'a->'b
函数(其中
'a
现在是元组),
Assert.Equal
现在是一个公平的游戏


两个值和一个元组是隐式可互换的,这根本不是真的。至少就F#语言而言,或者就基本的IL表示而言,不是这样。你可以这样认为,当你从C#code中调用一个
'a->'b->'C
函数时,它确实以与
'a*'b->'C
函数相同的语法方式被调用,但这与其说是一个规则,不如说是一个例外。

List.map2 Assert.Equal(test1,test2)
-当然这不是你想要的。除了第1条注释-如果你只想比较列表,
Assert.Equal(test1,test2)
工作得很好,不需要在列表上映射它。除了第2条注释-如果你有一个返回单位的函数,你通常希望使用
iter
,而不是
map
,这样你就不会得到一个单位列表。
printfn "%s" ("hello".ToUpper())  // ok
printfn "%s" <| "hello".ToUpper() // ok
// wrap method call with function AND swap params
let contains searchFor (s:string) = s.Contains(searchFor)

// all FP-style functions
["a"; "b"; "c"]
|> List.filter (contains "a")