F# 具有十进制选项类型的算术
我正在尝试对自定义类型使用F# 具有十进制选项类型的算术,f#,F#,我正在尝试对自定义类型使用十进制选项进行一些数学运算: type LineItem = {Cost: decimal option; Price: decimal option; Qty: decimal option} let discount = 0.25M let createItem (c, p, q) = {Cost = c; Price = p; Qty = q} let items = [ (Some 1M , None , So
十进制选项进行一些数学运算:
type LineItem = {Cost: decimal option; Price: decimal option; Qty: decimal option}
let discount = 0.25M
let createItem (c, p, q) =
{Cost = c; Price = p; Qty = q}
let items =
[
(Some 1M , None , Some 1M)
(Some 3M , Some 2.0M , None)
(Some 5M , Some 3.0M , Some 5M)
(None , Some 1.0M , Some 2M)
(Some 11M , Some 2.0M , None)
]
|> List.map createItem
我可以用计算机做一些非常简单的算术
items
|> Seq.map (fun line -> line.Price
|> Option.map (fun x -> discount * x))
这让我
val it : seq<decimal option> =
seq [null; Some 0.500M; Some 0.750M; Some 0.250M; ...]
我得到了错误
error FS0001: Type constraint mismatch. The type
'a option
is not compatible with type
decimal
The type ''a option' is not compatible with the type 'decimal'
我本以为会有一个seq
我一定是遗漏了什么,但我似乎看不出我遗漏了什么。在选项中。map
x
实际上是一个小数,但是选项的签名。map
是't Option->'U Option
。因此,这里:
Option.map (fun x -> x - (line.Cost |> Option.map (fun x -> x)))
您具有以下功能:
Option.map (fun x -> /*decimal*/ x - /*decimal option*/(line.Cost |> Option.map (fun x -> x)))
因此,必须将decimal选项
转换为decimal,以便与第一个选项.map中的内容兼容。但是现在您必须处理None
结果
下面是一个快速(且令人讨厌)的修复程序,它只是使用if
语句来提取值(将是一个小数),或者如果None
则返回0
items
|> Seq.map (fun line -> line.Price
|> Option.map (fun x -> discount * x)
|> Option.map (fun x -> x - if line.Cost.IsSome then line.Cost.Value else 0m)
|> Option.map (fun x -> x * if line.Qty.IsSome then line.Qty.Value else 0m))
对于更复杂的解决方案,我建议使用此选项。您将十进制
与十进制选项
混合使用
如果您试图使用Option.map
解决所有问题,您可能需要尝试使用Option.bind
,这样您的代码将“线性嵌套”:
items
|> Seq.map (
fun line ->
Option.bind(fun price ->
Option.bind(fun cost ->
Option.bind(fun qty ->
Some ((discount * price - cost ) * qty)) line.Qty) line.Cost) line.Price)
这可能是一个有趣的练习,特别是如果你想理解,那么你将能够使用,你可以创建自己的或使用库中的,如或:
但是,如果链接F#+则可以使用应用数学运算符:
open FSharpPlus.Operators.ApplicativeMath
items |> Seq.map (fun line -> ((discount *| line.Price) |-| line.Cost ) |*| line.Qty)
这是一个很好的学习方法,但如果不是这样,我建议使用F#内置功能,比如模式匹配,这会更容易:
items
|> Seq.map (fun line -> match line.Price, line.Qty, line.Cost with
| Some price, Some qty, Some cost ->
Some ((discount * price - cost ) * qty)
| _ -> None)
然后,由于还可以对记录进行模式匹配,因此可以进一步简化为:
items
|> Seq.map (function
| {Cost = Some cost; Price = Some price; Qty = Some qty} ->
Some ((discount * price - cost ) * qty)
| _ -> None)
请注意,Option.map(funx->x)
不会转换任何东西。您遇到的一个问题是以下代码:
(line.Cost |>Option.map(乐趣x->x))
lambda函数(funx->x)
已经存在。这是id
功能。它只会返回您没有更改的内容。您也可以这样编写代码:
(line.Cost |>Option.map id)
接下来呢。映射到id
函数没有意义。打开选项中的任何内容,对其应用id
函数。什么根本没有改变小数点。然后将小数再次包装到选项中。你也可以写:
line.Cost
并完全删除选项.map
,因为它什么也不做
这里的代码是:
|>Option.map(funx->x-(line.Cost |>Option.map(funx->x)))
与以下内容相同:
|>Option.map(乐趣x->x-line.Cost)
这显然不起作用,因为这里您尝试用line.Cost
aoption decimal
减去x
adecimal
。所以你得到了一个类型错误
我想你真正想做的是从line.Price
中减去line.Cost
,如果line.Cost
存在,否则你想保持line.Price
不变
一种方法是只为line.Costs
提供一个默认值,该值可以使用并且对减法没有影响。例如,如果line.Costs
为None
,则可以使用值0
进行减法
所以你也可以这样写:
|>Option.map(乐趣x->x-(defaultArg line.Cost 0m))
乘法的默认值为1m
。所以你总的来说是以
项目
|>Seq.map(趣味线->
线路,价格
|>Option.map(乐趣x->折扣*x)
|>Option.map(乐趣x->x-(默认参数行.成本0米))
|>Option.map(乐趣x->x*(默认参数行数量1m)))
例如,上面的代码返回:
[无;部分-2.500米;部分-21.250米;部分0.500米;部分-10.500米]
如果您的目标是一次计算完成后,整个计算将变成None
值为None
。我只想添加map2
作为辅助函数
模块选项=
设map2fxy=
将x,y与
|一些x,一些y->一些(f x y)
|无
然后你就可以写:
项目
|>List.map(趣味线->
线路,价格
|>Option.map(趣味价格->价格*折扣)
|>Option.map2(乐趣成本价格->价格-成本)行.cost
|>Option.map2(乐趣数量价格->价格*数量)行数量)
它将返回:
[无;无;部分-21.250米;无;无]
为完整起见,您还可以通过“提升”选项之外的值来利用选项类型的一元属性。这是链接方式和显示方式的一个稍微简单的变体。应用程序不仅在monad中包装值,还包装应用于它们的函数
我们首先使选项类型在bind
和return
方面适合于一元操作。谢天谢地,这些函数已经定义好了,参数顺序只做了一点调整
let (>>=) ma f = Option.bind f ma
let ``return`` = Some
除此之外,还有提升
功能和两个操作员,以方便操作。如果需要,可以通过内联标记它们来对其进行泛化
let liftOpt op x y =
x >>= fun a ->
y >>= fun b ->
``return`` (op a b)
let (.*) = liftOpt (*)
let (.-) = liftOpt (-)
现在你的计算变成了
items
|> Seq.map (fun line ->
(line.Price .* Some discount .- line.Cost) .* line.Qty )
|> Seq.iter (printfn "%A")
哪个会打印
约21.250米
为什么要继续做Option.map(funx->x)
?是的,我同意。应该缩短。修复了这个问题。在与同事交谈后,我在看到这个答案之前编写了一个optionMap2
函数。@Steven如果有3个选项,会发生什么?你会写map3
?好的,这只是一个细节,但是可读性呢?我认为用前进管道一步一步地做数学公式读起来不好。当你所有的数学公式都在一个地方时,看看我和其他人提供的备选方案。我推荐哪一种
let liftOpt op x y =
x >>= fun a ->
y >>= fun b ->
``return`` (op a b)
let (.*) = liftOpt (*)
let (.-) = liftOpt (-)
items
|> Seq.map (fun line ->
(line.Price .* Some discount .- line.Cost) .* line.Qty )
|> Seq.iter (printfn "%A")
<null>
<null>
Some -21.250M
<null>
<null>