F# Seq.head & Seq.tail type mismatch with custom types

自作多情 提交于 2019-12-02 03:00:01

问题


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

I have custom types Rank, and Suit, combined into a custom type Card, and a custom type collection Hand which is a sequence of cards.

When I use Seq.head in the recursive handValue function I get the error:

This expression was expected to have type 'Card' but here has type ''a -> 'b'

I was under the impression that as the hand is a sequence of Card , that Seq.head would return the first element in the sequence, as type Card.

Also when I use Seq.tail I get the error:

The type ''a -> seq<'b>' is not compatible with the type 'Hand'

I was under the impression that Seq.tail would return a sequence of Card, which although not explicitly declared as a Hand should be identical in functionality.

I'm unsure how to approach the first error at all, and for the second error what would be the most appropriate; casting the Hand type to a Sequence type and using Seq.tail, casting the returned Sequence type as a Hand type.

Or perhaps there is something minor in the syntax that i'm overlooking completely. Any help would be greatly appreciated.


回答1:


You're passing cardValue the Seq.head function; you need to apply that function first and pass the result instead:

let rec handValue (hand:Hand) =
    if Seq.length hand = 1
    then cardValue (Seq.head hand)
    else cardValue (Seq.head hand) + handValue (Seq.tail hand)

N.b. this will fail if passed an empty sequence and isn't tail recursive; fixing these, along with a little redundancy removal, yields something like:

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

That said, this is extremely inefficient; this code structure would work fine when using a list rather than a seq, but you're much better off using higher-order functions to process the latter:

let handValue hand = hand |> Seq.sumBy cardValue



回答2:


Why Seq.tail is inefficient

ildjarn's answer is correct; I won't repeat what he said, but I'll go into more depth about one detail. He said "this is extremely inefficient", but didn't go into details. But I'll tell you why you should avoid calling Seq.tail repeatedly to iterate through a sequence.

The reason why calling Seq.tail repeatedly is extremely inefficient is because of how it's implemented. When you say let s2 = Seq.tail s1, it creates a new enumerator for s2 that, when enumerated, will enumerate s1, drop its first item, then produce the rest of the items. Then you go through the recursive function again, and do the equivalent of let s3 = Seq.tail s2. This creates a new enumerator for s3 that, when enmerated, will enumerate s2, drop its first item, then produce the rest of the items. And when you enumerate s2, it enumerates all of s1, drops its first item, and produces the rest of the items. The result is that s3 will drop the first two items of s1, but it creates and enumerates two enumerators to do so. Then the next time through the loop, let s4 = Seq.tail s3 produces an enumerator that will enumerate s3 and drop its first item, which results in enumerating s2 and dropping its first item, which results in enumerating s1 and dropping its first item, so you've created and enumerated three enumerates in the process.

The result is that calling Seq.tail repeatedly on a sequence of length N ends up creating and enumerating 1+2+3+4+5+...+N enumerators, which is O(N^2) in time and uses O(N^2) space (!). It's far more efficient to turn the seq into a list; List.tail is O(1) in time and uses no space, so enumerating the whole list via List.tail is O(N) and uses no extra space besides the O(N) space you used to create the list in the first place.

  • Enumerating a seq via Seq.head and Seq.tail: O(N^2) time and O(N^2) space.
  • Enumerating a list via List.head and List.tail: O(N) time and constant space (if you already had the list) or O(N) space (if you had to create the list from a seq in order to enumerate it).

The better way

However, there's an even better way to write your handValue function. When I look at it, I see that what it's doing is: for every card in the hand, it calculates the value of the card, and sums up all the card values to get the hand value. You know what that sounds like? The Seq.sumBy function. So you could rewrite your handValue function like this:

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

And since Seq.sumBy is O(N) and takes zero extra space, that's even more efficient than turning your seq into a list and enumerating through the list.



来源:https://stackoverflow.com/questions/43554167/f-seq-head-seq-tail-type-mismatch-with-custom-types

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!