Jam*_*ies 4 haskell interface strategy-pattern
在OO世界中,我有一个类(我们称之为"Suggestor"),它实现接近"策略模式"的东西,以在运行时提供不同的算法实现.作为学习Haskell的练习,我想重写一下.
实际的用例非常复杂,所以我将简单介绍一个简单的例子.
假设我有一个Suggester
列出规则列表的类,并将每个规则作为过滤器应用于数据库结果列表.
每个规则都有三个阶段"构建查询","后查询过滤器"和"记分器".我们最终会得到满足以下要求的界面
buildQuery :: Query -> Query
postQueryFilter :: [Record] -> [Record]
scorer :: [Record] -> [(Record, Int)]
Run Code Online (Sandbox Code Playgroud)
Suggestor需要获取与此接口匹配的规则列表 - 在运行时动态 - 然后按顺序执行它们.必须首先在所有规则中运行buildQuery(),然后是postQueryFilter,然后是scorer.(即我不能只将一个规则的函数组合成一个函数).
在斯卡拉我只是做
// No state, so a singleton `object` instead of a class is ok
object Rule1 extends Rule {
def buildQuery ...
def postQueryFilter ...
def scorer ...
}
object Rule2 extends Rule { .... }
Run Code Online (Sandbox Code Playgroud)
然后可以通过传递相关规则来初始化服务(在运行时根据用户输入定义).
val suggester = new Suggester( List(Rule1, Rule2, Rule3) );
Run Code Online (Sandbox Code Playgroud)
如果规则是单个函数,那么这很简单 - 只需传递一个函数列表.但是,由于每个规则实际上是三个函数,我需要以某种方式将它们组合在一起,所以我有多个实现会议接口.
我的第一个想法是类型类,但是这些似乎不能满足我的需求 - 他们期望一个类型变量,并强制我的每个方法必须使用它 - 他们不这样做.
No parameters for class `Rule`
Run Code Online (Sandbox Code Playgroud)
我的第二个想法是将每个人放在一个haskell模块中,但由于模块不是"First Class",我不能直接传递它们(当然它们不强制执行接口).
第三,我尝试创建一个记录类型来封装函数
data Rule = Rule { buildQuery :: Query -> Query, .... etc }
Run Code Online (Sandbox Code Playgroud)
然后为每个定义一个"规则"的实例.当它在每个模块中完成时,它封装得很好并且工作正常,但感觉就像一个黑客,我不确定这是否适合使用haskell中的记录?
tl; dr - 如何将一组函数封装在一起,以便我可以将它们作为匹配接口的实例传递,但实际上并不使用类型变量.
或者我是否完全从错误的心态出现?
在我看来,你的解决方案不是"黑客",而是OO语言中的"策略模式":只需要解决语言的限制,特别是在丢失,不安全或不方便的Lambda/Closures/Function的情况下指针等,所以你需要一种"包装"才能让它"易于消化".
A"策略" 是基本上是一个功能(可以是与附加了一些额外的数据).但是如果一个函数确实是该语言的第一类成员 - 就像在Haskell中一样,则不需要将它隐藏在对象库中.
只需Rule
像您一样生成一个类型
data Rule = Rule
{ buildQuery :: Query -> Query
, postQueryFilter :: [Record] -> [Record]
, scorer :: [Record] -> [(Record, Int)]
}
Run Code Online (Sandbox Code Playgroud)
并构建通用的应用程序方法-我假设存在此类通用对象,因为这些Rules
对象旨在独立于SQL结果运行
applyRule :: Rule -> Results -> Results
Run Code Online (Sandbox Code Playgroud)
最后,您可以在任意位置实现任意数量的规则:只需导入Rule
类型并创建适当的值即可。就像在OO设置中一样,没有先验的理由为每个不同的规则赋予自己的类型。
easyRule :: Rule
easyRule = Rule id id (\recs -> zip recs [1..])
upsideDownRule :: Rule
upsideDownRule = Rule reverse reverse (\recs -> zip recs [-1, -2..])
Run Code Online (Sandbox Code Playgroud)
然后,如果您有的列表,则Rule
可以按顺序应用它们
applyRules :: [Rule] -> Results -> Results
applyRules [] res = res
applyRules (r:rs) res = applyRules rs (applyRule r res)
Run Code Online (Sandbox Code Playgroud)
实际上只是foldr
变相
applyRules rs res = foldr applyRule res rs
foo :: Results -> Results
foo = applyRules [Some.Module.easyRule, Some.Other.Module.upsideDownRule]
Run Code Online (Sandbox Code Playgroud)