Fork me on GitHub
#clojure
<
2021-08-04
>
didibus00:08:05

Two questions: 1. On a let, is it better to put type hint on the let-bound name or on the bounded form?

(let [^String foo "123"])
;; or
(let [foo ^String "123"])
2. Can you get the type hint of a let binding from inside the let?

ghadi00:08:56

always on the bound symbol

didibus00:08:37

Any reason why?

ghadi00:08:39

In the example you provided, a type hint is not necessary because the compiler knows the literal string is a…String

didibus00:08:49

Ya, I was just lazy in my example, my use case returns a custom Java object

ghadi00:08:19

it is not possible nor necessary to type hint numeric literals or Strings

vemv00:08:46

or coll literals, I make that mistake from time to time :)

vemv00:08:29

@didibus > 1. On a let, is it better to put type hint on the let-bound name or on the bounded form? A more realistic example would be:

(let [^String approach-1 (thing 42)
      approach-2 ^String (thing 42)]
  )
For me approach-1 is better. If thing is/becomes a macro, the metadata would be useless (good read: https://github.com/jonase/eastwood#unused-meta-on-macro) > 2. Can you get the type hint of a let binding from inside the let? (edited) Perhaps with your own let impl. Sounds like a thing to be best tackled at compile-time. Or you could use reflection e.g. (class x) in the middle of the let

Joel02:08:03

if i am using immutable data, do i also need to use an atom? can i just def the value again with the latest version? (eg. after an assoc invocation)

seancorfield02:08:42

I'm not sure where you are in your Clojure journey but that sounds like a question perhaps better suited to #beginners where folks have opted in to help folks learn Clojure in depth.

seancorfield02:08:26

This channel tends to assume a level of experience that is likely to get you short and sometimes sharp answers that may not provide enough detail.

seancorfield02:08:46

From your question, it sounds like you are considering using def inside another function @UH13Y2FSA?

seancorfield02:08:46

def is always global so you should pretty much never use it inside a function. If you're thinking about side effecting code, you'll probably want to explain more of what you're trying to do -- a lot of beginners reach for atom when they would use a regular (mutable) variable in another language.

Joel02:08:03

yes, i'm wanting to keep a lookup dictionary, that can be added to, but i don't expect client code to hold it for me. client code should be able to resolve using the dict using functions in that same namespace, but also be able to add to it as well... multiple threaded

Joel02:08:04

i'm sort of presuming atom is needed since it's multiple threads, but wasn't sure if that was overkill

simongray07:08:41

Use an atom. Definitely not overkill.

kwladyka08:08:41

https://clojure.wladyka.eu/posts/share-state/ I hope you will my article helpful. Give me some feedback if you like to :)

kwladyka08:08:42

PS disclaimer: the article was targeted to developers who have experience with Clojure. I don’t know if it is easy to understand for developers who start Clojure journey.

Joel15:08:50

having had a previous "lisp-life" in general I feel comfortable with clojure, but the things that are specific to clojure outside of the "new" data structures not so much. So yes, this article hits that gap in knowledge, thanks.

👍 2
wegi08:08:02

When using a full clojre(script) Stack and using datomic as a database: Is there a good reason not to use instants in some specified timezone to store and transfer time? (I’m aware that time handling is messy and my question is a little generalized. Just looking for obvious things I might be missing)

kwladyka08:08:54

As far as I know instants are the best to use. But I don’t use datomic, so I don’t know if this can have effect on this choice.

👍 2
jumar11:08:49

What you mean by “instants in some specified timezone”? Instants by definition don’t have timezone information ir rather they assume UTC

wegi12:08:12

I worded it clumsily. I meant saving instants and converting them to the users local timezone on the “last mile” before showing them.

greg13:08:49

How to datafy 3rd party protocols? Is there a better way than the one below?

(extend-protocol p/Datafiable
  Object
  (datafy [x]
    (if (satisfies? CustomProtocol x)
      (p/datafy (do-sth x))
      x)))

Ben Sless13:08:58

Call extend again

Ben Sless13:08:27

From the previous example:

(require '[clojure.core.protocols :as p])
(require '[malli.core :as m])

(extend-protocol p/Datafiable
  Object
  (datafy [x]
    (cond
      (satisfies? m/Schema x)
      (do
        (extend-protocol p/Datafiable
          (class x)
          (datafy [y] (m/-form y)))
        (p/datafy x))

      (satisfies? m/IntoSchema x)
      (do
        (extend-protocol p/Datafiable
          (class x)
          (datafy [y] (m/-form (m/schema y))))
        (p/datafy x))

      :else x)))

greg13:08:33

The thing is that I'm not sure extending-protocol for object is a good idea. If there is going to be another similar piece of code using the same trick, you I might get an undeterministic behaviour (well, deterministic but depending on the order of form evaluation). Have a look at the below example:

(require '[clojure.core.protocols :as p]
         '[clojure.datafy :refer [datafy]])

(defprotocol ProtA (say-a [_]))
(defprotocol ProtB (say-b [_]))
(def sample-prot-a (reify ProtA (say-a [_] "a")))
(def sample-prot-b (reify ProtB (say-b [_] "b")))

(say-a sample-prot-a) ;=> "a"
(say-b sample-prot-b) ;=> "b"
(satisfies? ProtA sample-prot-a) ;=> true
(satisfies? ProtB sample-prot-b) ;=> true

(extend-protocol p/Datafiable
  Object
  (datafy [x]
    (if (satisfies? ProtA x)
      :a-object
      x)))

(datafy sample-prot-a) ;=> :a-object

(extend-protocol p/Datafiable
  Object
  (datafy [x]
    (if (satisfies? ProtB x)
      :b-object
      x)))

(datafy sample-prot-b) ;=> :b-object
(datafy sample-prot-a) ;=> #object[dev$reify__57541 0x3a89db1d "dev$reify__57541@3a89db1d"]
So there are ProtA and ProtB - two independent protocols, potentially in two separate modules, that would like to datafy theirselfs. If I have two independent extend-protocol p/Datafiable on Object , one overrides another. That is why I'm looking for another ways 🙂 I'm not very well familiar with Clojure protocols, so I might be missing something 😅

Ben Sless14:08:10

It's not perfect but calling satisfies every time is bad

👍 2
greg15:08:08

Well, I think your example is better, not only because the satisfies. In your example, you extend on the specific type of malli class, so even if the someone calls again (extend-protocol p/Datafiable Object ...) it is ok, as long as the datafy was called on every Malli type.

greg15:08:54

Some alternative could be extending each Malli type separately, like:

(let [forms [[:and any?]
             [:map]
             [:map-of string? string?]
             [:multi {:dispatch :t}]
             [:or any?]
             [:orn [:a any?]]
             [:sequential string?]
             [:vector string?]]]
  (doseq [f forms]
    (extend-type (class f)
      p/Datafiable
      (datafy [x] (m/form x)))))

Ben Sless15:08:17

There are lots of types there, and you also miss out on user extension

Ben Sless15:08:56

I borrowed this implementation pattern from Clojure Applied

Ben Sless15:08:45

Page 23, Extending Protocols To Protocols

greg16:08:04

Haha, I read this book in the past. I've just grabbed it to check that page and what is there? An explanation mark to remember this trick 😄

Jim Newton13:08:56

Can someone try the following and tell me what happens? I'm getting a java null pointer exception.

(cl-format false "~&~
                            item=~A~@
                            transitions=~A~@
                            lhs=~A~@
                            rhs=~A~@"
                      1
                      2
                      3
                      4
                      ) 

Jim Newton13:08:47

it appears to be caused by the final ~@ this prints a line-feed, but the error seems to be that the underlying is trying to read arguments past 1 2 3 4.

Jim Newton13:08:49

seems to be reproducible by a much simpler call

(cl-format false "
~@")

Ed14:08:40

looks like in compile-directive it's calling (Character/toUpperCase ^Character directive) where directive is (first rest) which is assuming that the next character (after the @) is not nil ... right? ... that's the cause of the null pointer ... I'm pretty rusty with by cl-format, is that just a bad error message?

Ed14:08:28

a trailing ~@ seems to violate the description of what it should do, right? cos the newline is missing??

2
Jim Newton14:08:15

yes I think you're right. it shouldn't have a null exception, but rather complain that it is expecting at least one character after the @ ???

Jim Newton14:08:07

putting a newline after the @ works.

(cl-format false "~&~
                            item=~A~@
                            transitions=~A~@
                            lhs=~A~@
                            rhs=~A~@
"
                      1
                      2
                      3
                      4
                      )

Jim Newton14:08:11

in common lisp

(format nil "~@")
gives the following
error in FORMAT: String ended before directive was found
  ~@
  ^
   [Condition of type SB-FORMAT:FORMAT-ERROR]

Restarts:
 0: [RETRY] Retry SLIME REPL evaluation request.
 1: [*ABORT] Return to SLIME's top level.
 2: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1004289BC3}>)

Backtrace:
  0: (SB-FORMAT::FORMAT-ERROR-AT* "~@" 0 "String ended before directive was found" NIL)
  1: (SB-FORMAT::FORMAT-ERROR-AT "~@" 0 "String ended before directive was found")
  2: ((FLET SB-FORMAT::GET-CHAR :IN SB-FORMAT::PARSE-DIRECTIVE))
  3: (SB-FORMAT::PARSE-DIRECTIVE "~@" 0 NIL)
  4: (SB-FORMAT::%TOKENIZE-CONTROL-STRING "~@" 0 2 NIL)
  5: (SB-FORMAT::TOKENIZE-CONTROL-STRING "~@")
  6: (SB-FORMAT::%FORMAT #<SB-IMPL::CHARACTER-STRING-OSTREAM {10045EAB73}> "~@" NIL NIL)
  7: (FORMAT NIL "~@")
  8: (FORMAT NIL "~@") [more]
  9: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FORMAT NIL "~@") #<NULL-LEXENV>)
 10: (EVAL (FORMAT NIL "~@"))

Jim Newton14:08:39

ahhh I see what's happening. the @ is just a flag to the "~ " directive. that's clever. I never realized that before. I thought ~@ was the directive. duhhh

👍 2
Jim Newton15:08:11

I doubt it is the most obscure sentence in the specification.

🤪 2
😁 2
Jon Boone14:08:50

@jimka.issy where did you get the ~@ notation from? Checking the CLHS, it seems like you want ~% if you want to explicitly print a newline

(use '[clojure.pprint :only (cl-format)])

(cl-format false "~&~
                            item=~A~%
                            transitions=~A~%
                            lhs=~A~%
                            rhs=~A~%"
                      1
                      2
                      3
                      4
                      ) 

"item=1\n\n                            transitions=2\n\n                            lhs=3\n\n                            rhs=4\n"

Jim Newton14:08:42

the ~@ prints a new line and then skips the white space in the format string. This allows the lines such as "item=", "transitions=" etc to be aligned in the source code, but print at the left-margin in the output stream.

Jim Newton14:08:31

I guess ~@ at the end of string is really dubious. cl-format should skip the white-space that follows, of which there is non in my string.

Jim Newton14:08:47

using @ rather than % in your example should result in the following:

(use '[clojure.pprint :only (cl-format)])
(cl-format false "~&~
                            item=~A~@
                            transitions=~A~@
                            lhs=~A~@
                            rhs=~A~%"
                      1
                      2
                      3
                      4
                      ) 
"item=1\ntransitions=2\nlhs=3\nrhs=4\n"

Jim Newton14:08:27

optionally with a leading \n if necessary

Jim Newton15:08:58

I see (from a conversation with @l0st3d that it is not ~@ which produces this behavior but ~ followed by newline. this directive when given the @ flag has the special meaning of newline-skip-white-space-and-left-justify-output

Jon Boone15:08:32

Yes --- that’s why I missed the mention in the CLHS that you graciously provided

2
Jon Boone15:08:45

However, just substituting the ~@ for ~% in your example format string and converting to CL, it fails in SBCL (one of the best ANSI CL compliant open source implementations available) as well:

❯ sbcl
This is SBCL 2.1.7, an implementation of ANSI Common Lisp.
More information about SBCL is available at <>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
* (format nil "~&~
                            item=~A~@
                            transitions=~A~@
                            lhs=~A~@
                            rhs=~A~@"
                      1
                      2
                      3
                      4
                      )

debugger invoked on a SB-FORMAT:FORMAT-ERROR in thread
#<THREAD "main thread" RUNNING {7001538143}>:
  error in FORMAT: String ended before directive was found
  ~&~
                            item=~A~@
                            transitions=~A~@
                            lhs=~A~@
                            rhs=~A~@
                                                                                                                                                                ^

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-FORMAT::FORMAT-ERROR-AT* "~&~
                            item=~A~@
                            transitions=~A~@
                            lhs=~A~@
                            rhs=~A~@" 158 "String ended before directive was found" NIL)
0]

Jon Boone15:08:45

I think this is what you’re looking for:

❯ lein repl
nREPL server started on port 62801 on host 127.0.0.1 - 
REPL-y 0.4.3, nREPL 0.5.3
Clojure 1.8.0
OpenJDK 64-Bit Server VM 11.0.11+9
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=> (use '[clojure.pprint :only (cl-format)])
nil
user=>

user=> (cl-format false "~&~
  #_=>                             item=~A~@
  #_=>                             transitions=~A~@
  #_=>                             lhs=~A~@
  #_=>                             rhs=~A~@
  #_=>                       "
  #_=>                       1
  #_=>                       2
  #_=>                       3
  #_=>                       4
  #_=>                       )
"item=1\ntransitions=2\nlhs=3\nrhs=4\n"

Jim Newton15:08:04

exactly. it fails with a helpful error message.

Jon Boone15:08:39

Don’t know if you found my explorations helpful or not with respect to providing more information on the issue with cl-format regarding it yielding a informative error message rather than a NPE…

Jim Newton15:08:05

as I've said several times before. hats-off to whoever implemented cl-format for clojure. that must have been and horrendously difficult task.

👀 2
4
Ed15:08:02

it says this at the top of the file:

;; Author: Tom Faulhaber
;; April 3, 2009

Nazral15:08:13

Is there a way to get a hash of a map in Clojure?

ghadi15:08:05

user=> (hash {:a :b :c :d})
1986597936

Nazral15:08:24

ah-ha, thanks!

dpsutton15:08:54

and a good way to find that is (apropos "hash")

nice 2
Nazral15:08:26

Ah I didn't know that either, thanks a lot

raspasov16:08:11

Might need to type (clojure.repl/apropos "hash") , depends on the type of REPL, I guess

noisesmith19:08:42

clojure always runs (apply require clojure.main/repl-requires) which ends up referring clojure.repl/apropos to your startup ns, but some repls end up changing ns after startup

👍 3
noisesmith19:08:01

luckily that one liner isn't super hard to run in whatever ns you end up in

noisesmith19:08:18

for reference

user=> (pprint clojure.main/repl-requires)
[[clojure.repl :refer (source apropos dir pst doc find-doc)]
 [clojure.java.javadoc :refer (javadoc)]
 [clojure.pprint :refer (pp pprint)]]
nil

Jim Newton15:08:24

If a function f returns a function, and I want to call that function with an argument, the syntax is something like this ((f x y) u v) . However, I find that syntax somewhat cryptic. am I the only one?

(let [...
        new-final-transitions (for [q (xym/states-as-seq dfa)
                                    :when (:accepting q)]
                                ;; we designate new final states each as [:F some-exit-value]
                                [(:index q) :epsilon [:F ((:exit-map dfa) (:index q))]])]
In this code (:exit-map dfa) evaluates to a function which I call with the return value of (:index q). I have inserted an apologetic comment on the previous line.

Jim Newton15:08:13

I'm often tempted to write a function funcall just to make this pattern explicit. not sure if that would be less confusing or more confusing.

Jim Newton15:08:36

(let [...
        new-final-transitions (for [q (xym/states-as-seq dfa)
                                    :when (:accepting q)]
                                ;; we designate new final states each as [:F some-exit-value]
                                [(:index q) :epsilon [:F (funcall (:exit-map dfa) (:index q))]])]

Jim Newton15:08:12

or perhaps

(let [...
        new-final-transitions (for [q (xym/states-as-seq dfa)
                                    :when (:accepting q)]
                                ;; we designate new final states each as [:F some-exit-value]
                                [(:index q) :epsilon [:F (apply (:exit-map dfa) (:index q) nil)]])]

dpsutton15:08:17

i kinda agree. i will often let-bind the function to make it a bit more explicit

2
delaguardo15:08:23

There is always an option to use (.invoke (f x y) a b)

2
🤷 2
👍 2
Jim Newton17:08:04

question was do other people find this cryptic or is it a perfectly valid clojurism? BTW good to know about the .invoke function. thanks.

delaguardo17:08:27

Strictly speaking it is a method)

Jon Boone15:08:16

@jimka.issy — is your background in Lisp-1 languages like Scheme / Clojure or Lisp-2 languages like CL?

Jim Newton17:08:49

its about 50/50. the propietary lisp1 that I used for many years had a funcall function which people used for this case, but was normally redundant.

Ed16:08:25

if you want a funcall you could use (apply (:exit-map dfa) [(:index q)]) or something?? ... but I too tend to use let to name the function

Ben Sless16:08:46

(defmacro >-
  [x & forms]
  (loop [x x, forms forms]
    (if forms
      (let [form (first forms)
            threaded (if (seq? form)
                       (with-meta `(~x ~(first form) ~@(next form)) (meta form))
                       (list x form))]
        (recur threaded (next forms)))
      x)))

(>- f (x y) (u v))
:man-shrugging:

🤯 2
Jim Newton17:08:44

yes if you want to do it with a macro, I guess you really need to preserve the meta data. wouldn't it be better with a normal lambda function? Something like the following untested:

(defn funcall [f &rest args]
   (cond (empty? args)
         (f)
         
         (empty? (rest args))
         (apply f args)
         
         :else ;; need to assert (last args) is a seq!
         (apply f (concat (butlast args) (last args)))))

Ben Sless18:08:14

Besides the very heavy performance overhead of calling last/butlast and apply/concat, the macro is repeatedly left associative, which means you can write stuff like (>- f x y z) which will expand to (((f x) y) z) , could be useful if you're doing stuff based on untyped LC

Jim Newton17:08:46

I'd like to try my hand at debugging an error I found in cl-format. This means locally I need to re-define (for testing) a function which is defined in cl_format.clj and is defined as follows:

(defn- compile-directive [s offset]
  (let [[raw-params [rest offset]] (extract-params s offset)
        [_ [rest offset flags]] (extract-flags rest offset)
        directive (first rest)
        def (get directive-table (Character/toUpperCase ^Character directive))
        params (if def (map-params def (map translate-param raw-params) flags offset))]
    (if (not directive)
      (format-error "Format string ended in the middle of a directive" offset))
    (if (not def)
      (format-error (str "Directive \"" directive "\" is undefined") offset))
    [(struct compiled-directive ((:generator-fn def) params offset) def params offset)
     (let [remainder (subs rest 1) 
           offset (inc offset)
           trim? (and (= \newline (:directive def))
                      (not (:colon params)))
           trim-count (if trim? (prefix-count remainder [\space \tab]) 0)
           remainder (subs remainder trim-count)
           offset (+ offset trim-count)]
       [remainder offset])]))
I tried the following:
(def clojure.pprint/compile-directive
 (fn [s offset]
    ...)
but I get an error
Syntax error compiling def at (clojure-rte:localhost:64772(clj)*:1829:23).
Can't create defs outside of current ns

seancorfield17:08:37

(require 'clojure.pprint)
(in-ns 'clojure.pprint)
(defn compile-directive ...)
(in-ns 'your.ns)

Jim Newton17:08:44

does the trick. Thanks for the suggestion.

Jim Newton17:08:57

Not sure why the restriction to not be able to create defs outside the current ns is a necessary restriction. 😡

seancorfield19:08:31

I would say because it destroys locality of definition. If "any" code goes around redefining things in other namespaces then a) you can no longer rely on just looking in the namespace where its source lives and b) the semantics of the function now depend on the order that code is executed. Both of those are a Bad Thing(tm) in maintainable code.

Jim Newton07:08:40

That's an interesting idea, but it doesn't work. I can still call in-ns in any file, and end up doing the same thing in a less declarative way. It seems to me that allowing the more declarative defn x/foo or def x/foo would only help to find definitions rather than obscure them. Plus, one of the features of defmethod is that even if the defmulti foo is defined in namespace X, I can define methods on X/foo in many different files thus allowing applications to extend a multi-function.

seancorfield07:08:49

Sure, but this makes you work at it and deliberately doesn't normalize it. That is usually Clojure's way.

jumar19:08:47

Is clojure.core AOT compiled? I'm trying to use approach similar to the when-class macro and conditionally definite a function only if java.lang.ProcessHandle is available. I'm not sure about possible implications for AOT compiled uberjar. Would this be a good approach?

(when-class "java.lang.ProcessHandle"
    (let [all-proc-fn (requiring-resolve 'util.processes.impl/all-processes)]
      (->> (all-proc-fn)
           .iterator
           iterator-seq
           (mapv process-info))))


;;; in another ns
(ns util.processes.impl)

(defn all-processes []
  (java.lang.ProcessHandle/allProcesses))

p-himik19:08:09

> Is clojure.core AOT compiled? Yes.

vemv19:08:03

> Would this be a good approach? I think so, just make sure "AOTer" and "production" environments match. Same JDKs, identical classpaths, etc Best achieved at CI level

jumar19:08:00

Yeah, it might be actually a problem - I'll need to check. We have to support Java 8 in some environments but in others (like our Docker image) we can count on JDK 11.

vemv19:08:09

Might be stating the obvious but that would be a good reason to generate different AOTed artifacts on a CI matrix

👍 2
jumar19:08:12

@U2FRKM4TW Where can I check that core is AOT compiled? I skimmed https://clojure.org/reference/compilation but didn't find it there - only that it's directly linked

p-himik19:08:11

> As of Clojure 1.8, the Clojure core library itself is compiled with direct linking.

p-himik19:08:29

If you open the jar, you'll see a load of .class files.

2
jumar19:08:16

Ah right, so compiled is the same as AOT-compiled - yeah, i guess that's obvious, just being a bit confused.

jumar19:08:03

Is it possible to make use of direct linking without AOT? - I'd think so but...

vemv19:08:43

btw being pedantic "compiled" can also apply to any use of clojure.core/compile... which you can invoke at runtime, which makes it a case of non-AOT compilation

jumar19:08:02

Yeah, but the net affect is the same/similar? That is a class file being written to the disk. Perhaps there's a difference depending on how/when you load the code that is being compiled - before vs after compilation?

jumar20:08:29

Hmm, I was surprised that the above code works well in AOT-compiled uberjar: • when run with Java 8 it returns empty collection • when run with Java 16 it returns expected processes.

vemv20:08:02

> Yeah, but the net affect is the same/similar? That is a class file being written to the disk. > Perhaps there's a difference depending on how/when you load the code that is being compiled - before vs after compilation? Subtle one, I think I'd need a refresher for accurately answering :) I think that clojure has a contractual priority when finding both a .class and .clj file (don't ask me which ;p) The classpath and order of operations might also affect

vemv20:08:33

> Hmm, I was surprised that the above code works well in AOT-compiled uberjar: yeah tbh it looked like it would just work. It partly depends on the use case. If you want to rest assured that you won't find opaque issues in a future I'd go the "pedantic route" and create fine-grained artifacts as menioned

jumar20:08:31

Anyway, thank you both for the advice!

🙂 2