This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-07-04
Channels
- # announcements (9)
- # bangalore-clj (1)
- # beginners (164)
- # calva (7)
- # clj-kondo (12)
- # cljs-dev (5)
- # cljsrn (7)
- # clojure (100)
- # clojure-spec (5)
- # clojure-sweden (2)
- # clojure-uk (4)
- # clojurescript (9)
- # conjure (22)
- # datomic (53)
- # fulcro (62)
- # graalvm (27)
- # helix (10)
- # joker (6)
- # malli (1)
- # mount (4)
- # nrepl (3)
- # off-topic (8)
- # quil (1)
- # releases (1)
- # sci (42)
- # shadow-cljs (1)
- # testing (7)
- # tools-deps (26)
- # vim (24)
Hi! I’d like to build a small command line tool with clojure, that takes some user input and generates a webpage from it. I made this initially with clojure using (read-line) for the input. I am now trying it as a clojurescript project, but am getting errors that read-line is not defined. Is read-line not a function with clojurescript, or is there an issue with my project setup?
how are you running the js? node?
and what made you switch from clojure to clojurescript?
if the startup time is an issue, you can try babaska, https://github.com/borkdude/babashka
Mostly to keep the startup time low. I’m new to clojure, and wanted to try to keep the amount of time i spent on build tooling to a minimum, as it was hurting my momentum.
so i started with a ‘lein new app’ app, and it worked welll enough, but calling it (even as an uberjar) took about 20 seconds.
the app is intended to be a note taker of sorts, where I can call it, it’ll ask a couple of inputs that I type in quickly, and then keep on with my day.
I would definitely check out https://github.com/borkdude/babashka in that case
I love what i’ve used of babashka so far, but correct me if i’m wrong…you can’t really use dependencies with it?
I'm actually not sure
clojurescript is also pretty reasonable
Just for full context: it’d be a front page for a website that had that day’s sunrise/sunset and moonphase along with observations i’d made that day and my own comment on the weather.
So I’d want a way to write and save these observations, plus something that took these observations, plus a weather api, created a page, and replaced it as the new index.html in my website folder
pretty neat
thanks! I’m excited for it, and it seems scoped well enough as a learner project…while providing good use.
it seems like cljs doesn't have read-line since read-line would depend on the platform, but it would definitely be possible to do with js interop to the node js utilities
ah good point, and thank you for the link! Do you know if callbacks are handled in a special way with clojurescript?
it seems like most of the node ways of getting input return a callback, but wasn’t quite sure how to transpose that structure to clojure just yet.
there's clojure.core.async, but there's a bit of a learning curve on that
Ah, hmmm. Perhaps it might be better to check out the build tooling a bit more--so I can keep the working code, but try to just reduce the start up time.
I’ve heard GraalVM made things faster, but wasn’t sure if it was a rabbit hole to get it set up
Or….just have two projects…one that builds the site (it’d be done as a cron job, and i wouldn’t notice the startup time)…and a babashka script that takes some inputs and writes it to the right file.
I've tried GraalVM and it really is some cool tech. I've found that it can be pretty hit or miss depending on what libraries you're using
depending on the library, it can definitely be a rabbit hole
Babashka includes Cheshire by default, and I'm almost certain you can do hiccup in some way. I would recommend it over clojure script if only because it's so much simpler to use.
There's also bootleg https://github.com/retrogradeorbit/bootleg
oh, awesome. Thank you, @U7RJTCH6J and @U7S5E44DB!
More complete babashka-compatible hiccup: https://github.com/lambdaisland/open-source/blob/2cfde3dfb460e72f047bf94e6f5ec7f519c6d7a0/src/lioss/hiccup.clj
Hi, can someone image a reason to write a function like this #(= % %)
instead of something like (fn [x] true)
?
You can make use https://clojuredocs.org/clojure.core/constantly as well. One potential use case is using it as a placeholder argument to call a function that expects a “single arity function” as parameter
Thank you! I figured that just wanted to make sure I didn't miss something important 😋
there's at least one argument that is false
for #(= % %)
but true for the other alternatives offered: NaN
this could be a badly constructed NaN check
I am at a loss of what is happening once again. I am trying try Datomic, spent the whole week, still can't do anything. The aws setup part works, if I set the credentials explicitly even stuff like datomic client access <system>
works, I have it running right now, it's connected.
What I can't do is anything in the client.
For any call I get back
Error building classpath. Could not find artifact com.datomic:ion:jar:0.9.43 in central (
I tried to to set that datomic-cloud repository at least 15 different ways, I have no idea why it tries to look it up in the central : (
If you want to ask in #datomic with a little more detail on your deps.edn and aws cred setup, we can help figure it out
Hey team, noob editor question. I have been a long-time user of cursive, and love it. However, I decided recently to learn one of the l33t editors -- vim or emacs. (coding for ~8 yrs professionally...and never really gave em a true shot) I am leaning towards vim, but I do know that clj is best on emacs. Would love to hear thoughts: • For people who have tried vim and cursive, is the vim support for clj just as good as cursive? (paredit, send form to repl, refactor, search, etc) • For people who have tried vim and emacs, how do they compare for clj?
Here's a link: https://github.com/Olical/conjure
if you want to learn vim, there is also plugin for IntelliJ
Gotcha! Indeed I may end up moving back to cursive, but am thinking I should try pure vim, to get used to the key bindings
but vim is mostly made for working with lines not forms, so the vim bindings are not great for editing clojure
this is false, vi is made for text objects, and parenthesized forms are text objects, even without any vim or plugin features
ya(
- copies the innermost list, yaF
copies the outermost form, d%
deletes from the point to the paren match, etc. etc. etc.
I know that these exists but overall specialized paredit will be better. At least I never learned vim enough to match the speed I have in cursive. And I suspect most vim users work with lines or words mainly. Similarly as most Unix utilities works with lines a for instance diff (and so git) is terrible for diffing changes in Clojure code.
having used vim text objects and paredit I disagree. I used emacs for over a decade, and for the first five years of my clojure career.
use the editor that works best for you, but please don't make false claims about others
I am disagreeing with the sentiment that you need to use vim or emacs to be good programmer or to code Clojure. It doesn't offer as much as people think it does, aside cryptic controls.
btw. if you know how to setup git to make good diffing of Clojure code (form-based, not line based), I would be intersted
you can get custom bindings for Clojure, but then it is no longer vim in my opinion 🙂 but sure, it might work
It's true lots of vims operations work on lines, but text objects are very well suited for lisp editing.
Vim (or maybe vi) has some other uses which may be compelling if you do much with remote servers (I use Emacs, but sometimes wish I didn't have to look up vi commands when I do need it)
Conjure's form selection is actually written in Lua (Fennel - a lisp - compiled to Lua) which relies on Neovim's findpairpos, it'll eventually be replaced by digging through the AST in Neovim's memory as and when Neovim has pervasive tree-sitter support. So it's not even text objects 😅 it just behaves kind of like it. (sorry, this is pretty off topic for questions about getting into Clojure!)
Can someone explain why #(= %
sym)` works like expected inside a macro but this sym))` doesn't? (Call to clojure.core/fn did not conform to spec.)(fn pred-const [x] (= x
And is there a resource that can help me to understand the error messages better:innocent:? So that I can better help myself 🙈
fn takes an unqualified symbol as function name
So use ~’pred-const
This is a classic macro issue
What is the text of the error message you were getting?
Unhandled clojure.lang.Compiler$CompilerException
Error compiling src/polymatheia/query.clj at (23:4)
#:clojure.error{:phase :macro-syntax-check,
:line 23,
:column 4,
:source
"/home/exa/workspace/polymatheia/src/polymatheia/query.clj",
:symbol clojure.core/fn}
Compiler.java: 6972 clojure.lang.Compiler/checkSpecs
Compiler.java: 6988 clojure.lang.Compiler/macroexpand1
Compiler.java: 7093 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6789 clojure.lang.Compiler/analyze
Compiler.java: 7095 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6789 clojure.lang.Compiler/analyze
Compiler.java: 6745 clojure.lang.Compiler/analyze
Compiler.java: 3820 clojure.lang.Compiler$InvokeExpr/parse
Compiler.java: 7109 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6789 clojure.lang.Compiler/analyze
Compiler.java: 6745 clojure.lang.Compiler/analyze
Compiler.java: 6120 clojure.lang.Compiler$BodyExpr$Parser/parse
Compiler.java: 5467 clojure.lang.Compiler$FnMethod/parse
Compiler.java: 4029 clojure.lang.Compiler$FnExpr/parse
Compiler.java: 7105 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6789 clojure.lang.Compiler/analyze
Compiler.java: 7174 clojure.lang.Compiler/eval
Compiler.java: 7132 clojure.lang.Compiler/eval
core.clj: 3214 clojure.core/eval
core.clj: 3210 clojure.core/eval
interruptible_eval.clj: 91 nrepl.middleware.interruptible-eval/evaluate/fn
main.clj: 437 clojure.main/repl/read-eval-print/fn
main.clj: 437 clojure.main/repl/read-eval-print
main.clj: 458 clojure.main/repl/fn
main.clj: 458 clojure.main/repl
main.clj: 368 clojure.main/repl
RestFn.java: 137 clojure.lang.RestFn/applyTo
core.clj: 665 clojure.core/apply
core.clj: 660 clojure.core/apply
regrow.clj: 20 refactor-nrepl.ns.slam.hound.regrow/wrap-clojure-repl/fn
RestFn.java: 1523 clojure.lang.RestFn/invoke
interruptible_eval.clj: 84 nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 56 nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 155 nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
AFn.java: 22 clojure.lang.AFn/run
session.clj: 190 nrepl.middleware.session/session-exec/main-loop/fn
session.clj: 189 nrepl.middleware.session/session-exec/main-loop
AFn.java: 22 clojure.lang.AFn/run
Thread.java: 832 java.lang.Thread/run
1. Caused by clojure.lang.ExceptionInfo
Call to clojure.core/fn did not conform to spec.
#:clojure.spec.alpha{:problems
({:path [:fn-tail :arity-1 :params],
:pred clojure.core/vector?,
:val polymatheia.query/pred-const,
:via
[:clojure.core.specs.alpha/params+body
:clojure.core.specs.alpha/param-list
:clojure.core.specs.alpha/param-list],
:in [0]}
{:path [:fn-tail :arity-n],
:pred
(clojure.core/fn
[%]
(clojure.core/or
(clojure.core/nil? %)
(clojure.core/sequential? %))),
:val polymatheia.query/pred-const,
:via
[:clojure.core.specs.alpha/params+body
:clojure.core.specs.alpha/params+body],
:in [0]}),
:spec
#object[clojure.spec.alpha$regex_spec_impl$reify__2509 0x3bc0f844 "clojure.spec.alpha$regex_spec_impl$reify__2509@3bc0f844"],
:value
(polymatheia.query/pred-const
[polymatheia.query/x]
(clojure.core/= polymatheia.query/x :key)),
:args
(polymatheia.query/pred-const
[polymatheia.query/x]
(clojure.core/= polymatheia.query/x :key))}
alpha.clj: 705 clojure.spec.alpha/macroexpand-check
alpha.clj: 697 clojure.spec.alpha/macroexpand-check
AFn.java: 156 clojure.lang.AFn/applyToHelper
AFn.java: 144 clojure.lang.AFn/applyTo
Var.java: 705 clojure.lang.Var/applyTo
Compiler.java: 6970 clojure.lang.Compiler/checkSpecs
Compiler.java: 6988 clojure.lang.Compiler/macroexpand1
Compiler.java: 7093 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6789 clojure.lang.Compiler/analyze
Compiler.java: 7095 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6789 clojure.lang.Compiler/analyze
Compiler.java: 6745 clojure.lang.Compiler/analyze
Compiler.java: 3820 clojure.lang.Compiler$InvokeExpr/parse
Compiler.java: 7109 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6789 clojure.lang.Compiler/analyze
Compiler.java: 6745 clojure.lang.Compiler/analyze
Compiler.java: 6120 clojure.lang.Compiler$BodyExpr$Parser/parse
Compiler.java: 5467 clojure.lang.Compiler$FnMethod/parse
Compiler.java: 4029 clojure.lang.Compiler$FnExpr/parse
Compiler.java: 7105 clojure.lang.Compiler/analyzeSeq
Compiler.java: 6789 clojure.lang.Compiler/analyze
Compiler.java: 7174 clojure.lang.Compiler/eval
Compiler.java: 7132 clojure.lang.Compiler/eval
core.clj: 3214 clojure.core/eval
core.clj: 3210 clojure.core/eval
interruptible_eval.clj: 91 nrepl.middleware.interruptible-eval/evaluate/fn
main.clj: 437 clojure.main/repl/read-eval-print/fn
main.clj: 437 clojure.main/repl/read-eval-print
main.clj: 458 clojure.main/repl/fn
main.clj: 458 clojure.main/repl
main.clj: 368 clojure.main/repl
RestFn.java: 137 clojure.lang.RestFn/applyTo
core.clj: 665 clojure.core/apply
core.clj: 660 clojure.core/apply
regrow.clj: 20 refactor-nrepl.ns.slam.hound.regrow/wrap-clojure-repl/fn
RestFn.java: 1523 clojure.lang.RestFn/invoke
interruptible_eval.clj: 84 nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 56 nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 155 nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
AFn.java: 22 clojure.lang.AFn/run
session.clj: 190 nrepl.middleware.session/session-exec/main-loop/fn
session.clj: 189 nrepl.middleware.session/session-exec/main-loop
AFn.java: 22 clojure.lang.AFn/run
Thread.java: 832 java.lang.Thread/run
What editor or tool were you using? And what Clojure version? I spent a lot of time in Clojure 1.10 and 1.10.1 making this better but it’s somewhat tool dependent so I’m just curious about your experience. I would expect way less (and better) output from a base repl in 1.10.x.
Hey thanks for all the effort you putting in. I can check when I'm at home but I guess I'm not on the newest version yet. Anyways I really appreciate your work :hugging_face:
To answer your questions: I use GNU Emacs 26.3
(doom-emacs with cider). My Clojure version is clojure 1.10.1-7
. My overall experience with the error messages is actually quite satisfying, the error message tells me the location of the error and I usually use the repl from there to track the error down. In the particular case I posted was, that I already knew the line of the error, but the error message doesn't lead me anywhere to close my lack of knowledge (at least it doesn't do it in a prominent way, that a newbie like me can figure it out 😇). For example a useful keyword to search for or something similar. I hope this helps and thanks again.
So I’m using you’re using Cider? The Clojure error triager can turn the stack you saw into something a little less daunting and that’s what you should see on other repls.
Hey you are right. The error message I've got was inside the cider error-buffer. That's error msg inside the repl:
Syntax error macroexpanding clojure.core/fn at (src/polymatheia/query.clj:22:4).
polymatheia.query/pred-const - failed: vector? at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
polymatheia.query/pred-const - failed: (or (nil? %) (sequential? %)) at: [:fn-tail :arity-n] spec: :clojure.core.specs.alpha/params+body
I've just picked up emacs for clojure. There so much to learn and I haven't connected all the information yet 🙈😇Yeah, that’s way more focused but still a pretty confusing spec error. We might be able to tweak that spec to make it better. Thanks
The spec here is basically expecting an optional fn name then the params in an arity-1 function. In this case the qualified symbol didn’t match the option so it’s trying to match the qualified symbol it has with the next thing - the param list. So it’s actually missing the potential match that is the intended one entirely. It’s actually a pretty subtle but common case to look at.
But the user clue here should really be the wrong value - polymathiea.query/pred-const
Hey team, what is the clojure view on the idea of "thinking locally" From exp in big cos, one of the big benefits of types (apart from the many drawbacks) was this: • If you have a function in the middle of a callstack, and need to change it, you can be rather certain that all else will be fine, as long as you don't change the input and output type. If you do change it, then the compiler will guide you to all the places that depended on it • In practice, since most code lived somewhere in the middle of the stack, this was very useful How do we deal with this kind of thing in clojure? • For example, we could use spec at the boundaries, but if a new engineer came into a function in the middle of the stack, it would be hard for them to have guarantees or safely change. We could add safety checks everywhere, but that would make most code needlessly complex From Rich Hickey talks, I have a feeling the clojure view of design is quite different -- from how we define functions & contracts. Am not quite sure as I try to make it concrete -- would love a clojurian's take
A reasonable counter argument could be that just because a function still returns the same type that it returned before it was changed doesn't necessarily mean that all else will be fine.
It doesn't really matter. Maybe static typing can catch a few of these easy mistakes. But it does not catch everything anyway. And people know that, so they also push unit testing to catch more. If you are doing random changes in your code without fully understanding them and hoping that the machine will do the checking for you, sooner or later you will shoot your own leg. Clojure way is to simplify the code so that you have a better chance to actually understand what is going on. I can easily do refactoring but searching for usages of a function and seeing what precisely is impacted.
Hey!
I was initially trying to figure out how to use the output of str/split
as respective params for a function, but then I discovered apply
and it worked perfectly.
Now where I'm stuck, though, is I need to add another param AFTER the params added from the str/split
.
So what was (apply my-func (str/split "example for slack" #" "))
I now need something like (apply my-func (str/split "example for slack" #" ") "another param")
.
I hope that made sense, and thanks!
one option is to add the parameter to the front
if you need to add it to the end, you have to add it to the end of the sequence produced by str/split, for instance using concat
(apply my-func (concat (str/split "example for slack" #" ") "another param"))
(let [a [1 1]
b [0 1]]
(= [1 2] (fn-add a b))
how I make this fn-dd that is sum of (a0+b0, a1+b1)thanks I was doing this in my code, maybe it's another error :thinking_face:
in other part, hah
ow! made it
hahaha
it's in another part of my code
@pavel.klavik thanks! I think I'm going to go with the "changing parameter order" solution. Thanks! I didn't know that apply took params before the vector. Thanks 😃
kinda of
Any built-in way to make Clojure throw an exception when a value does not exist in a set instead of returning nil?
Something like this (not sure why you want this):
(defn with-exception-instead-of-nil [val]
(if (nil? val)
(throw (Exception. "Returned nil"))
val))
Use like:
(with-exception-instead-of-nil (your-fn-that-returns-nil args))
But a value not existing in a set isn't usually something that should throw an exception, again, depends on your use case.
Thanks, I figured I could do something like that. Was wondering if there was a built-in way of doing it. It is useful when using enums to query the database for example. It leave no room for typos etc.
I'm trying to retrieve the equal elements in the beginning of the two lists, I've come up with a rather convoluted way. I'm sure there is a better way of doing this? (The result should be (1 2)
. )
The natural way to do this is reduce.
(reduce (fn [[x y] same]
(if (= x y)
(conj same x)
(reduced same)))
[] paired)
Great, thanks, I learned about reduced
! I didn't actually get the same correct result though -- is paired the same structure as above?
ya, it should be, maybe I am doing something wrong, let me test it
sorry, I swapped arguments in reduce fn
(let [xs [1 2 3 5]
ys [1 2 4 5]
paired (partition 2 (interleave xs ys))]
(reduce (fn [same [x y]]
(if (= x y)
(conj same x)
(reduced same)))
[] paired))
Ah, yes, I see it now too, thanks.
I'm curious, would there be a way of iterating over the two sequences without creating paired
first?
It 'feels' like it should - but I could not figure it out.
I don't think there is something like that, but partition and interleave should both be lazy, so it should work well
I would probably just remove paired from let and add it directly into reduced at the end
(I'm using paired in a different expression too on in this case)
Here’s my quick take using loop
(defn take-matches [coll1 coll2]
(loop [[x & xs] coll1
[y & ys] coll2
acc []]
(if (or (nil? x) (nil? y) (not= x y))
acc
(recur xs ys (conj acc x)))))
Probably the wrong way to do it but...
(defn pairs
([xs ys] (pairs xs ys []))
([xs ys acc]
(if (or (empty? xs) (empty ys?) (not= (first xs) (first ys)))
acc
(recur (next xs) (next ys) (conj acc (first xs))))))
(defn pairs
([xs ys] (pairs xs ys []))
([xs ys acc]
(if (or (empty? xs)
(empty? ys)
(not= (first xs) (first ys)))
acc
(recur (next xs)
(next ys)
(conj acc (first xs))))))
(defn pairs
([xs ys] (pairs xs ys []))
([xs ys acc]
(cond
(= xs ys) xs
(not= (first xs) (first ys)) acc
:else (recur (next xs)
(next ys)
(conj acc (first xs))))))
Also works and doesn't need empty checks for equal inputs@somedude314 i think you might be well served by spec here
Yeah, thanks for the pointer. I believe I will use it but it seems I need to wrap it somehow since (spec/conform ::langs :en)
for example return the value if available which is what I want but doesn’t throw an exception when the value isn’t available. It returned invalid which make it as good as using string literal if used directly
(spec/def ::class-statuses #{:active :archived :deleted})
(defn enum
[val vals]
(if (spec/valid? vals val)
val
(throw (Exception. (format "%s is not a valid value of %s spec." val vals)))))
(enum :active :myproj.constant/class-statuses)
This is the behaviour I need in some of my Ring handlers. Am I missing something?something odd is happening in that I have this function:
(defn prettify-date
`[date]`
`(println date)`
`(println (str date)))`
and when I run it from with the param sent from a date queried from postgres (via next.jdbc
) the output is:
#inst "2020-07-03T21:00:00.000-00:00"
2020-07-04
but when I copy and paste #inst "2020-07-03T21:00:00.000-00:00"
and run it in the REPL manually, the function prints:
#inst "2020-07-03T21:00:00.000-00:00"
Sat Jul 04 00:00:00 IDT 2020
I'm a bit confused why it seems to be getting the same input, but casting it to a string differently. Anyone have any idea what's going on? Thanks!
SQL dates and other dates will pr-str
(readable stringify) identically, but have different toString implementations (what str
uses)
a simpler example of the same behavior
(ins)user=> (println (map inc [1 2 3]))
(2 3 4)
nil
(ins)user=> (println (str (map inc [1 2 3])))
clojure.lang.LazySeq@7c42
nil
the pr-str
behavior that println used shows the lazy seq as a list, the toString
behavior which str introduces gives a dumb object identity reference
maybe the simpler way to say all this is that println uses pr, and pr does conversions, with the general goal that what it writes be readable
by using str you prevent pr from doing its thing, by handing it a string
wow okay thanks! It took me awhile to find this bug and once I did, it was quite the head-scratcher. I'll look into pr
and use that. Thanks, @noisesmith!
if you want unambiguous logging of values, the function returned by (juxt type pr-str)
is helpful
(ins)user=> (def expose (juxt type pr-str))
#'user/expose
(ins)user=> (expose (map inc [1 2 3]))
[clojure.lang.LazySeq "(2 3 4)"]
(ins)user=> (expose (java.util.Date.))
[java.util.Date "#inst \"2020-07-04T19:34:38.391-00:00\""]
What I'm trying to do is make it human-readable. I assume I'm not the first to want to do that, though, so this is probably a solved problem...
usually the string returned by pr-str
is clear, in that case, and that function can be used inside whatever logging function you like
though of course prn
will just print with the same rules pr-str
uses
pr-str
simply returned #inst "2020-07-03T21:00:00.000-00:00"
for me so it didn't seem to act as wanted
maybe what you really want is SimpleDateFormat https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/SimpleDateFormat.html
@binny If that was a SQL DATE
then it's likely to have .toString()
rendering just a date -- but under the hood it's a java.sql.Date
, which extends java.util.Date
and that's going to be local -- with a timezone -- hence the five hour offset. Dates (and timezones in particular) are hard on the JVM, and SQL types make that even harder (esp. since PostgreSQL has both timezone-aware and timezone-neutral SQL types 😐
I'm getting that sense! I'm sure you're well-seasoned, though, with directly printing date
types from postgres via next.jdbc
. What's the easiest/most sensible way to do that?
I always use an explicit date formatter, as noisesmith suggested.
(but I also generally avoid SQL DATE
types unless I have a very specific need for date-only columns)