我最近开始使用计算表达式来简化我的代码。到目前为止,对我来说唯一有用的是MaybeBuilder,由此定义:
type internal MaybeBuilder() =
member this.Bind(x, f) =
match x with
| None -> None
| Some a -> f a
member this.Return(x) =
Some x
member this.ReturnFrom(x) = x
但我想探索其他用途。一种可能性是我目前面临的情况。我有供应商提供的一些定义对称矩阵的数据。为了节省空间,仅给出矩阵的三角形部分,因为另一侧只是转置。所以,如果我在csv中看到一行
abc,def,123
这意味着行abc和列def的值是123.但我不会看到诸如
之类的行def,abc,123
由于矩阵的对称性,已经给出了这些信息。我已将所有这些数据加载到Map<string,Map<string,float>>
中,并且我有一个函数可以获取任何类似这样的条目的值:
let myLookupFunction (m:Map<string,Map<string,float>>) s1 s2 =
let try1 =
match m.TryFind s1 with
|Some subMap -> subMap.TryFind s2
|_ -> None
match try1 with
|Some f -> f
|_ ->
let try2 =
match m.TryFind s2 with
|Some subMap -> subMap.TryFind s1
|_ -> None
match try2 with
|Some f -> f
|_ -> failwith (sprintf "Unable to locate a value between %s and %s" s1 s2)
现在我知道计算表达式,我怀疑可以隐藏匹配语句。 我可以像使用MaybeBuilder那样稍微清理一下
let myFunction2 (m:Map<string,Map<string,float>>) s1 s2 =
let maybe = new MaybeBuilder()
let try1 = maybe{
let! subMap = m.TryFind s1
return! subMap.TryFind s2
}
match try1 with
|Some f -> f
|_ ->
let try2 = maybe{
let! subMap = m.TryFind s2
return! subMap.TryFind s1
}
match try2 with
|Some f -> f
|_ -> failwith (sprintf "Unable to locate a value between %s and %s" s1 s2)
这样做,我已经从4个匹配语句转到2.是否有一种(非人为的)通过使用计算表达式进一步清理它的方法?
答案 0 :(得分:6)
首先,每次需要时创建一个新的MaybeBuilder
都有点浪费。你应该这样做一次,最好是在MaybeBuilder
本身的定义旁边,然后在任何地方使用相同的实例。这就是大多数计算构建器的工作方式。
第二:如果你只是定义&#34;尝试&#34;你可以减少混乱的数量。逻辑作为一个函数并重用它:
let myFunction2 (m:Map<string,Map<string,float>>) s1 s2 =
let try' (x1, x2) = maybe{
let! subMap = m.TryFind x1
return! subMap.TryFind x2
}
match try' (s1, s2) with
|Some f -> f
|_ ->
match try' (s2, s1) with
|Some f -> f
|_ -> failwith (sprintf "Unable to locate a value between %s and %s" s1 s2)
第三次,请注意您正在使用的模式:试试这个,如果没有尝试,如果没有尝试另一个,等等。模式可以抽象为函数(这就是&#39; s整个演出!),所以让我们这样做:
let orElse m f = match m with
| Some x -> Some x
| None -> f()
let myFunction2 (m:Map<string,Map<string,float>>) s1 s2 =
let try' (x1, x2) = maybe{
let! subMap = m.TryFind x1
return! subMap.TryFind x2
}
let result =
try' (s1, s2)
|> orElse (fun() -> try' (s2, s1))
match result with
|Some f -> f
|_ -> failwith (sprintf "Unable to locate a value between %s and %s" s1 s2)
最后,我认为你以错误的方式解决问题。你真正想要的是一个带有两部分对称密钥的字典。那么为什么不这样做呢?
module MyMatrix =
type MyKey = private MyKey of string * string
type MyMatrix = Map<MyKey, float>
let mkMyKey s1 s2 = if s1 < s2 then MyKey (s1, s2) else MyKey (s2, s1)
let myFunction2 (m:MyMatrix.MyMatrix) s1 s2 =
match m.TryFind (MyMatrix.mkMyKey s1 s2) with
| Some f -> f
| None -> failwith (sprintf "Unable to locate a value between %s and %s" s1 s2)
这里,MyKey
是一种封装一对字符串的类型,但保证这些字符串是&#34;顺序&#34; - 即第一个按字典顺序排列&#34; less&#34;比第二个。为了保证这一点,我创建了private类型的构造函数,而是公开了一个正确构造密钥的函数mkMyKey
(有时称为"smart constructor")。
现在,您可以自由地使用MyKey
来构建和查找地图。如果您输入(a, b, 42)
,则会同时获得(a, b, 42)
和(b, a, 42)
。
有些人:我在代码中看到的一般错误是无法使用抽象。您不必处理最低级别的每个数据。该语言允许您定义更高级别的概念,然后根据它们进行编程。使用那种能力。
答案 1 :(得分:4)
我理解这可能只是为了在这里提出问题而进行的简化 - 但是当没有找到任何密钥时你真正想做什么?你期望第一次查找失败的频率是多少?
有充分的理由避免F#中的异常 - 它们更慢(我不知道确切程度多少,它可能取决于您的用例)并且它们应该用于“特殊情况”,但语言对他们有很好的支持。
使用例外,你可以把它写成一个非常易读的三线:
let myLookupFunction (m:Map<string,Map<string,float>>) s1 s2 =
try m.[s1].[s2] with _ ->
try m.[s2].[s1] with _ ->
failwith (sprintf "Unable to locate a value between %s and %s" s1 s2)
那就是说,我完全同意Fyodor的说法,定义你自己的数据结构以保存数据而不是使用地图地图(可能是切换的键)会很有意义。