F#计算表达式:可以用一个来简化这段代码吗?

Che*_*vas 3 f# computation-expression

我最近开始使用计算表达式来简化我的代码.到目前为止,对我来说唯一有用的是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
Run Code Online (Sandbox Code Playgroud)

但我想探索其他用途.一种可能性是我目前面临的情况.我有供应商提供的一些定义对称矩阵的数据.为了节省空间,仅给出矩阵的三角形部分,因为另一侧只是转置.所以,如果我在csv中看到一行代码

abc,def,123

这意味着行abc和列def的值是123.但是我不会看到像这样的行

def,abc,123

因为矩阵的对称性质已经给出了这个信息.

我已经在a中加载了所有这些数据,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)
Run Code Online (Sandbox Code Playgroud)

既然我知道计算表达式,我怀疑匹配语句可以被隐藏.我可以使用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)
Run Code Online (Sandbox Code Playgroud)

这样做,我已经从4个匹配语句变为2.是否有一种(非人为的)通过使用计算表达式进一步清理它的方法?

Fyo*_*kin 6

首先,MaybeBuilder每次需要时创建一个新的东西都是浪费.你应该这样做一次,最好是在MaybeBuilder它自己的定义旁边,然后在任何地方使用相同的实例.这就是大多数计算构建器的工作方式.

第二:如果你只是将"try"逻辑定义为一个函数并重用它,你可以减少杂乱的数量:

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)
Run Code Online (Sandbox Code Playgroud)

第三,注意你正在使用的模式:尝试这个,如果没有尝试,如果没有尝试另一个,等等.模式可以抽象为函数(这是整个演出!),所以让我们这样做:

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)
Run Code Online (Sandbox Code Playgroud)

最后,我认为你的方式是错误的.你真正想要的是一个带有两部分对称密钥的字典.那么为什么不这样做呢?

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)
Run Code Online (Sandbox Code Playgroud)

这里,MyKey是一种封装一对字符串的类型,但保证这些字符串是"按顺序" - 即第一个字符串按字典顺序"少于"第二个字符串.为了保证这一点,我创建了类型为private的构造函数mkMyKey,而是公开了一个正确构造密钥的函数(有时称为"智能构造函数").

现在,您可以自由地使用MyKey构造和查找地图.如果你投入(a, b, 42),你会得到两个(a, b, 42)(b, a, 42).

除了一些:我在代码中看到的一般错误是未能使用抽象.您不必处理最低级别的每个数据.该语言允许您定义更高级别的概念,然后根据它们进行编程.使用该能力.