From Common Lisp's perspective, the example given is just:
`(and (> divident 0.025)
(< payout-ratio 0.5)
(or (< pe-ratio 20)
(< price 130)))
You can walk that tree recursively as-is, calling appropriate operators and substituting correct values.Or, you can wrap it in a (lambda (divident payout-ratio pe-ratio price) ...) and pass to (compile ...), and get a piece of executable code. With perhaps a line or two of book-keeping (that can be added by your code, and not necessarily typed in by the user), you can make the rule declare which variables it cares about. So a full example:
(let* ((rule '(lambda (data)
(destructuring-bind (&key divident payout-ratio pe-ratio price &allow-other-keys) data
(and (> divident 0.025)
(< payout-ratio 0.5)
(or (< pe-ratio 20)
(< price 130))))))
(executable-rule (compile nil rule)))
(list (funcall executable-rule '(:divident 0.5 :payout-ratio 0.4 :pe-ratio 50 :price 10 :other-data 42))
(funcall executable-rule '(:divident 0.5 :payout-ratio 0.5 :pe-ratio 50 :price 10 :other-data 42))))
;; Result:
(T NIL)Which, for me, is 90% of the use-cases
I can certainly understand using something like this when your users are building rules, because you don't know what they want to build so you just provide the base implementations and the operators. But if engineers are implementing all the logic, it seems like the criticisms called out in Wikipedia regarding obfuscation and re-implementation of basic operators would apply.
https://en.wikipedia.org/wiki/Specification_pattern#Criticis...
Orm's support expression trees ( eg, nhibernate,...)
So the specifications pattern for my use-case require expression trees.
There are workarounds ofc, but translating specifications to Sql is not the desired use-case of the pattern.
Specifications make it easy to have a 100% equal business filter ( that translate it to Sql through the ORM) and making it testable.
- Specification.AssertAll(resultset, "not all elements are valid according to this specification");
( Using pseudocode )