【问题标题】:F# function to return either of multiple typesF# 函数返回多种类型中的任何一种
【发布时间】:2017-01-17 05:51:16
【问题描述】:

我的场景是用户输入要执行的文本的 DSL。 text 可以是数字运算,也可以是对字符串或数字的关系运算(返回布尔值)。 在用户输入中可以是常量值或变量(其中变量值必须从另一个源/字典中获取并用于操作)

例子(数字运算)

(1 + 2)
(@variableName@ + 2)   //returns int

关系运算示例

(@countryname@ in {abc,def,ghi}) //returns bool over variable with string value
(3 > 5) //returns bool over int
(@VariableInt@ > 5) // bool over variable having int value

我有 DU 用数字表示

type NumberExpression
| VariableName of string  //variable name
|Constant of int
| Add of NumberExpression * expression

type RelationExpression
 | Boolean of bool
 | GT of NumberExpression * NumberExpression 
 | In of NumberExpression * string list

为了解释我的表达式/变量,我有类似的东西,如果是 variableName,我想从这里返回字符串或 int,这样我的 RelationExpression|In 可以获得字符串值

let rec Interpret input =
 match input with
 | VariableName(name) -> 
     let stringvalue,datatype = datadictionary.[name] //assume value is from map. I need to do typecase and either return string or int here???
 | Constant(value) ->value

对于变量的数字操作适用于关系表达式,因为它的变量返回类型是 int。现在我必须处理返回字符串值的变量的返回类型。 我的 NumberExpression|variable 如何返回 int/string

我可以尝试创建一个浮点和字符串的鉴别器联合,我的函数将返回这个新的鉴别器联合,但现在的问题是所有默认情况下浮点操作在我的新鉴别器联合上都无效(即使是浮动价值)。

type myResultType = 
   | Number of float
   | StringItem of string

//assume function has some logic to return either float/string
let myFunction input = 
  match input with
  | "F" -> input |> Number
  | "S" -> input |> StringItem

// assume some logic to consume float number returned by my function. 
let finalValue = (10.0) + (myFunction "200F")

尽管我的函数返回内部浮点数,但它的新联合类型并没有 + 运算符。所以使用 DU 返回任何一种类型的方法对我都不起作用。如何处理?

【问题讨论】:

  • 在保持类型安全的同时,没有 du 就无法完成,或者在 du 中添加运算符需要付出很多努力
  • 这是一个逻辑问题而不是技术问题。有一些方法可以让它工作,但是当返回字符串时它会在运行时失败,所以我们又来质疑如果它不提供类型安全,为什么要使用 DU。你能提供更多关于你的具体场景的细节吗,看起来设计有问题。
  • 既然我看到了你的上下文,我想说你应该在操作中使用它之前评估变量。所以:let eval (thing:myResultType) = match thing with Number n -> n | StringItem name -> lookupValueOf namelookupValueOf 函数接受一个字符串并返回一个浮点数。那么你就不必再重新定义运算符了:你只需在计算值之前将这些字符串转换为浮点数(因为用户键入他们尚未定义的名称是错误的,对吧?)
  • 我的变量可以保存字符串/整数值。现在我的lookupValueof 函数必须要么返回一个浮点数/字符串。我的查找源是一个字典,其值为 Tuple("stringvalue"* "DataType")。根据数据类型,我必须将 stringvalue 类型转换为该数据类型。

标签: f# f#-3.0


【解决方案1】:

这是您可以做到的一种方法。我先展示代码,再解释一下:

type myResultType = 
   | Number of float
   | StringItem of string

// I fleshed out your `myFunction` example so it actually compiles
let myFunction (input:string) = 
  match input.[input.Length-1] with
  | 'F' -> input.[0..input.Length-2] |> float |> Number
  | 'S' -> input.[0..input.Length-2] |> StringItem

// Use this one for unary operators (no examples given here)
let floatOp op result =
    match result with
    | Number n -> op n |> Number
    | anythingElse -> anythingElse

// Use this one to define operators where the "real" float is the first operand
let floatOp2 op floatArg result =
    match result with
    | Number n -> op floatArg n |> Number
    | anythingElse -> anythingElse

// Use this one to define operators where the "real" float is the second operand
let floatOp2' op result floatArg =
    match result with
    | Number n -> op n floatArg |> Number
    | anythingElse -> anythingElse

let (+.) = floatOp2 (+)
let ( *. ) = floatOp2 (*)   // Spaces needed so this doesn't look like a comment
// etc.
let (.+) = floatOp2' (+)
// etc.

let finalValue = (10.0) +. (myFunction "200F")
let finalValue' = (myFunction "200F") .+ 10.0

您无法重新定义现有的 + 运算符,但您可以创建自己的运算符来处理 Number 情况(如果您的结果类型是 StringItem,则不执行任何操作案子)。由于所有这些运算符的通用代码看起来几乎相同,因此我将其提取到自己的函数中。然后,您可以定义-./. 等运算符,其方式与定义+.*. 运算符的方式大致相同。

请注意我在定义*. 运算符时如何在括号内放置空格。每当您定义以 * 字符开头(或结尾)的自定义运算符时,这都是必需的。否则 (**) 看起来像是评论的开始。例如,let (*!*) arg1 arg2 = ... 将 F# 解析器视为包含单个感叹号的注释,它认为您正在定义一个名为 arg1 的函数,该函数采用名为 arg2 的单个参数。但是,let ( *!* ) arg1 arg2 = ... 将被正确解析为定义一个接受两个参数的运算符 *!*

【讨论】:

  • 然而,我基本同意@Gustavo 的评论:这可能是一个应该做出不同的设计决策的标志。如果您发现要从函数中返回两种不同的类型,则可能需要重新考虑该函数的设计。为什么要它返回两种不同的类型?这是数据模型的基本部分,还是意外的复杂性?
  • 感谢所有输入,我已修改问题以提供更多有关我的问题的上下文
猜你喜欢
  • 1970-01-01
  • 2017-10-02
  • 1970-01-01
  • 1970-01-01
  • 2016-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多