F# 中的符号计算






4.96/5 (24投票s)
本文介绍了如何使用 F# 进行符号数学计算。
引言
函数式语言的一个绝佳应用领域就是符号计算。例如,转换代数表达式或计算函数导数。我记得多年前,当我看到一个只占不满一屏幕的 Prolog 程序,却能告诉我 sin(x) 的导数是 cos(x) 时,我是多么地印象深刻。所以,我也想在 F# 中做同样的事情。
如果你问一个只使用过程式语言的开发者写一个符号形式计算导数的程序,很有可能他会从解析输入字符串开始。这正是过程式语言的特性:你通过构建一系列将输入转换为输出的步骤来解决任务。一个专注于更高抽象层面的开发者可能会从构建表达式树类开始,这样他就可以在这些类之上实现导数计算。但是,导数计算与创建类来存储树节点有什么关系呢?我们为什么要在意呢?
在过程式语言中,正如 Niklaus Wirth 在他的书名《算法 + 数据结构 = 程序》中所说,我们需要在意。所以我们用算法来表达我们的命令式程序,并将它们应用于数据结构。
在 F# 中,情况完全不同。你只需描述你的领域、你的规则。然后事情自然而然地发生。
本文介绍了如何在 F# 中应用以下任务
- 导数的符号计算
- 简化代数表达式
- 格式化代数表达式
- 解析代数表达式
所有这些任务都可以串联起来,以实现复合操作:直接以纯文本形式进行导数的符号计算,输入和输出也都是纯文本,正如我们希望看到的那样。因此,如果我们用输入“sin(x ^ 2)”来运行我们的程序,我们将得到输出“2 * x * cos(x ^ 2)”。
1. 计算导数
首先,我们需要定义我们计算的范围,我们的符号。请注意,Expression 类型并没有定义数据结构——它只是我们用于构成函数表达式的符号的集合。
type Expression =
| X
| Const of float
| Neg of Expression
| Add of Expression * Expression
| Sub of Expression * Expression
| Mul of Expression * Expression
| Div of Expression * Expression
| Pow of Expression * Expression
| Exp of Expression
| Log of Expression
| Sin of Expression
| Cos of Expression
我们刚刚声明的就是 F# 中的判别联合。我不会花时间介绍 F# 的语法细节,我正在学习 F# 的两本很棒的书:《Programming F#》和《Real World Functional Programming》。(我从第二本开始,但不得不切换到第一本,在我看来,它提供了更好的函数式编程和 F# 风格的过渡。)
正如你所见,我们对表达式语法做了一些限制:我们假设数字常量是 float
类型,并且我们只支持四种函数:指数、自然对数、正弦和余弦。但这对于语言演示的目的来说应该足够了。此外,只有一个变量符号(X)。
在继续定义导数之前,有几个辅助构造我想定义。我们可能需要一个通用的匹配来处理二元运算符(Add
、Sub
、Mul
、Div
)和支持的函数(Exp
、Log
、Sin
、Cos
)。所以我们将定义所谓的活动模式,可以用来匹配这些构造。
let (|Op|_|) (x : Expression) =
match x with
| Add(e1, e2) -> Some(Add, e1, e2)
| Sub(e1, e2) -> Some(Sub, e1, e2)
| Mul(e1, e2) -> Some(Mul, e1, e2)
| Div(e1, e2) -> Some(Div, e1, e2)
| Pow(e1, e2) -> Some(Pow, e1, e2)
| _ -> None
let (|Func|_|) (x : Expression) =
match x with
| Exp(e) -> Some(Exp, e)
| Log(e) -> Some(Log, e)
| Sin(e) -> Some(Sin, e)
| Cos(e) -> Some(Cos, e)
| _ -> None
再次,我恐怕不能用这篇帖子来定义活动模式、“香蕉夹”(“(|”和“|)”)和选项语法(“Some”和“None”)的含义,网上有很多信息,但你可以将上面的定义看作类似于正则表达式匹配:表达式“Add(e1, e2)
”将被匹配为“Op
”(Add, e1, e2
),而表达式“Exp(e)
”将被匹配为“Func (Exp, e)
”。我们稍后会看到这有多么有用。
现在剩下的就是导数的定义。它是这样的:
let rec Derivative x : Expression =
match x with
| X -> Const(1.)
| Const(n) -> Const(0.)
| Neg(e) -> Neg(Derivative(e))
| Add(e1, e2) -> Add(Derivative(e1), Derivative(e2))
| Sub(e1, e2) -> Sub(Derivative(e1), Derivative(e2))
| Mul(e1, e2) -> Add(Mul(Derivative(e1), e2), Mul(e1, Derivative(e2)))
| Pow(e, Const(n)) -> Mul(Const(n), Pow(e, Const(n-1.)))
| Pow(Const(n), e) -> Mul(Mul(Log(Const(n)), Pow(Const(n), e)), Derivative(e))
| Exp(X) -> Exp(X)
| Log(X) -> Div(Const(1.), X)
| Sin(X) -> Cos(X)
| Cos(X) -> Neg(Sin(X))
| Div(Const(1.), e) -> Div(Derivative(e), Pow(e, Const(2.)))
| Func(g, f) ->
let dg = Derivative(g(X))
let df = Derivative(f)
match dg with
| Func(dgf, dge) -> Mul(dgf(f), df)
| Op (op, e1, e2) -> Mul(op(e1, e2), df)
| _ -> failwith(sprintf "Unable to match compound function [%A]" dg)
| _ -> failwith(sprintf "Unable to match expression [%A]" x)
正如你所见,它不是一个算法——它是一个关于导数是什么的描述。你也可以明白为什么我们需要引入“Op
”和“Func
”活动模式:它们用于声明复杂的函数导数求值。没有这些模式,我们就需要列出所有支持的运算符和函数。
为了测试它是如何工作的,我们可以打开一个 F# 交互窗口(FSI)并输入一个函数进行测试。
> let d = Derivative(Sin(X));;
val d : Expression = Cos X
> let d = Derivative(Exp(Pow(X,Const(2.))));;
val d : Expression = Mul (Exp (Pow (X,Const 2.0)),Mul (Const 2.0,X))
就这样!但是既然我们在进行符号计算,我们可以改进它的呈现部分。目前,如果没有尝试简化结果表达式,那么如果我们计算一个对应于“5 * x + 3
”的表达式的导数,我们将得到一个正确但看起来很傻的答案。
> let d = Derivative(Add(Mul(Const(5.), X), Const(3.)));;
val d : Expression = Add (Add (Mul (Const 0.0,X),Mul (Const 5.0,Const 1.0)),Const 0.0)
但是,如果 F# 中计算导数如此简单,那么编写一个简化代数表达式的函数应该不难。这将在下一节。
2. 简化代数表达式
所以现在我们想改进表达式的美观性,换句话说,将它们以最简单的可能形式呈现。为了实现这个目标,我们定义了一个递归函数 Simplify
,它将 Expression 类型的输入转换为同一类型的输出,并尝试将表达式重写成更短的形式。与 Derivative 函数一样,定义是一系列规则而不是顺序算法。
let rec Simplify x : Expression =
match x with
| Add(Const(n1), Const(n2)) -> Const(n1 + n2)
| Sub(Const(n1), Const(n2)) -> Const(n1 - n2)
| Mul(Const(n1), Const(n2)) -> Const(n1 * n2)
| Div(Const(n1), Const(n2)) -> Const(n1 / n2)
| Neg(Const(0.)) -> Const(0.)
| Neg(Neg(e)) -> e |> Simplify
| Add(e, Const(0.)) -> e |> Simplify
| Add(Const(0.), e) -> e |> Simplify
| Add(Const(n), e) -> Add(e, Const(n)) |> Simplify
| Add(e1, Neg(e2)) -> Sub(e1, e2) |> Simplify
| Add(Neg(e1), e2) -> Sub(e2, e1) |> Simplify
| Sub(e, Const(0.)) -> e |> Simplify
| Sub(Const(0.), e) -> Neg(e) |> Simplify
| Mul(e, Const(1.)) -> e |> Simplify
| Mul(Const(1.), e) -> e |> Simplify
| Mul(e, Const(0.)) -> Const(0.)
| Mul(Const(0.), e) -> Const(0.)
| Mul(e, Const(n)) -> Mul(Const(n), e) |> Simplify
| Mul(Div(Const(n), e1), e2) -> Mul(Const(n), Div(e2, e1)) |> Simplify
| Mul(e1, Div(Const(n), e2)) -> Mul(Const(n), Div(e1, e2)) |> Simplify
| Mul(Neg(e1), e2) -> Neg(Mul(e1, e2)) |> Simplify
| Mul(e1, Neg(e2)) -> Neg(Mul(e1, e2)) |> Simplify
| Div(Const(0.), e) -> Const(0.)
| Div(e, Const(1.)) -> e |> Simplify
| Div(Neg(e1), e2) -> Neg(Div(e1, e2)) |> Simplify
| Div(e1, Neg(e2)) -> Neg(Div(e1, e2)) |> Simplify
| Pow(Const(0.), e) -> Const(0.)
| Pow(Const(1.), e) -> Const(1.)
| Pow(e, Const(0.)) -> Const(1.)
| Pow(e, Const(1.)) -> e |> Simplify
| Op (op, e1, e2)
->
let e1s = Simplify e1
let e2s = Simplify e2
if e1s <> e1 || e2s <> e2 then
op(Simplify e1, Simplify e2) |> Simplify
else
op(e1, e2)
| _ -> x
规则集不完整。例如,没有规则可以将“2 * (3 + X)
”重写为“6 + 2*X
”,但这应该足以消除大多数明显的冗余,例如乘以一和加零。所以,如果我们能启动 F# 交互窗口,我们可以测试它的工作方式。
> let s = Simplify(Add(Mul(Const 0., X), Mul(Const 5., Const 1.)));;
val s : Expression = Const 5.0
现在我们可以做的是扩展上一篇文章中编写的 Derivative
函数,以便它可以利用我们新的 Simplify
函数。
let rec Derivative x : Expression =
let y =
match x with
| X -> Const(1.)
| Const(n) -> Const(0.)
| Neg(e) -> Neg(Derivative(e))
| Add(e1, e2) -> Add(Derivative(e1), Derivative(e2))
| Sub(e1, e2) -> Sub(Derivative(e1), Derivative(e2))
| Mul(e1, e2) -> Add(Mul(Derivative(e1), e2), Mul(e1, Derivative(e2)))
| Pow(e, Const(n)) -> Mul(Const(n), Pow(e, Const(n-1.)))
| Pow(Const(n), e) -> Mul(Mul(Log(Const(n)), Pow(Const(n), e)), Derivative(e))
| Exp(X) -> Exp(X)
| Log(X) -> Div(Const(1.), X)
| Sin(X) -> Cos(X)
| Cos(X) -> Neg(Sin(X))
| Div(Const(1.), e) -> Div(Derivative(e), Pow(e, Const(2.)))
| Func(g, f) ->
let dg = Derivative(g(X))
let df = Derivative(f)
match dg with
| Func(dgf, dge) -> Mul(dgf(f), df)
| Op (op, e1, e2) -> Mul(op(e1, e2), df)
| _ -> failwith(sprintf "Unable to match compound function [%A]" dg)
| _ -> failwith(sprintf "Unable to match expression [%A]" x)
Simplify y
现在让我们通过计算各种函数的 derivative
来测试一下。
> let d1 = Derivative(Add(Mul(Const(5.), X), Const(3.)))
let d2 = Derivative(Add(Pow(X, Const(3.)), Const(3.)))
let d3 = Derivative(Sin(Mul(Const(2.), X)))
let d4 = Derivative(Log(Mul(Const(2.), X)))
let d5 = Derivative(Exp(Mul(Const(2.), X)))
let d6 = Derivative(Exp(Pow(X, Const(2.))))
let d7 = Derivative(Log(Sin(X)))
let d8 = Derivative(Log(Cos(X)));;
>
val d1 : Expression = Const 5.0
val d2 : Expression = Mul (Const 3.0,Pow (X,Const 2.0))
val d3 : Expression = Mul (Const 2.0,Cos (Mul (Const 2.0,X)))
val d4 : Expression = Div (Const 2.0,X)
val d5 : Expression = Mul (Const 2.0,Exp (Mul (Const 2.0,X)))
val d6 : Expression = Mul (Exp (Pow (X,Const 2.0)),Mul (Const 2.0,X))
val d7 : Expression = Div (Cos X,X)
val d8 : Expression = Neg (Div (Sin X,X))
我们能改进什么吗?嗯,也许……既然它变得如此容易,为什么不设定最终目标:将输入和输出呈现为纯文本,而不是 Expression 项。这样我们就可以输入“log(cos(x))
”并得到“-sin(x)/x
”。我认为这将需要更多的工作来实现,但一旦我们完成,它将非常有趣。在下一节。
3. 格式化表达式
首先,我们定义几个辅助函数来格式化运算符和函数名称。
let OpName (e: Expression) : string =
match e with
| Add(e1, e2) -> "+"
| Sub(e1, e2) -> "-"
| Mul(e1, e2) -> "*"
| Div(e1, e2) -> "/"
| Pow(e1, e2) -> "^"
| _ -> failwith(sprintf "Unrecognized operator [%A]" e)
let FuncName (e: Expression) (a : string) : string =
match e with
| Exp(x) -> sprintf "e^(%s)" a
| Log(x) -> sprintf "log(%s)" a
| Sin(x) -> sprintf "sin(%s)" a
| Cos(x) -> sprintf "cos(%s)" a
| _ -> failwith(sprintf "Unrecognized function [%A]" e)
然后,表达式格式化程序的粗略实现只需要不多的代码行。
let rec FormatExpression (inner : Expression) : string =
match inner with
| X -> "x";
| Const(n) -> sprintf "%f" n
| Neg x -> sprintf "-%s" (FormatExpression(x))
| Op(op, e1, e2) -> "(" + FormatExpression(e1) + " " +
OpName(inner) + " " + FormatExpression(e2) + ")"
| Func(f, e) -> FuncName(inner) (FormatExpression(e))
这段代码只有一个问题:它总是用括号将代数运算包围起来,这只有在表达式包含在外部表达式中时才需要。这是一个冗余括号的例子。
FormatExpression(Mul(Mul(Const(2.), X), Const(3.)))
>
val it : string = “((2.000000 * x) * 3.000000)”
然而,修改原始代码并不复杂,这样它就不会用括号包围顶层表达式。
let FormatExpression x =
let rec FormatSubExpression (outer : Expression option,
inner : Expression) : string =
match inner with
| X -> "x"
| Const(n) -> sprintf "%f" n
| Neg x -> sprintf "-%s" (FormatSubExpression(Some(inner), x))
| Op(op, e1, e2) ->
let s = FormatSubExpression(Some(inner), e1) + " " +
OpName(inner) + " " + FormatSubExpression(Some(inner), e2)
match outer with
| None -> s
| _ -> "(" + s + ")"
| Func(f, e) -> FuncName(inner) (FormatSubExpression(None, e))
FormatSubExpression(None, x)
现在我们得到了漂亮的外观输出。
let t1 = FormatExpression(Mul(Const(2.), X))
let t2 = FormatExpression(Mul(Const(3.), Mul(Const(2.), X)))
let t3 = FormatExpression(Mul(Mul(Const(2.), X), Const(3.)))
let t4 = FormatExpression(Mul(Add(X, Const(2.)), Const(3.)))
let t5 = FormatExpression(Neg(Mul(Const(2.), X)))
let t6 = FormatExpression(Sin(X))
>
val t1 : string = “2.000000 * x“
val t2 : string = “3.000000 * (2.000000 * x)”
val t3 : string = “(2.000000 * x) * 3.000000”
val t4 : string = “(x + 2.000000) * 3.000000”
val t5 : string = “-(2.000000 * x)”
val t6 : string = “sin(x)”
4. 解析表达式
在对表达式格式化感到满意之后,我们现在可以继续进行表达式解析,这似乎是一项更具挑战性的任务。首先,我们需要一个分词器,它将输入字符串转换为一个标记列表——构成最终表达式的原子。这是一个简单的分词器。
let Tokenize (value : System.String) =
let value = value.Replace(" ", "")
let value = value.Replace("e^(", "e(")
let value = value.Replace("(", " ( ")
let value = value.Replace(")", " ) ")
let value = value.Replace("+", " + ")
let value = value.Replace("-", " - ")
let value = value.Replace("*", " * ")
let value = value.Replace("/", " / ")
let value = value.Replace("^", " ^ ")
value.Trim().Split([|' '|]) |> Seq.toList |> List.filter (fun e -> e.Length > 0)
> Tokenize "(x * 4) * sin(x) * (30 + 40)"
>
val it : string list = ["(";"x"; "*"; "4"; ")"; "*";
"sin"; "("; "x"; ")"; "*"; "("; "30"; "+"; "40"; ")"]
分词器包含一个特定于处理指数函数(e ^ x)的规则。与其他函数(log、sin、cos)不同,指数使用幂运算符表示法,因此为其添加适当的支持将使帖子系列的大部分内容专门用于此特定情况。所以我对指数的使用做了一个小小的限制:它的参数总是用括号括起来(所以输入字符串应该看起来像“e ^ (x)”,而不是“e ^ x”),并且在分词过程中,表达式被转换为类似于其他函数的表示法:e(x)。因此,在进行表达式解析时,我们不必以特殊方式处理指数函数。
下一步是消除括号并将标记分成组,每组代表一个简单的表达式构造。例如,“(2 + x) * (5 - x)
”表达式可以分成包含表达式“2 + x
”、“5 – x
”以及将它们绑定在一起的运算符“*
”的组。我们分几步实现这一点:首先为每个标记分配一个级别(每次遇到开括号就增加,遇到闭括号就减少),然后将具有相同级别的连续标记放在一个列表中。这是处理这些操作的代码以及一个使用示例。
let rec LevelTokens (lst : string list) (level : int) : (string * int) list =
match lst with
| [] -> []
| "(" :: tail -> LevelTokens tail (level+1)
| ")" :: tail -> LevelTokens tail (level-1)
| x :: tail when IsOperator(x) -> (x, level) :: LevelTokens tail level
| head :: tail -> (head, level) :: LevelTokens tail level
let GroupTokens (item : (string * int))
(acc : (string list * int) list) : (string list * int) list =
match acc, item with
| [], (s, l) -> [([s], l)]
| (s1, l1) :: tail, (s, l) when l = l1 -> (s :: s1, l) :: tail
| head :: tail, (s, l) -> ([s], l) :: head :: tail
let lst = “(x * 4) * sin(x) * (30 + 40)”
(Tokenize lst |> LevelTokens) 0
let items = List.foldBack GroupTokens
((Tokenize lst |> LevelTokens) 0) [] |> List.map(fun (x, y) -> x)
>
val lst : string = "(x * 4) * sin(x) * (30 + 40)"
val items : string list list = [["x"; "*"; "4"]; ["*"; "sin"];
["x"]; ["*"]; ["30"; "+"; "40"]]
我们还需要一些辅助函数:测试一个 string
是否代表一个运算符或函数,一对活动模式定义来匹配数字常量和变量,以及应用解析后的运算符或函数到它们所绑定的表达式的方法。
let IsOperator (x : string) =
match x with
| "+" | "-" | "*" | "/" | "^" -> true
| _ -> false
let IsFunction (x : string) =
match x with
| "e" | "log" | "sin" | "cos" -> true
| _ -> false
let (|ToVar|_|) s =
if s = "x" then
Some(X)
else
None
let ApplyOperator (op : string, e1 : Expression, e2 : Expression) : Expression =
match op with
| "+" -> Add(e1, e2)
| "-" -> Sub(e1, e2)
| "*" -> Mul(e1, e2)
| "/" -> Div(e1, e2)
| "^" -> Pow(e1, e2)
| _ -> failwith(sprintf "Unrecognized operator [%s]" op)
let ApplyFunction (func : string, e : Expression) : Expression =
match func with
| "e" -> Exp(e)
| "log" -> Log(e)
| "sin" -> Sin(e)
| "cos" -> Cos(e)
| _ -> failwith(sprintf "Unrecognized function [%s]" func)
let (|ToConst|_|) s =
let success, result = Double.TryParse(s)
if success then
Some(Const(result))
else
None
let ParseItem (s : string) : Expression =
match s with
| ToVar e -> e
| ToConst e -> e
有了支持性内容,这里是将文本输入转换为表达式树的代码。
let rec ParseExpression (s : string) : Expression =
let rec LevelTokens (lst : string list) (level : int) : (string * int) list =
match lst with
| [] -> []
| "(" :: tail -> LevelTokens tail (level+1)
| ")" :: tail -> LevelTokens tail (level-1)
| x :: tail when IsOperator(x) -> (x, level) :: LevelTokens tail level
| head :: tail -> (head, level) :: LevelTokens tail level
let GroupTokens (item : (string * int))
(acc : (string list * int) list) : (string list * int) list =
match acc, item with
| [], (s, l) -> [([s], l)]
| (s1, l1) :: tail, (s, l) when l = l1 -> (s :: s1, l) :: tail
| head :: tail, (s, l) -> ([s], l) :: head :: tail
let rec MergeTokensWithExpressions
(e : Expression, items : (string list) list) : Expression =
match items with
| [] -> e
| [[func]] when IsFunction(func) -> ApplyFunction(func, e)
| [op; x] :: tail when IsOperator(op) ->
MergeTokensWithExpressions(ApplyOperator(op, e, ParseItem(x)), tail)
| [x; op] :: tail when IsOperator(op) ->
MergeTokensWithExpressions(ApplyOperator(op, ParseItem(x), e), tail)
| (op::x::rest) :: tail when IsOperator(op) ->
MergeTokensWithExpressions(ApplyOperator(op, e,
ParseFlatExpression(x::rest)), tail)
| (x::op::y::rest) :: tail when IsOperator(op) ->
ApplyOperator(op, ParseItem(x), MergeTokensWithExpressions(e, (y::rest)::tail))
| _ -> failwith(sprintf "Unable to build expression from [%A]" items)
let rec MergeExpressions (e : Expression, items : string list) : Expression =
match items with
| [] -> e
| op :: x :: tail when IsOperator(op) ->
MergeExpressions(ApplyOperator(op, e, ParseItem(x)), tail)
| x :: op :: tail when IsOperator(op) ->
MergeExpressions(ApplyOperator(op, ParseItem(x), e), tail)
| _ -> failwith(sprintf "Unable to build expression from [%A]" items)
let ParseFlatExpression (tokens : string list) : Expression =
match tokens with
| [] -> failwith("Expression string is empty")
| "-" :: x :: tail -> MergeExpressions(Neg(ParseItem(x)), tail)
| x :: tail -> MergeExpressions(ParseItem(x), tail)
let rec ParseTokenGroups (lst : (string list) list) : Expression =
match lst with
| [ls] -> ParseFlatExpression(ls)
| ls :: [op] :: tail when IsOperator(op) ->
ApplyOperator(op, ParseFlatExpression(ls), ParseTokenGroups(tail))
| ls :: [op :: optail] when IsOperator(op) ->
MergeTokensWithExpressions(ParseFlatExpression(ls), [op :: optail])
| ls :: (op :: optail) :: tail when IsOperator(op) ->
ApplyOperator(op, ParseFlatExpression(ls),
MergeTokensWithExpressions(ParseTokenGroups(tail), [optail]))
| ls :: tail -> MergeTokensWithExpressions(ParseTokenGroups(tail), [ls])
let leveledTokens = (Tokenize s |> LevelTokens) 0
let tokenGroups =
List.foldBack GroupTokens leveledTokens [] |> List.map(fun (x, y) -> x)
ParseTokenGroups(tokenGroups)
现在只需要测试一下所有这些是如何工作的。
let f1 = ParseExpression "5*x + 3" |> Derivative |> FormatExpression
let f2 = ParseExpression "x^3 +3" |> Derivative |> FormatExpression
let f3 = ParseExpression "sin(2*x)" |> Derivative |> FormatExpression
let f4 = ParseExpression "log(2*x)" |> Derivative |> FormatExpression
let f5 = ParseExpression "e ^(2*x)" |> Derivative |> FormatExpression
let f6 = ParseExpression "e ^(x^2)" |> Derivative |> FormatExpression
let f7 = ParseExpression "log(sin(x))" |> Derivative |> FormatExpression
let f8 = ParseExpression "log(cos(x))" |> Derivative |> FormatExpression
let f9 = ParseExpression "1 / x" |> Derivative |> FormatExpression
let f10 = ParseExpression "2 ^ x" |> Derivative |> FormatExpression
>
val f1 : string = "5.000000"
val f2 : string = "3.000000 * (x ^ 2.000000)"
val f3 : string = "2.000000 * cos(2.000000 * x)"
val f4 : string = "2.000000 / x"
val f5 : string = "2.000000 * e^(2.000000 * x)"
val f6 : string = "e^(x ^ 2.000000) * (2.000000 * x)"
val f7 : string = "cos(x) / x"
val f8 : string = "-(sin(x) / x)"
val f9 : string = "1.000000 / (x ^ 2.000000)"
val f10 : string = "log(2.000000) * (2.000000 ^ x)"
这样我们就完成了:我们现在可以用纯文本输入数学表达式,并以纯文本形式获得符号导数计算的结果。一切都在 F# 中完成!
参考文献
- Chris Smith - Programming F#
- Tomas Petricek, Jon Skeet - Real World Functional Programming: With Examples in F# and C#
历史
- 2010 年 6 月 11 日:首次发布