Fork me on GitHub
#beginners
<
2022-01-17
>
didibus00:01:19

You have to be careful when using ::, normally it is done to avoid key conflicts, but you need to be sure that it is not something that others will depend on. So like use it only for things for which the namespace is meant to write/read from as an internal detail, and not to be persisted or serialized.

noisesmith01:01:26

@didibus what's the harm there? you serialize a form that had ::foo and :your-ns/foo gets written to disk

emccue01:01:02

yeah it provides some potential for you to break things by moving the code, thats basically it.

didibus02:01:47

Your namespace name is not stable, so it's easy to break consumers or future you. That's why 1.11 introduced lightweight aliases, so that you can make a stable namespace alias and use ::alias/foo over ::foo for your keywords.

didibus02:01:32

In your example, a year later, the namespace might have moved/been renamed too: my-ns, or maybe the code accessing ::foo was moved to a separate ns like your-ns-impl. And now suddenly you load this file you'd save to disk, and (::foo (edn/read-string (slurp "my-file.edn"))) returns nil, because the file contains :your-ns/foo, but ::foo now returns :my-ns/foo or :your-ns-impl/foo

seancorfield02:01:02

"That's why 1.11 introduced lightweight aliases" -- no, it was not introduced because "namespace names are not stable".

didibus02:01:19

That was definitely one reason, at least I remember asking about this problem and Alex saying that this is why they are looking into lightweight aliases

seancorfield02:01:58

It was introduced because qualified keywords are not related to namespaces and people were doing all sorts of workarounds for the lack of lightweight aliases.

didibus02:01:42

That seems like just a different way to say the same thing? The issue is coupling keywords to namespaces. And :: does that. One of the problem this coupling causes is what I mentioned. Because of this coupling and its problem, people were doing all sorts of workarounds.

seancorfield02:01:32

Don't mix ::foo with ::quux/foo

seancorfield02:01:27

::foo couples you to (the current) namespace. ::quux/foo uses an alias that can point at anything.

didibus02:01:39

Agree with that. I go a step further, and also just say to be careful if using ::foo, its easy to introduce breakage or corrupt data using it.

seancorfield02:01:43

I think ::foo fits into the "scope" discussion -- ::foo is naturally scoped to just the enclosing namespace. It's unique and almost "private".

seancorfield02:01:23

If I'm sharing a qualified keyword across namespaces, I generally pick a meaningly qualifier, not just whatever random namespace it happens to be in.

seancorfield02:01:29

Like, in halt-when, it uses ::halt -- and I'd consider that a private implementation detail. I wouldn't expect anyone to use :clojure.core/halt or have an alias to clojure.core and do something like ::core/halt.

didibus03:01:49

Ya, completely agree. That's what I meant, ::foo should be for things that are internal details. Like implementation details or pseudo-private as you say. Should also be temporary, as-in, not survive an app restart or be persisted/etc. The thing is, it is common for people to have a (ns my-app.user) and then start using ::name, ::address, ::password, which then gets stored in the DB, to file, passed to payloads to other services, etc. Sometimes they started with :name and :address, but when they add Spec realize, oh it wants namespaced keywords, and the simple fix is ::. And that's fine, but the issue is that later people don't realize that refactorings can break this data. Sometimes even simple things, like a library might throw an ex-info with data as {::type :invalid-input}, the user of the library will write some try/catch and hard-code :the-cool-lib/type in there to check the type of the exception. Later the library author might refactor things again so that this is thrown from a different namespace, not realizing they will break consumers.

didibus03:01:24

For those reasons, I feel the producer and consumer code of :: needs to be pretty self-contained, or dare I say, encapsulated šŸ˜›

seancorfield03:01:40

I agree, with the caveat that ::foo and ::quux/foo are very different.

didibus06:01:54

Can you elaborate what you imply with that caveat?

seancorfield18:01:06

@didibus I think we're in agreement here: ::foo should be considered "private" to a namespace or at least "encapsulated", whereas ::quux/foo is just shorthand for :some.quux.thing/foo where the alias is some local name you've chosen to make your code less verbose.

