我很好奇您为什么不考虑选择 LINQ。它似乎满足您的所有标准。注意我没有使用 Scala 的经验,所以我不能对此发表评论。
- 整个应用程序(到目前为止)都是用 C# 编写的,因此与 .NET 的轻松集成至关重要。
- 大量数据被读入 .NET-DataTables,然后需要进行评估和转换。结果应包含在 .NET 类型中(字典、集合、数组等...)。
在这里,LINQ > F# > Clojure-CLR。如果一切都已经在 C# 中,那么 LINQ 将是最容易集成的。Visual Studio 对智能感知和函数定义导航之类的支持在纯 C# 程序中似乎要好得多。从 C# 调用 Clojure 可能很可怕——理论上它应该可以正常工作,但在实践中,请准备好花费数周时间找出为什么事情没有按照您期望的方式工作。它真的被设计成顶级的东西;你从 Clojure 调用 C#,相反的方向在 Clojure-CLR 开发人员的优先级列表中并不高;有基本的支持,但你得到你得到的。
- 性能非常重要。目前我的算法搜索通常需要两秒钟(不包括 sql),这还可以,但应该改进。我们的服务器有 16 个处理器,因此欢迎并行处理。由于我们每秒收到一个搜索请求,并且当前实现是单线程的,因此处理器时间仍然可用。
LINQ ~= F# > Clojure。我在其他地方读到,对于大多数惯用编写的算法,LINQ 的性能可以证明比 F#略好,但它们已经足够接近了,这无关紧要。PLINQ 使并行性变得容易。Clojure-CLR 的启动时间非常慢,而且运行时开销也会减慢速度。
LINQ >= F# > Clojure。并不是说 F# 完全不成熟,但 Visual Studio 的支持有点落后,而且世界上基于 LINQ 的生产代码(以及更多堆栈溢出答案)比 F# 多得多。
阅读有关 F# 的文章让我感觉很复杂,因为它似乎希望能够做几乎所有事情,而对于给定的任务,我倾向于使用更“纯”的数学方法。但也许这对于 F# 也是可能的,我还没有意识到这一点。
没有一种语言像 Haskell 那样是纯纯的,但就编写非纯代码的难度而言,我将其列为:LINQ > Clojure > F# > Scala。LINQ 只能通过调用不纯的方法而变得不纯。Clojure 有 refs 和 atom,F# 任何东西都可以被指定为可变的,而 Scala(根据我的理解)实际上只是带有功能特性的 Java。
F# 和 Scala 的功能特性是对模式匹配的语言支持。在 C# 中,您需要某种继承层次结构或 b?x:y 运算符链以功能性地执行操作(或者如果您对非功能性方法感到满意,则使用 if/else),模式匹配在不同的情况下进行条件操作原始数据类型的变化更加简洁。这可能在您计算精确匹配、前缀匹配和模糊匹配排名时很有用,但是var alg = x.match == exact ? alg1 : x.match == prefix ? alg2 : alg3
C# 中的 ab?x:y 链在这种简单情况下会非常清晰——当匹配变得更加复杂时,语言集成模式匹配变得更加复杂有价值的。
有趣的是,我认为 F# 证明比 LINQ 更有用的工具包的一个方面不是查询,LINQ 本身的名称应该表明它可以处理,而是将搜索字符串解析为表达式树。 这是函数式语言和模式匹配真正擅长的一个领域,添加 FsLex 和 FsYacc 等工具可以让您抢占先机。
综上所述,我认为决定取决于你想去哪里。如果您只想清理搜索算法并完成它,我建议您使用 LINQ 方法。但是,如果您想为整个程序逐步采用更注重功能的风格(并且您的公司愿意为您投入的时间付费),那么不妨看看 F#选项。无论哪种方式,我都会先执行 LINQ 选项,因为这对您来说可能更直接,并且一旦您开始走这条路,就会帮助引导您的 F# 在功能上更具惯用性。
简单地说,这就是您想要的,只需填写您的 Near 和 Equal 提取器的函数,以及 GetRank 和 GetStrings 函数,然后使用下面的
static IEnumerable<Record> FetchRecords(this Tree tree) {
return tree.Op == "OR" ? tree.Args.SelectMany(FetchRecords).Distinct() :
tree.Op == "AND" ? tree.Args.Select(FetchRecords).Aggregate((intersect, current) => intersect.Intersect(current)) :
tree.Op == "NEAR" ? FetchValsNear(tree.Args[0].Op, tree.Args[1].Op) :
FetchValsEqual(tree.Op);
}
static IEnumerable<Record> FetchValsEqual(string s) {
throw new NotImplementedException();
}
static IEnumerable<Record> FetchValsNear(string s1, string s2) {
throw new NotImplementedException();
}
static IEnumerable<Tuple<Record, double, string[]>> OrderByRank(this IEnumerable<Record> vals) {
return from val in vals
let rank = GetRank(val)
orderby rank
let strings = GetStringsIn(val)
select Tuple.Create(val, rank, strings);
}
static string[] GetStringsIn(Record val) {
throw new NotImplementedException();
}
static double GetRank(Record val) {
throw new NotImplementedException();
}
class Tree {
public string Op;
public Tree[] Args;
}
struct Record {/*your record here--use struct so Distinct and Intersect above work naturally (or use class and override Equals)*/}
像这样:
foreach (var tuple in myTree.FetchRecords().AsParallel().OrderByRank().Take(30)) {
// add to datagrid or whatever
}
这为您提供了简单的并行性和惰性,因此该GetStringsIn
函数仅在您获取的记录上执行(在本例中为前 30 个)。(请注意,可以使用此处的任何示例AND
来简化选择器)。IntersectAll