I'm trying to use gen/fmap with two random alphanumeric strings. Then I concatenate them with "#" and append ".com". But I'm struggling with the syntax.
First attempt:
(gen/fmap str (gen/string-alphanumeric) "#" (gen/string-alphanumeric) ".com")
But gen/fmap only takes two arguments.
Second attempt, where I group the second part doesn't work either
(gen/fmap str ((gen/string-alphanumeric) "#" (gen/string-alphanumeric) ".com"))
EDIT: I have a partial solution. It generates an email address, but the part before and after the # are the same. Example: john#john.com
This is the partial solution
(def gen-full-string
(gen/such-that #(not= % "") gen/string-alphanumeric))
(gen/fmap #(str % "#" % ".com") gen-full-string)
I wrote gen-full-string because the empty string "" was crashing the code. Since I have parsing and plan to make validation functions, I didn't care about the empty string. I wanted to test core functionality not edge cases. Once I implement validation, I will probably remove gen-full-string. So the email generator would become (gen/fmap #(str % "#" % ".com") gen/string-alphanumeric)
Gary Fredericks has a library https://github.com/gfredericks/test.chuck that adds string regex generation to Spec. This allows you to use a regex that is as simple or as detailed as you want for email addresses.
When I run this script:
clojure -Sdeps '{:deps {org.clojure/test.check {:mvn/version "1.1.0"}}}' /dev/stdin <<EOF
(require '[clojure.test.check.generators :as gen])
(def gen-email
(gen/fmap (fn [[s1 s2]] (format "%s#%s.com" s1 s2))
(gen/tuple gen/string-alphanumeric
gen/string-alphanumeric)))
(run! prn (gen/sample gen-email))
EOF
I get this output:
"#.com"
"#.com"
"9p#VH.com"
"x1#Ws.com"
"23mF#93.com"
"b40#14.com"
"v0n#5Wskg.com"
"mNo#R85LuM.com"
"#.com"
"8Z84B9U0#f9QSJgM.com"
I like test.check, but it takes a lot of time to understand the details. I created a helper library you may like. An example:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[tupelo.gen :as tg]
[clojure.test.check.properties :as prop]
))
(dospec 9
(do
(newline)
(spy :sample)
(prop/for-all [w tg/word-alpha+]
(spyx w))))
(dospec 9
(do
(newline)
(spy :emails)
(prop/for-all [w1 tg/word-alpha+
w2 tg/word-alpha+]
(let [email-addr (str w1 \# w2 ".com")]
(spyx email-addr)
)
)))
with result:
-----------------------------------
Clojure 1.10.3 Java 15.0.2
-----------------------------------
Testing tst.demo.core
:spy--tst.demo.core--line-011 => :sample
w => "b"
w => "lb"
w => "k"
w => "Y"
w => "mMWC"
w => "TzD"
w => "Nq"
w => "wQzPrF"
w => "HqEM"
{:result true, :num-tests 9, :seed 1629153846012, :time-elapsed-ms 1, :test-var "dospec-line-8"}
:spy--tst.demo.core--line-018 => :emails
email-addr => "k#v.com"
email-addr => "M#uL.com"
email-addr => "N#a.com"
email-addr => "OUr#B.com"
email-addr => "O#v.com"
email-addr => "UtZ#ndXS.com"
email-addr => "qhtDt#YLfE.com"
email-addr => "mLw#pFjKJJq.com"
email-addr => "hJ#DkCSnpAG.com"
{:result true, :num-tests 9, :seed 1629153846014, :time-elapsed-ms 1, :test-var "dospec-line-15"}
More details here. The above example uses my favorite template project.
Related
I'm trying to convert a local-date into an instant with millis using java-time, but instant returns a timestamp without millis.
(def UTC (java-time/zone-id "UTC")
(defn local-date-to-instant [local-date]
(-> local-date
(.atStartOfDay UTC)
java-time/instant))
(local-date-to-instant (java-time/local-date))
=> #object[java.time.Instant 0xdb3a8c7 "2021-05-13T00:00:00Z"]
but
(java-time/instant)
=> #object[java.time.Instant 0x1d1c27c8 "2021-05-13T13:12:31.782Z"]
The service downstream expects a string in this format: yyyy-MM-ddTHH:mm:ss.SSSZ.
Create a DateTimeFormatter that prints an ISO instant with milliseconds (3 fractional digits) even if they are zeroes:
(ns steffan.overflow
(:require [java-time :as jt])
(:import (java.time.format DateTimeFormatterBuilder)))
(def iso-instant-ms-formatter
(-> (DateTimeFormatterBuilder.) (.appendInstant 3) .toFormatter))
Example of use:
(def today-inst (jt/truncate-to (jt/instant) :days))
(str today-inst) ; => "2021-05-13T00:00:00Z"
(jt/format iso-instant-ms-formatter today-inst) ; => "2021-05-13T00:00:00.000Z"
You need to use a DateTimeFormatter. Then you can write code like:
LocalDate date = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd");
String text = date.format(formatter);
In particular, look at these format pattern codes:
S fraction-of-second fraction 978
A milli-of-day number 1234
n nano-of-second number 987654321
I think the S code is the best one for your purposes. You'll have to experiment a bit as the docs don't have all the details.
Here is one example:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[schema.core :as s]
[tupelo.java-time :as tjt]
)
(:import
[java.time Instant LocalDate LocalDateTime ZonedDateTime]
[java.time.format DateTimeFormatter]
))
(dotest
(let [top-of-hour (tjt/trunc-to-hour (ZonedDateTime/now))
fmt (DateTimeFormatter/ofPattern "yyyy-MM-dd HH:mm:ss.SSS")
]
(spyx top-of-hour)
(spyx (.format top-of-hour fmt))
))
with result
-----------------------------------
Clojure 1.10.3 Java 15.0.2
-----------------------------------
Testing tst.demo.core
top-of-hour => #object[java.time.ZonedDateTime 0x9b64076 "2021-05-13T07:00-07:00[America/Los_Angeles]"]
(.format top-of-hour fmt) => "2021-05-13 07:00:00.000"
The above is based from this template project and the library tupelo.java-time.
LocalDate doesn't have a time component. .atStartOfDay puts zeroes in all time fields. A LocalDateTime can be converted to an Instant through .toInstant.
Could someone shed a light on the following behavior please?
Let's assume I have this namespace with a spec:
(ns user.specs
(:require [clojure.alpha.spec :as s]
[clojure.alpha.spec.gen :as gen]
[clojure.string :as str]))
# Non-blank string of 20 to 50 ascii chars.
(s/def ::text (s/with-gen
(s/and string? #(not (str/blank? %)))
#(gen/such-that
(complement str/blank?)
(gen/fmap
clojure.string/join
(gen/vector
(gen/char)
20 50)))))
Now I want to reuse this spec.
(in-ns 'user)
(require '[user.specs :as su])
=> nil
(def kws [::dir
::ns])
=> #'user/kws
(s/def ::dir ::su/text)
=> :user/dir
(s/def ::ns string?)
=> :user/ns
(s/register ::spec (s/schema* kws))
=> :user/spec
When exercising the last spec, I get an error:
(s/exercise ::spec)
Error printing return value (IllegalArgumentException) at clojure.core/-cache-protocol-fn (core_deftype.clj:583).
No implementation of method: :conform* of protocol: #'clojure.alpha.spec.protocols/Spec found for class: clojure.lang.Keyword
However, if I redef the ::dir spec usinig s/register and s/get-spec instead of s/def, no problem:
(s/register ::dir (s/get-spec ::su/text))
=> :user/dir
(s/exercise ::spec)
=>
([#:user{:dir "teôÆ>EüáéNj¬u}zþs²DÍ$", :ns ""}
#:user{:dir "teôÆ>EüáéNj¬u}zþs²DÍ$", :ns ""}]
[#:user{:dir ":éû,#Î|)Q«óCS\t´ÿ4ÚÝܺ»Ân5Zq", :ns ""}
#:user{:dir ":éû,#Î|)Q«óCS\t´ÿ4ÚÝܺ»Ân5Zq", :ns ""}]
... elided
I'm assuming, from the error message, that with s/def, spec resolves ::dir as the literal ::su/text keyword instead of the associated spec.
1) Why?
2) Is s/register + s/get-spec an appropriate solution?
I'm trying to reuse a "utility" spec in a few places under domain specific names.
FWIW, I'm using spec-alpha2 in order to build specs dynamically and benefit from schema + select.
Aliasing specs like (s/def ::dir ::su/text) is not currently working in spec 2, which is still a work in progress.
Does Clojure/Script offer a way to build a destructured map out of the arguments plus filled-in defaults in case the keys weren't supplied in the call?
Consider this example (that doesn't quite do what the code implies by a quick glance). Does clojure provide a way to build the map prompt with these four keys and values either from the caller or the defaults. I hate to think I have to repeat these key names two more times to get what I am after.
(re-frame/reg-event-db
:open-prompt
(fn [db [_ {title :title
text :text
label-yes :label-yes
label-no :label-no
:or {title "Confirm"
text "Are you sure?"
label-yes "Ok"
label-no "Cancel"}
:as prompt}]]
(-> db
(update :state conj :modal-prompt)
(assoc :prompt prompt))))
After reviewing the official documentation page about destructuring, I don't think that Clojure proposes a more convient way of doing that.
But just by curiosity, I was wondering what is the code generated by destructuring, because I'm expecting it relies on macro stuff. Let consider this toy example:
(def my-map {:text "Some text"})
(let
[{title :title
:or {title "Confirm"}
:as prompt} my-map]
(str "I got " title " from " prompt))
;; => "I got Confirm from {:text \"Some text\"}"
(macroexpand '(let
[{title :title
:or {title "Confirm"}
:as prompt} my-map]
(str "I got " title " from " prompt)))
;; => (let*
;; [map__12555
;; my-map
;; map__12555
;; (if
;; (clojure.core/seq? map__12555)
;; (clojure.lang.PersistentHashMap/create
;; (clojure.core/seq map__12555))
;; map__12555)
;; prompt
;; map__12555
;; title
;; (clojure.core/get map__12555 :title "Confirm")]
;; (str "I got " title " from " prompt))
So as you can see, after a macro expansion, the :or mechanism which allows to specifies default value relies on clojure.core/get.
In this particular example, title is affected by (clojure.core/get map__12555 :title "Confirm") form. It's a way to avoid repeating the title variable, but does it worth it?
You can also check the source code of the destructuring macro to get full details about it, but personally I found it pretty difficult to handle ^^'.
it is doable, maybe not very practical though, but nice for self education:
let's begin with making up the function what would be special binding case.
let's say, we want to pass vectors of length 2 or 3, where vector of 2 will represent the simple binding map key-value pair like [:as abc] or [a :a], and the vector of size 3 would be k-v-default triple: [a :a "my default"]. The example of it's usage:
(bindings-preproc [['a 1 "asd"]
['b 2 "ddd"]
[:or {'x 10}]
[:as 'whole]])
resulting to
{a 1, b 2, :or {x 10, a "asd", b "ddd"}, :as whole}
this function could look like this:
(defn bindings-preproc [decls]
(let [defaults (into {} (keep (fn [decl]
(when (and (not (keyword? (first decl)))
(= 3 (count decl)))
(let [[nm _ default] decl]
[nm default])))
decls))
all-kvs (apply assoc {} (mapcat (partial take 2) decls))]
(update all-kvs :or merge defaults)))
(this one doesn't include error checks for the sake of illustrative simplicity)
The next thing is to employ it inside the binding macros. The idea to make bindings-preproc a macro fails, because binding forms are checked for validity before the inner macros are evaluated.
But still we have a feature, that would help, namely reader tags. They are used for example when you use #inst syntax. Since these reader tags are processed at read-time, before any macros are getting expanded, we can plug our preprocessor in.
(here i will use actual reference update, to demonstrate it from repl, but in real projects you would declare these tags in a special file)
user> (alter-var-root
#'default-data-readers
assoc 'my/reader #'user/bindings-preproc)
;;=> {uuid #'clojure.uuid/default-uuid-reader,
;; inst #'clojure.instant/read-instant-date,
;; my/reader #'user/bindings-preproc}
so, now we can try to make it work:
(defn f [#my/reader [[a :a 10]
[b :b 20]
[z :z]
[:keys [k1 k2 k3]]
[[c1 c2 & cs] :c]
[:or {z 101
k3 :wooo}]
[:as whole]]]
{:a a :b b :c1 c1 :c2 c2 :cs cs :z z :k1 k1 :k2 k2 :k3 k3 :whole whole})
user> (f {:a 1000 :c [:one]})
;;=> {:cs nil,
;; :c2 nil,
;; :z 101,
;; :c1 :one,
;; :k3 :wooo,
;; :b 20,
;; :whole {:a 1000, :c [:one]},
;; :k1 nil,
;; :k2 nil,
;; :a 1000}
user> (let [a 10
b 20
#my/reader [[x :x 1]
[y :y 2]
[z :z 100]] {:z 432}]
[a b x y z])
;;=> [10 20 1 2 432]
I like to make a map of all default values, then use into or similar to fuse the user-supplied values into the map of default values. For example:
(ns tst.demo.core
(:use tupelo.core tupelo.test) )
(def stuff-default {:a 1 :b 2})
(defn apply-defaults
[arg]
(let [stuff (glue stuff-default arg)] ; or use `into`. Last one wins, so put defaults first
(with-map-vals stuff [a b]
(newline)
(spyx a)
(spyx b))
stuff))
(dotest
(is= (apply-defaults {}) ; no inputs => all default values
{:a 1, :b 2})
(is= (apply-defaults {:a 100}) ; some inputs => partial defaults
{:a 100, :b 2})
(is= (apply-defaults {:a 100, :b 200}) ; all inputs => no defaults used
{:a 100, :b 200}))
Here glue is like into but with more error checking. We also use tupelo.core/with-map-vals to destruct the map, with less repetition than native Clojure destructuring (vals->map does the reverse).
The output is:
-------------------------------
Clojure 1.10.1 Java 14
-------------------------------
a => 1
b => 2
a => 100
b => 2
a => 100
b => 200
Ran 2 tests containing 3 assertions.
0 failures, 0 errors.
I'm trying to destructure an instant and get year, month, day.
I've tried with java-time/as function without success.
(ns myproject.time-test
(:require [java-time :as jt])
(:gen-class))
(def curr-time (jt/instant (System/currentTimeMillis)))
(jt/as curr-time :year)
Can anyone point me in the right direction?
I would do it without the .. to make it clear you are using Java interop (it appears that clojure.java-time has no wrapper to convert from an Instant to a ZonedDateTime:
(-> (jt/instant)
(.atZone (ZoneId/systemDefault)) ; => java ZonedDateTime obj
(.getYear))
=> 2018
There are other ways which may be useful:
(jt/zoned-date-time) => #object[java.time.ZonedDateTime 0x2585437a
"2018-07-19T11:42:37.093731-07:00[America/Los_Angeles]"]
(jt/year (jt/zoned-date-time)) => #object[java.time.Year 0x74694f06 "2018"]
(jt/year) => #object[java.time.Year 0x16c69c47 "2018"]
and also
(jt/as (jt/zoned-date-time) :year :month-of-year :day-of-month) => (2018 7 19)
Another way to convert an Instant to a ZonedDateTime:
(let [zdt (ZonedDateTime/ofInstant (jt/instant) (ZoneId/systemDefault))]
(.getYear zdt) => 2018
(.getMonth zdt) => #object[java.time.Month 0x403d9a5b "JULY"]
(.getDayOfMonth zdt) => 19
(ns mastering.stackoverflow
(:import
(java.time ZoneId)))
(.. (jt/instant)
(atZone (ZoneId/systemDefault))
(getYear))
Other methods like getMonthValue, getMinute are also available.
You could do "extraction" that way:
(let [i (.. (jt/instant)
(atZone (ZoneId/systemDefault)))
extract (juxt (memfn getYear) (memfn getMinute))]
(extract i))
; => [2018 37]
Take the following program as an example:
(defn echo-ints []
(doseq [i (->> (BufferedReader. *in*)
(line-seq)
(map read-string)
(take-while integer?))]
(println i)))
The idea is to prompt the user for input and then echo it back if it's an integer. However, in this particular program almost every second input won't be echoed immediately. Instead the program will wait for additional input before processing two inputs at once.
Presumably this a consequence of some performance tweaks happening behind the scenes. However in this instance I'd really like to have an immediate feedback loop. Is there an easy way to accomplish this, or does the logic of the program have to be significantly altered?
(The main motivation here is to pass the infinite sequence of user inputs to another function f that transforms lazy sequences to other lazy sequences. If I wrote some kind of while-loop, I wouldn't be able to use f.)
It is generally not good to mix lazyness with side-effect (printing in this case), since most sequence functions have built-in optimizations that cause unintended effects while still being functionally correct.
Here's a good write up: https://stuartsierra.com/2015/08/25/clojure-donts-lazy-effects
What you are trying to do seems like a good fit for core.async channels. I would think as the problem as 'a stream of user input' instead of 'infinite sequence of user inputs', and 'f transforms lazy sequences to lazy sequences' becomes 'f transform a stream into another stream'. This will allow you to write f as transducers which you can arbitrarily compose.
I would do it like the following. Note we use spyx and spyxx from the Tupelo library to display some results.
First, write a simple version with canned test data:
(ns tst.demo.core
(:use tupelo.test)
(:require
[tupelo.core :as t] )
(:import [java.io BufferedReader StringReader]))
(t/refer-tupelo)
(def user-input
"hello
there
and
a
1
and-a
2
and
a
3.14159
and-a
4
bye" )
(defn echo-ints
[str]
(let [lines (line-seq (BufferedReader. (StringReader. str)))
data (map read-string lines)
nums (filter integer? data) ]
(doseq [it data]
(spyxx it))
(spyx nums)))
(newline)
(echo-ints user-input)
This gives us the results:
it => <#clojure.lang.Symbol hello>
it => <#clojure.lang.Symbol there>
it => <#clojure.lang.Symbol and>
it => <#clojure.lang.Symbol a>
it => <#java.lang.Long 1>
it => <#clojure.lang.Symbol and-a>
it => <#java.lang.Long 2>
it => <#clojure.lang.Symbol and>
it => <#clojure.lang.Symbol a>
it => <#java.lang.Double 3.14159>
it => <#clojure.lang.Symbol and-a>
it => <#java.lang.Long 4>
it => <#clojure.lang.Symbol bye>
nums => (1 2 4)
So, we see that it works and gives us the numbers we want.
Next, write a looping version. We make it terminate gracefully when our test data runs out.
(defn echo-ints-loop
[str]
(loop [lines (line-seq (BufferedReader. (StringReader. str)))]
(let [line (first lines)
remaining (rest lines)
data (read-string line)]
(when (integer? data)
(println "found:" data))
(when (not-empty? remaining)
(recur remaining)))))
(newline)
(echo-ints-loop user-input)
found: 1
found: 2
found: 4
Next, we write an infinite loop to read the keyboard. You need to terminate this one with CRTL-C at the keyboard:
(ns demo.core
(:require [tupelo.core :as t])
(:import [java.io BufferedReader StringReader]))
(t/refer-tupelo)
(defn echo-ints-inf
[]
(loop [lines (line-seq (BufferedReader. *in*))]
(let [line (first lines)
remaining (rest lines)
data (read-string line)]
(when (integer? data)
(println "found:" data))
(when (not-empty? remaining)
(recur remaining)))))
(defn -main []
(println "main - enter")
(newline)
(echo-ints-inf))
And we run it manually:
~/clj > lein run
main - enter
hello
there
1
found: 1
and
a
2
found: 2
and-a
3
found: 3
further more
4
found: 4
^C
~/clj >
~/clj >