šŸ‘ 1
Krishan V08:01:22

Hey everyone, how would I go about interpolating a variable in a string?

emccue14:01:23

also https://github.com/clojure/core.incubator

(let [name     "bob"
      item-id  "A567"
      currency "EUR"]
  (<< ""))

noisesmith15:01:24

there's also clojure.pprint/cl-format or for the simplest cases, str

emccue16:01:24

personally iā€™d just use str if its regular stuff

šŸ’Æ 1
noisesmith16:01:42

with occasionally nested pr-str if you need the alternate printing behavior

mbjarland16:01:59

Is there something built into clojure which you could use as a priority queue? Or are my options to use a sorted map ignoring the values, use sorted set, or to use java interop with java.util.PriorityQueue?

noisesmith16:01:34

the sorted set / map would delete items if the priority is equal to an existing item. I'd just use the java one via interop.

mbjarland16:01:39

ok...was somehow expecting there to be something lying around, or some way of using existing primitives to get the job done. Thanks @noisesmith - that answers the question.

noisesmith16:01:14

there are libraries on github offering clojure priority queues, but I can't vouch for any of them

mbjarland16:01:56

oh and I was going to use unique things as the sorted items...so no collisions would be possible, but still...I'll probably drop back to the trusted and true java impl like suggested

mbjarland16:01:48

out goes immutability I guess...sad

noisesmith16:01:25

yeah when the behavior desired is explicitly side effecting, the cross over to immutable data, then a second one back to the mutable world via atom etc. are just two sources of errors IMHO

Muhammad Usman16:01:47

hi all can someone please help me to generate swagger ui for my API in clojure

noisesmith16:01:40

what have you done so far? there are libraries for generating swagger - eg. https://github.com/metosin/ring-swagger

Muhammad Usman16:01:27

[reitit.swagger :as swagger] [reitit.swagger-ui :as swagger-ui] ["" {:no-doc true} ["/swagger.json" {:get (swagger/create-swagger-handler)}] ["/api-docs/*" {:get (swagger-ui/create-swagger-ui-handler)}]]

Muhammad Usman16:01:01

i only need to generate swagger ui of API so i can create same API in node js

didibus18:01:54

Clojure APIs have no type definitions, so there is nothing to use to generate Swagger APIs

didibus18:01:28

But if you want to use a spec language to specify the shape of your APIs that works as both something you can use in Clojure and to generate Swagger APIs, you should look at https://github.com/metosin/malli#swagger2

didibus18:01:41

There's also https://github.com/metosin/schema-tools if you prefer using Plumatic Schema or https://github.com/metosin/spec-tools if you prefer using Clojure Spec

andy.fingerhut17:01:11

@mbjarland There is this library with immutable priority queues: https://github.com/clojure/data.priority-map

Muhammad Hamza Chippa22:01:34

How would you replace Factory design patterns in functional programming?

emccue23:01:40

before someone else says it - with a function

emccue23:01:57

interface ThingFactory {
    Thing makeThing();
}

...

void work(ThingFactory factory) {
    var thing = factory.makeThing();
    thing.whatever();
}
(defn work [factory]
  (let [thing (factory)]
    (.whatever thing)))

emccue23:01:09

you can also use the exact pattern as is (where the producer method gets a special name) with traits/typeclasses/protocols depending on your FP language

teodorlu23:01:19

You can probably get a long way by just using generic data structures for input too. Taking a map as input is fine. Taking a sequence of maps as input is fine. Then you can just use Clojure to manipulate those data structures :)

teodorlu23:01:25

See for instance XTDB start-node. Input is just a map! https://docs.xtdb.com/clients/clojure/#_start_node

pablore03:01:48

Higher-order functions are also a form of factory. Picture it as a function that creates a customized constructor for an object.

(defn vending-machine-factory [params]
  (fn [] (str "Giving out a " params "!"))

(let [coke-vending-machine (vending-machine-factory "coke")]
   (println (coke-vending-machine))
>> Giving out a coke!