2014-02-10 2 views
3

В мире OO у меня есть класс (назовем его «Suggestor»), который реализует что-то, приближающееся к «Шагу стратегии», чтобы обеспечить различные реализации алгоритма во время выполнения. Как упражнение в обучении Haskell, я хочу переписать это.«Шаблон стратегии» в Haskell

Фактический прецедент довольно сложный, поэтому я рассмотрю более простой пример.

Предположим, у меня есть класс Suggester, который принимает список правил и применяет каждое правило в качестве фильтра к списку результатов базы данных.

Каждое правило состоит из трех этапов: «Build Query», «Post Query Filter» и «Scorer». Мы по существу в конечном итоге с интерфейсом встречи следующие

buildQuery :: Query -> Query 
postQueryFilter :: [Record] -> [Record] 
scorer :: [Record] -> [(Record, Int)] 

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 { .... } 

И тогда можно инициализировать службу, передав соответствующие правила через (Defined во время выполнения на основе пользовательского ввода).

val suggester = new Suggester(List(Rule1, Rule2, Rule3)); 

Если правила были единой функцией, это было бы просто - просто передайте список функций. Однако, поскольку каждое правило на самом деле является тремя функциями, мне нужно как-то сгруппировать их, поэтому у меня есть несколько реализаций, соответствующих интерфейсу.

Моей первой мыслью были классы классов, однако они, похоже, не соответствуют моим потребностям - они ожидают переменную типа и обеспечивают, чтобы каждый из моих методов использовал ее, а это не так.

No parameters for class `Rule` 

Моя вторая мысль была просто поместить каждый из них в модуле Haskell, но модули не являются «Первый класс» Я не могу передать их вокруг непосредственно (и они, конечно, не применять интерфейс).

В-третьих, я попытался создать тип записи, чтобы инкапсулировать функции

data Rule = Rule { buildQuery :: Query -> Query, .... etc } 

А затем определяется экземпляр «Правило» для каждого. Когда это делается в каждом модуле, он инкапсулирует красиво и отлично работает, но чувствует себя взломанным, и я не уверен, что это подходящее использование записей в haskell?

tl; dr - Как я могу инкапсулировать группу функций, чтобы я мог передавать их как экземпляр чего-то, соответствующего интерфейсу, но на самом деле не использую переменную типа.

Или я полностью исхожу из-за неправильного мышления?

+7

Каким образом делает это чувство как взломать? Я думаю, что это вполне приемлемый способ использования записи. – YellPika

+0

Записи Haskell выглядят так, будто они предназначены для хранения данных, а не функций. Является ли приемлемым идиоматический Haskell обходить функции как часть записи? Я, возможно, ослеплен сходством между записями и классами дел и слишком большим весом для идеи OO, что для этого есть конкретная конструкция. –

+6

Функции - это данные. I.e., они являются первоклассными данными. Они могут быть переданы, возвращены и сохранены. Когда это больше не кажется странным, вы будете хорошо на пути к просветлению ОО. Функции OO обычно не являются первоклассными данными, поэтому мы переносим функции в объекты, как в шаблонах Command и Strategy. –

ответ

4

Просто генерировать один Rule типа, как вы сделали

data Rule = Rule 
    { buildQuery :: Query -> Query 
    , postQueryFilter :: [Record] -> [Record] 
    , scorer :: [Record] -> [(Record, Int)] 
    } 

И построить общее применение метода-я предполагая, что такую ​​универсальную вещь существует, учитывая, что эти Rules предназначены для работы независимо друг от друга более чем SQL результатов

applyRule :: Rule -> Results -> Results 

Наконец, вы можете реализовать столько правил, сколько захотите, просто импортируйте тип Rule и создайте соответствующее значение. Нет априорной причины, чтобы каждый из этих правил использовал свой собственный тип, как вы могли бы в настройке OO.

easyRule :: Rule 
easyRule = Rule id id (\recs -> zip recs [1..]) 

upsideDownRule :: Rule 
upsideDownRule = Rule reverse reverse (\recs -> zip recs [-1, -2..]) 

Тогда, если у вас есть список Rule с вы можете применить их в порядке

applyRules :: [Rule] -> Results -> Results 
applyRules []  res = res 
applyRules (r:rs) res = applyRules rs (applyRule r res) 

, который на самом деле просто foldr в маскировке

applyRules rs res = foldr applyRule res rs 

foo :: Results -> Results 
foo = applyRules [Some.Module.easyRule, Some.Other.Module.upsideDownRule] 
+1

Кроме того, всякий раз, когда вы размышляете о шаблонах проектирования OO в Haskell, пожалуйста, посмотрите [на этом посту от E.Z. Ян] (http://blog.ezyang.com/2010/05/design-patterns-in-haskel/). –

8

На мой взгляд, ваше решение - это не «взломать», а «шаблон стратегии» на языках OO: это необходимо только для того, чтобы обойти ограничения языка, особенно в случае отсутствия, небезопасного или неудобного Lambdas/Closures/Function Указатели и т. Д., Поэтому вам нужна своего рода «обертка», чтобы сделать ее «удобоваримой» для этого языка.

«Стратегия» - это в основном функция (возможно, с некоторыми дополнительными данными). Но если функция действительно является первоклассным членом языка - как в Haskell, нет необходимости скрывать ее в шкафу объекта.

+4

Точно. Следующие переводы могут быть полезны при изучении FP путем не-обучения OO: Composite pattern = тип алгебраических данных; Шаблон посетителя = fold (r), шаблон итератора = лень. – d8d0d65b3f7cf42

+0

Спасибо. Конечный результат на удивление все еще в значительной степени идентичен «стратегии» в мире ОО. Множество функций, соответствующих интерфейсу, содержащемуся в структуре данных. Независимо от того, является ли это записью, структурой или классом, дизайн заканчивается очень похожим (до тех пор, пока он не является апатридом и чист в первую очередь). Роза с любым другим именем .... Конечно, если бы это была упаковка одной функции, тогда структура не понадобилась бы, поскольку она могла быть передана напрямую, но это то же самое во многих языках OO. Мне нужно отучиться намного меньше, чем я думал (до сих пор). –

+0

Это верно только для стратегий с одним методом. – leventov

Смежные вопросы