F#Seq.head&;Seq.tail类型与自定义类型不匹配

F#Seq.head&;Seq.tail类型与自定义类型不匹配,f#,type-mismatch,custom-type,F#,Type Mismatch,Custom Type,我有自定义类型的等级,和套装,组合成一张自定义类型的卡,以及一个自定义类型集合手,这是一系列卡 当我在递归handValue函数中使用Seq.head时,我得到错误: 此表达式应具有类型'Card',但此处具有类型'a->'b' 我的印象是,由于手是卡的序列,因此Seq.head将返回序列中的第一个元素,即类型卡 另外,当我使用Seq.tail时,我会得到错误: 您正在传递cardValue函数的seq.head类型'a->seq;您需要先应用该函数,然后传递结果: type Suit = Sp

我有自定义类型的
等级
,和
套装
,组合成一张自定义类型的
,以及一个自定义类型集合
,这是一系列卡

当我在递归
handValue
函数中使用
Seq.head
时,我得到错误:

此表达式应具有类型
'Card'
,但此处具有类型
'a->'b'

我的印象是,由于
的序列,因此
Seq.head
将返回序列中的第一个元素,即类型

另外,当我使用
Seq.tail
时,我会得到错误:


您正在传递
cardValue
函数的
seq.head
类型
'a->seq;您需要先应用该函数,然后传递结果:

type Suit = Spades | Clubs | Hearts | Diamonds
type Rank = Ace | Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King
type Card = {suit: Suit; rank: Rank}

type Hand = Card seq

let AllSuits = [ Spades; Clubs; Hearts; Diamonds ]
let AllRanks = [ Ace; Two; Three; Four; Five; Six; Seven; Eight; Nine; Ten; Jack; Queen; King ]

let cardValue (card:Card) = 
    match card.rank with
    | Ace -> 1
    | Two -> 2
    | Three -> 3
    | Four -> 4
    | Five -> 5
    | Six -> 6
    | Seven -> 7
    | Eight -> 8
    | Nine -> 9
    | Ten | Jack | Queen | King -> 10

let rec handValue (hand:Hand) =
    if Seq.length hand = 1
    then cardValue Seq.head
    else cardValue Seq.head + handValue Seq.tail
注意:如果传递了一个空序列并且不是尾部递归的,则此操作将失败;修复这些问题,再加上一点冗余消除,会产生如下结果:

let rec handValue (hand:Hand) =
    if Seq.length hand = 1
    then cardValue (Seq.head hand)
    else cardValue (Seq.head hand) + handValue (Seq.tail hand)
也就是说,这是非常低效的;当使用
列表
而不是
seq
时,此代码结构可以很好地工作,但最好使用高阶函数来处理后者:

let handValue hand =
    let rec impl acc h =
        if Seq.isEmpty h then acc
        else impl (cardValue (Seq.head h) + acc) (Seq.tail h)
    impl 0 hand
为什么Seq.tail效率低下 伊尔贾恩的回答是正确的;我不会重复他说的话,但我会更深入地谈一个细节。他说“这是极其低效的”,但没有详细说明。但是我将告诉您为什么应该避免反复调用
Seq.tail
来迭代序列

重复调用
Seq.tail
效率极低的原因在于它的实现方式。当您说
let s2=Seq.tail s1
时,它会为
s2
创建一个新的枚举器,该枚举器在枚举时将枚举
s1
,删除其第一项,然后生成其余项。然后,再次执行递归函数,并执行与让s3=Seq.tail s2
等效的操作。这将为
s3
创建一个新的枚举器,该枚举器在合并时将枚举
s2
,删除其第一项,然后生成其余项。当您枚举
s2
时,它将枚举所有
s1
,删除其第一项,并生成其余项。结果是,
s3
将删除
s1
的前两项,但它会创建并枚举两个枚举器。然后在下一次循环中,
让s4=Seq.tail s3
生成一个枚举器,该枚举器将枚举
s3
并删除其第一项,这将导致枚举
s2
并删除其第一项,这将导致枚举
s1
并删除其第一项,因此,您已经创建并枚举了三个枚举

结果是,在长度为N的序列上重复调用
Seq.tail
,最终创建并枚举1+2+3+4+5+…+N个枚举数,时间为O(N^2),并使用O(N^2)空间(!)。将seq转换为列表要高效得多
List.tail
在时间上是O(1)并且不使用任何空间,因此通过
List.tail
枚举整个列表是O(N),并且除了最初创建列表时使用的O(N)空间之外,不使用额外的空间

  • 通过
    seq.head
    seq.tail
    枚举序列:O(N^2)时间和O(N^2)空间
  • 通过
    list.head
    list.tail
    枚举列表:O(N)时间和常量空间(如果您已经拥有该列表)或O(N)空间(如果您必须从seq创建列表以枚举它)
更好的方法 但是,还有一种更好的方法来编写
handValue
函数。当我看到它时,我看到它所做的是:对于手中的每一张卡,它计算卡的价值,并将所有卡的价值相加得到手的价值。你知道这听起来像什么吗?这个因此,您可以像这样重写
handValue
函数:

let handValue hand = hand |> Seq.sumBy cardValue
由于
Seq.sumBy
是O(N)并且不需要额外的空间,因此这比将Seq转换为列表并枚举列表更有效

let handValue (hand:Hand) = hand |> Seq.sumBy cardValue