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

How to handle different types for input of algorithm?

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

// 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))

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

您当然可以包装相同的 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

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