我用了最后几天深入挖掘Clojure和ClojureScript中的 clojure.spec 。
到目前为止,我觉得最有用的是,在依赖于某种格式的数据的公共函数中,使用规范作为:pre
和:post
中的警卫。
(defn person-name [person]
{:pre [(s/valid? ::person person)]
:post [(s/valid? string? %)]}
(str (::first-name person) " " (::last-name person)))
这种方法的问题是,我得到的java.lang.AssertionError: Assert failed: (s/valid? ::person person)
没有任何关于究竟没有达到规范的信息。
在:pre
或:post
警卫中,是否有人如何获得更好的错误消息?
我了解conform
和explain*
,但这对那些:pre
或:post
警卫没有帮助。
答案 0 :(得分:8)
在较新的alpha中,现在有s/assert
可用于断言输入或返回值与规范匹配。如果有效,则返回原始值。如果无效,则会在解释结果中引发断言错误。断言可以打开或关闭,甚至可以选择从编译的代码中省略,以产生0生产影响。
(s/def ::first-name string?)
(s/def ::last-name string?)
(s/def ::person (s/keys :req [::first-name ::last-name]))
(defn person-name [person]
(s/assert ::person person)
(s/assert string? (str (::first-name person) " " (::last-name person))))
(s/check-asserts true)
(person-name 10)
=> CompilerException clojure.lang.ExceptionInfo: Spec assertion failed
val: 10 fails predicate: map?
:clojure.spec/failure :assertion-failed
#:clojure.spec{:problems [{:path [], :pred map?, :val 10, :via [], :in []}], :failure :assertion-failed}
答案 1 :(得分:6)
我认为您的想法是使用curl -XPOST localhost:9200/sentimentresult/sentiment/_bulk -d '
{"index":{"_id":"1"}}
{"Positivity_score":55.17999999,"Negativity_score":35.12,"Average_Positivity":0.8621874999999999,"Average_Negativity":0.54875}
{"index":{"_id":"2"}}
{"Positivity_score":134.71999999999997,"Negativity_score":90.08000000000017,"Average_Positivity":0.8419999999999999, "Average_Negativity":0.5701265822784821}
{"index":{"_id":"3"}}
{"Positivity_score":44.800000000000004,"Negativity_score":28.799999999999986,"Average_Positivity":0.7344262295081968, "Average_Negativity":0.47999999999999976}
'
来验证函数输入和输出,而不是前后条件。
这篇博文的底部有一个很好的例子:http://gigasquidsoftware.com/blog/2016/05/29/one-fish-spec-fish/。快速摘要:您可以使用:args和:ret键定义一个函数的规范,包括输入和返回值(因此替换前置条件和后置条件),使用spec/instrument
,检测它,然后输出类似于使用spec/fdef
时无法满足规范。
从该链接派生的最小示例:
explain
这等同于放置一个前提条件,即函数有一个整数参数和一个后置条件,它返回一个字符串。除非你得到更多有用的错误,就像你正在寻找的那样。
官方指南中的更多详细信息:https://clojure.org/guides/spec ---请参阅标题“规范功能”。
答案 2 :(得分:2)
如果不考虑是否应使用前置和后置条件来验证函数参数,可以通过使用clojure.test/is
包装谓词来打印来自前置和后置条件的更清晰的消息,如答案中所建议的那样下面:
How can I get Clojure :pre & :post to report their failing value?
那么你的代码看起来像这样:
(ns pre-post-messages.core
(:require [clojure.spec :as s]
[clojure.test :as t]))
(defn person-name [person]
{:pre [(t/is (s/valid? ::person person))]
:post [(t/is (s/valid? string? %))]}
(str (::first-name person) " " (::last-name person)))
(def try-1
{:first-name "Anna Vissi"})
(def try-2
{::first-name "Anna"
::last-name "Vissi"
::email "Anna@Vissi.com"})
(s/def ::person (s/keys :req [::first-name ::last-name ::email]))
评估
pre-post-messages.core> (person-name try-2)
会产生
"Anna Vissi"
并评估
pre-post-messages.core> (person-name try-1)
会产生
FAIL in () (core.clj:6)
expected: (s/valid? :pre-post-messages.core/person person)
actual: (not (s/valid? :pre-post-messages.core/person {:first-name "Anna Vissi"}))
AssertionError Assert failed: (t/is (s/valid? :pre-post-messages.core/person person)) pre-post-messages.core/person-name (core.clj:5)
答案 3 :(得分:0)
当您不想使用s/assert
或无法启用s/check-assserts
时,这很有用。改善MicSokoli的答案:
:pre
只是在乎返回的值都是真实的,因此我们可以将返回值"Success!\n"
转换为true
(出于严格性考虑),而throw
则将错误说明和输入数据,以防输出不成功。
(defn validate [spec input]
(let [explanation (s/explain-str spec input)]
(if (= explanation "Success!\n")
true
(throw (ex-info explanation {:input input}))))
这可能是该版本的一种变体,但它将运行两次规范:
(defn validate [spec input]
(if (s/valid? spec input)
true
(throw (ex-info (s/explain spec input) {:input input}))))
用法:
(defn person-name [person]
{:pre [(validate ::person person)]}
(str (::first-name person) " " (::last-name person)))