我正在尝试decimal options使用自定义类型进行一些数学运算:
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
Run Code Online (Sandbox Code Playgroud)
我可以做一些非常简单的算术
items
|> Seq.map (fun line -> line.Price
|> Option.map (fun x -> discount * x))
Run Code Online (Sandbox Code Playgroud)
这给了我
val it : seq<decimal option> =
seq [null; Some 0.500M; Some 0.750M; Some 0.250M; ...]
Run Code Online (Sandbox Code Playgroud)
如果我尝试实际计算我需要的东西
items
|> Seq.map (fun line -> line.Price
|> Option.map (fun x -> discount * x)
|> Option.map (fun x -> x - (line.Cost
|> Option.map (fun x -> x)))
|> Option.map (fun x -> x * (line.Qty
|> Option.map (fun x -> x))))
Run Code Online (Sandbox Code Playgroud)
我收到了错误
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'
Run Code Online (Sandbox Code Playgroud)
在哪里我会期待一个seq<decimal option>.
我一定是在遗漏一些东西,但我似乎无法发现它是什么,我错过了.
你混合decimal使用decimal option.
如果您尝试解决所有问题,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)
Run Code Online (Sandbox Code Playgroud)
这可能是一个有趣的练习,特别是如果你想了解monad,那么你将能够使用计算表达式,你可以创建自己的或使用像F#x或F#+这样的库:
open FSharpPlus.Builders
items |> Seq.map (fun line ->
monad {
let! price = line.Price
let! cost = line.Cost
let! qty = line.Qty
return ((discount * price - cost ) * qty)
}
)
Run Code Online (Sandbox Code Playgroud)
但如果您链接F#+,您将拥有应用数学运算符:
open FSharpPlus.Operators.ApplicativeMath
items |> Seq.map (fun line -> ((discount *| line.Price) |-| line.Cost ) |*| line.Qty)
Run Code Online (Sandbox Code Playgroud)
这是学习的好东西,但除此之外我建议使用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)
Run Code Online (Sandbox Code Playgroud)
然后,由于您还可以对记录进行模式匹配,因此可以进一步简化为:
items
|> Seq.map (function
| {Cost = Some cost; Price = Some price; Qty = Some qty} ->
Some ((discount * price - cost ) * qty)
| _ -> None)
Run Code Online (Sandbox Code Playgroud)
请注意,Option.map (fun x -> x)不会转换任何内容.
您遇到的一个问题是以下代码:
(line.Cost |> Option.map (fun x -> x))
Run Code Online (Sandbox Code Playgroud)
lambda 函数(fun x -> x)已经存在。这就是id功能。它只会返回您未更改的任何内容。您也可以编写这样的代码:
(line.Cost |> Option.map id)
Run Code Online (Sandbox Code Playgroud)
接下来的事情。对id函数进行映射是没有意义的。您解开选项中的任何内容,将id函数应用于它。什么根本没有改变小数点。然后你再次将小数包裹在一个选项中。你也可以只写:
line.Cost
Run Code Online (Sandbox Code Playgroud)
并完全删除Option.map它,因为它什么都不做。
所以你在这里的代码:
|> Option.map (fun x -> x - (line.Cost |> Option.map (fun x -> x)))
Run Code Online (Sandbox Code Playgroud)
等同于:
|> Option.map (fun x -> x - line.Cost)
Run Code Online (Sandbox Code Playgroud)
这显然是因为在这里你尝试减去不起作用x一个decimal带line.Cost一个option decimal。所以你会得到一个类型错误。
我想你真正想做的是line.Cost从line.Priceifline.Cost存在中减去,否则你想保持line.Price不变。
一种方法是仅提供line.Costs可以使用的默认值,并且对减法没有影响。例如,您可以使用0减法值if line.Costsis None。
所以你也可以写这样的东西:
|> Option.map (fun x -> x - (defaultArg line.Cost 0m))
Run Code Online (Sandbox Code Playgroud)
乘法的默认值是1m。所以你总体结束。
items
|> Seq.map (fun line ->
line.Price
|> Option.map (fun x -> discount * x)
|> Option.map (fun x -> x - (defaultArg line.Cost 0m))
|> Option.map (fun x -> x * (defaultArg line.Qty 1m)))
Run Code Online (Sandbox Code Playgroud)
例如上面的代码返回:
[None; Some -2.500M; Some -21.250M; Some 0.500M; Some -10.500M]
Run Code Online (Sandbox Code Playgroud)
如果您的目标是None当单个值是 时,整个计算就会变成None。我只想添加map2一个辅助函数。
module Option =
let map2 f x y =
match x,y with
| Some x, Some y -> Some (f x y)
| _ -> None
Run Code Online (Sandbox Code Playgroud)
那么你就可以写:
items
|> List.map (fun line ->
line.Price
|> Option.map (fun price -> price * discount)
|> Option.map2 (fun cost price -> price - cost) line.Cost
|> Option.map2 (fun qty price -> price * qty) line.Qty)
Run Code Online (Sandbox Code Playgroud)
它会返回:
[None; None; Some -21.250M; None; None]
Run Code Online (Sandbox Code Playgroud)