我试图在自定义类型上使用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
我可以用
做一些非常简单的算术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; ...]
如果我尝试实际计算我需要的东西
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))))
我收到错误
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<decimal option>
。
我必须遗漏一些东西,但我似乎无法发现我失踪的任何东西。
答案 0 :(得分:6)
您正在将decimal
与decimal option
混合。
如果您尝试使用Option.map
解决所有问题,则可能需要尝试使用Option.bind
,因此您的代码将被线性嵌套&#39;:
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)
这可能是一项有趣的练习,特别是如果你想理解monads,那么你将能够使用computation expression,你可以自己创建或使用像{{{{}}这样的库。 3}}或F#x:
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)
}
)
但如果您链接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 (fun x -> x)
不会转换任何内容。
答案 1 :(得分:4)
您遇到的一个问题是以下代码:
(line.Cost |> Option.map (fun x -> x))
lambda函数(fun x -> x)
已经存在。这是id
函数。它只返回你没有改变的东西。你也可以编写你喜欢的代码:
(line.Cost |> Option.map id)
接下来的事情。通过id
函数进行映射毫无意义。打开选项中的任何内容,将id
函数应用于该选项。什么都没有改变小数。然后在选项中再次包装小数。你也可以写:
line.Cost
并完全删除Option.map
,因为它什么都不做。
所以你在这里的代码:
|> Option.map (fun x -> x - (line.Cost |> Option.map (fun x -> x)))
与:
相同|> Option.map (fun x -> x - line.Cost)
这显然不起作用,因为在这里你尝试用x
和decimal
减去line.Cost
option decimal
。所以你得到了一个类型错误。
我想你真正想做的是如果line.Cost
存在,则从line.Price
减去line.Cost
,否则你想保持line.Price
不变。
一种方法是提供可以使用的line.Costs
的默认值,并且对减法没有影响。例如,如果0
为line.Costs
,则可以使用值None
进行减法。
所以你也可以写这样的东西:
|> Option.map (fun x -> x - (defaultArg line.Cost 0m))
乘法的默认值为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)))
上面的代码例如返回:
[None; Some -2.500M; Some -21.250M; Some 0.500M; Some -10.500M]
如果您的目标是整个计算尽快变为None
值为None
。我只想添加map2
作为辅助函数。
module Option =
let map2 f x y =
match x,y with
| Some x, Some y -> Some (f x y)
| _ -> None
然后你就可以写:
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)
它会返回:
[None; None; Some -21.250M; None; None]
答案 2 :(得分:1)
在Option.map
x
内Option.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 option
因此Option.map
必须转换为十进制才能与第一个None
中的内容兼容。但现在你必须处理if
结果。
下面是一个快速(而且令人讨厌的)修复,只是使用Value
语句来提取None
(这将是一个小数)或者如果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))
然后返回0。
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test
it('POST /api/v1/account/me status 500', function(done) {
var agent = chai.request.agent(server);
agent.post('/api/v1/account/login')
.send({_email: 'test@test.com', _password: 'testtest'})
.then(function(res){
agent.get('/api/v1/account/logout')
.then(function(res2){
agent.get('/api/v1/account/me')
.then(function(res3){
res2.should.have.status(500);
done();
});
});
});
});
对于更复杂的解决方案,我建议使用此answer。
答案 3 :(得分:1)
为了完整起见,您还可以通过&#34;提升&#34;来利用选项类型的monadic属性。选项之外的值。这是由applicative approach链接的@PiotrWolkowski和@Gustavo显示的更简单的变体。 Applicative不仅包装monad中的值,还包括应用于它们的函数。
我们首先根据bind
和return
使选项类型适用于monadic操作。值得庆幸的是,这些函数已经定义,参数顺序只有一点调整。
let (>>=) ma f = Option.bind f ma
let ``return`` = Some
除此之外,还有lift
功能和几个操作员以方便使用。如果需要,可以通过内联标记来概括它们。
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>