问题描述
我正在自学 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 }
如果你修改上面的代码来使用它,你会得到一个更好、更易读的解决方案!