如何处理不同类型的算法输入?

时间:2018-01-17 01:45:56

标签: design-patterns f# functional-programming discriminated-union

我有一个根据用户输入的内容查找数据的算法。有多种方法可以指定唯一记录,因此我允许用户输入多个不同的唯一标识符。然而,当我开始编写算法时,我的头脑中响起了警钟,因为它看起来很冗长或不起作用。这只是感觉我做错了什么。让我用代码告诉你。

// Types for the domain model
type EmployeeID = ID of int

type EmployeeName =
    { First  : string
      Last   : string }

// Some instances of EmployeeName to use later
let james = {First = "James"; Last = "Winklenaught"}
let ted = {First = "Theodore"; Last = "Chesterton"}

// Input for the algorithm
type matcherInput =
    | EmployeeIDWrapper of EmployeeID
    | EmployeeNameWrapper of EmployeeName

// Output of the algorithm
type matcherOutput = 
    { Info : string }

// Returns data if it found it from our search algorithm
let LookupEmployee (input : matcherInput) : matcherOutput option = 
    (* 
       There's a lot of algorithm here in the real version
       that creates the lookup tables (maps). I just put in
       some dummy data instead. 
    *)
    let numberLookup = 
        Map.ofList [(james, ID 1); (ted, ID 2)]

    let infoLookup = 
        Map.ofList [(ID 1,{Info = "CEO"});(ID 2,{Info = "CFO"})]

    // output
    match input with
    | EmployeeIDWrapper number -> 
        Map.tryFind number infoLookup
    | EmployeeNameWrapper name -> 
        Map.tryFind name numberLookup 
        |> Option.bind (fun number -> Map.tryFind number infoLookup)



// doesn't work = (
LookupEmployee james
LookupEmployee (ID 1)

// right, but verbose
LookupEmployee (EmployeeNameWrapper james)
LookupEmployee (EmployeeIDWrapper (ID 1))

不知何故需要解开一切似乎对我来说太过分了。在这种情况下我不应该使用受歧视的工会吗?是否有我可以利用的既定功能设计模式?

1 个答案:

答案 0 :(得分:3)

您当然可以包装相同的DU案例,但是您需要调用MatcherInput.EmployeeID 2来获取MatcherOutput.EmployeeID。如有必要,您可以使用一些活动模式隐藏魔法。另一件事是,我假设记录应该包含在Name中。

type EmployeeName =
    { First  : string
      Last   : string }

type MatcherInput =
    | Name of EmployeeName
    | EmployeeID of int
    | Info of string

let james = Name {First = "James"; Last = "Winklenaught"}
let ted = Name {First = "Theodore"; Last = "Chesterton"}

let LookupEmployee (input: MatcherInput)  =

    let numberLookup = 
            Map.ofList [(james, EmployeeID 1); (ted, EmployeeID 2)]

    let infoLookup = 
        Map.ofList [(EmployeeID 1,Info "CEO");(EmployeeID 2,Info "CFO")]

    match input with 
    | Name n -> numberLookup.[Name n]
    | EmployeeID _  -> infoLookup.[input]
    | Info _ -> failwith "Dont match on Info"

LookupEmployee ted
LookupEmployee (EmployeeID 2)

如果您希望将输入和输出类型分开,并且将在更多类型上进行匹配,您可以使用泛型DU:

type EmployeeName =
    { First  : string
      Last   : string }

type MatcherInput =
    | Name of EmployeeName
    | EmployeeID of int

type MatcherOutput<'a> = 
    | Other of 'a
    | Info of string

let james = Name {First = "James"; Last = "Winklenaught"}
let ted = Name {First = "Theodore"; Last = "Chesterton"}

let LookupEmployee (input: MatcherInput) =

    let numberLookup = 
            Map.ofList [(james, EmployeeID 1); (ted, EmployeeID 2)]

    let infoLookup = 
        Map.ofList [(EmployeeID 1,Info "CEO");(EmployeeID 2,Info "CFO")]

    match input with 
    | Name _ -> Other (numberLookup.[input])
    | EmployeeID _ ->  infoLookup.[input]

let x = EmployeeID 1

LookupEmployee ted
LookupEmployee x

对于另一种解决方案,我会将员工信息保存在一条记录中。并始终返回Value作为完整记录,然后提取必要的信息。对于Key,您可以为记录的不同部分构建各种映射。如果更好的话,你甚至可以嵌套地图。