使用标记值寻找有关分类更改问题的反馈

问题描述

我正在自学 F#。我正在解决一个问题,以改变旧的英国货币单位。一半的问题是用三元组解决的,然后你再用一个记录来解决问题。好消息是我已经让我的三重解决方案工作了,我相信我也可以让记录变体工作。

然而,由于我是一个人工作,而且书中没有解决的例子,我正在寻找反馈。特别是,我解决这个问题的方式与我使用过程/命令式语言解决它的方式非常相似。此外,提示要求我使用模式,但我没有看到我的解决方案可以从使用模式中受益的任何地方。此外,我会询问有关代码中的警告和错误的问题。

代码如下

(*
3.2 The former British currency had 12 pence to a shilling and 20 shillings to a pound. 
Declare functions to add and subtract two amounts,represented by triples (pounds,shillings,pence) of integers,and declare the functions when a representation by records is used. 
Declare the functions in infix notation with proper precedences,and use patterns to obtain readable declarations.

Hansen,Michael R.; Rischel,Hans. Functional Programming Using F# (p. 66). Cambridge University Press. Kindle Edition. 
*)

(*
 My strategy is to convert pounds,and pence to the smallest unit,pence,then rebuild a normalized form of 
 change from just pence.

 Addition and subtraction are done in Pence converted to int.
*)

type OldBritishCurrency = | Pound of int
                          | Shilling of int
                          | Pence of int

let pencePerShilling = 12
let shillingPerPound = 20
let pencePerPound = shillingPerPound * pencePerShilling

// OldBritishCurrency.Pence != int and I don't kNow how to overload an (int (Pence x)) cast yet
let pencetoInt (Pence p) = p

(*
Ch03.fsx(87,39): warning FS0025: Incomplete pattern matches on this expression. 
    For example,the value 'Pence (_)' may indicate a case not covered by the pattern(s).

I am thinking of OldBritishCurrency as a type with subtypes Pound,Shilling,and Pence,However,a tagged type is obvIoUsly NOT a subtype,at least not exactly.

The warning message and the question seem to assume the need for a pattern to ID the tag.

This function converts Pound * Shilling * Pence to Pence.
*)
let oldBritishChangetoPence(Pound bp,Shilling s,Pence p) =
    Pence (bp * pencePerPound + s * pencePerShilling + p)

let pencetoShillingsAndPence(Pound bp,Pence p) =
    (Pound bp,Shilling (s + p / pencePerShilling),Pence (p % pencePerShilling))

let shillingsToPoundsAndShillings(Pound bp,Pence p) =
    (Pound (bp + s / shillingPerPound),Shilling (s % shillingPerPound),Pence p)

//Converts Pence to Pound * Shilling * Pence in standard form
let pencetonormalizedOldBritishCurrency (Pence p) = 
        pencetoShillingsAndPence(Pound 0,Shilling 0,Pence p)  |> shillingsToPoundsAndShillings
        
let (.+.) (Pound bp,Pence p) (Pound bp',Shilling s',Pence p') =
    let a = pencetoInt(oldBritishChangetoPence(Pound bp,Pence p)) // don't kNow how to overload +
    let b = pencetoInt(oldBritishChangetoPence(Pound bp',Pence p'))
    pencetonormalizedOldBritishCurrency(Pence (a + b))

let (.-.) (Pound bp,Pence p)) // don't kNow how to overload -
    let b = pencetoInt(oldBritishChangetoPence(Pound bp',Pence p'))
    pencetonormalizedOldBritishCurrency(Pence (a - b))

// testing

oldBritishChangetoPence(Pound 2,Shilling 2,Pence 3)

pencetonormalizedOldBritishCurrency(Pence 507)

oldBritishChangetoPence(Pound 10,Shilling 10,Pence 0)

pencetonormalizedOldBritishCurrency(Pence 2520)

pencetonormalizedOldBritishCurrency(Pence 253)

//

oldBritishChangetoPence(Pound 0,Shilling 19,Pence 11)

pencetonormalizedOldBritishCurrency(Pence(507 + 239))

(Pound 2,Pence 3) .+. (Pound 0,Pence 11)

pencetonormalizedOldBritishCurrency(Pence(507 - 239))

(Pound 2,Pence 3) .-. (Pound 0,Pence 11)

pencetonormalizedOldBritishCurrency(Pence(239 - 507))

(Pound 0,Pence 11) .-. (Pound 2,Pence 3)

//

oldBritishChangetoPence(Shilling 0,Shilling 1,Pence 1)
(*
Microsoft.FSharp.Core.MatchFailureException: The match cases were incomplete
at FSI_0053.oldBritishChangetoPence(OldBritishCurrency _arg1,OldBritishCurrency _arg2,OldBritishCurrency _arg3) 
in C:\Users\trent\Source\Repos\HansenAndRischelCh03\HansenAndRischelCh03\Ch03.fsx:line 0
at <StartupCode$FSI_0054>.$FSI_0054.main@()
Stopped due to error

So,if I don't use a pattern,I get a warning,but when I provide Shilling and Pound is expected,I get an error
similar to a type match error ... which is GOOD!  It's just what I want.

Can I ignore the prevIoUs warning?

And if I can protect myself with something akin to type checking,what is the pattern matching 
    the question wanted me to include?
*)

解决方法

我认为您对数据的基本表示有点令人困惑。您将 OldBritishCurrency 定义为可区分的联合,可以是磅、先令或便士。但是,您不使用它来表示可以在三种更改中的任何一种中指定的数据。您通常需要英镑先令便士,最好使用元组:

type OldBritishCurrency = int * int * int

但是,我认为您将英镑、先令和便士兑换成便士的想法很有效。您可以使用元组表示很容易地做到这一点:

let pencePerShilling = 12
let shillingPerPound = 20
let pencePerPound = shillingPerPound * pencePerShilling

let oldBritishToPence (bp,s,p) =
    (bp * pencePerPound + s * pencePerShilling + p)

let penceToOldBritish p =
    let s = p / pencePerShilling
    (s / shillingPerPound,s % shillingPerPound,p % pencePerShilling)

在这种表示中,你失去了一些可读性,因为元组只是三个未命名的整数。这是记录会更好的地方:

type OldBritishCurrency = { Pounds:int; Shillings:int; Pence:int }

如果你修改上面的代码来使用它,你会得到一个更好、更易读的解决方案!