This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-06-04
Channels
- # announcements (3)
- # babashka (14)
- # beginners (151)
- # calva (14)
- # cider (9)
- # clj-kondo (24)
- # cljdoc (12)
- # cljs-dev (195)
- # cljsjs (3)
- # cljsrn (13)
- # clojars (12)
- # clojure (234)
- # clojure-dev (3)
- # clojure-europe (9)
- # clojure-greece (1)
- # clojure-italy (2)
- # clojure-japan (4)
- # clojure-nl (4)
- # clojure-spec (89)
- # clojure-taiwan (1)
- # clojure-uk (16)
- # clojuredesign-podcast (2)
- # clojurescript (17)
- # conjure (11)
- # core-async (4)
- # core-typed (31)
- # cursive (9)
- # datomic (8)
- # emacs (17)
- # figwheel (1)
- # fulcro (5)
- # ghostwheel (42)
- # graphql (3)
- # hugsql (5)
- # jackdaw (3)
- # jobs-discuss (93)
- # joker (4)
- # meander (6)
- # mount (1)
- # off-topic (23)
- # pathom (10)
- # re-frame (23)
- # reitit (7)
- # remote-jobs (18)
- # shadow-cljs (153)
- # spacemacs (24)
- # sql (30)
- # tools-deps (14)
- # vim (12)
- # xtdb (1)
Was wondering, in Clojure, what's the best way to model an ordered list where I need to remove and insert from inside?
Normally, a linked list works really well for this. Since you can add elements in the middle easily, without needing to push out all the elements after or move up the ones before.
You could probably build a simple insert fn and remove fn for lists. I don’t come across many use cases that really need to operate on indices like that tho
That's most likely what I'll end up doing, and just loop over it to rebuild, since I don't have a big list anyways.
sorted-map already suggested by @lilactown , didn’t see
I would probably use a sorted-map for something like that; alternatively I guess you can split and re-concat vectors via rrb-vector, but it’s somewhat more involved
If it’s a small enough of a collection, you can even just (remove …) (filterv …) from a vector?
Hum.. ya sorted-map wouldn't work, because I don't have anything to sort on. Though... I did think I could have an order key, and if I make it a double, in theory, I could take the element before and after in the current order, and make the new element the middle double value between the two
rrb-vector is probably the best I guess. For my use case, I'll probably just go with a vector that I split, conj/pop and concat.
Was just showing someone how to build a quick todo list app, and got a bit choked on the drag and drop for moving items in the list around 😛
@didibus haha got it… Yea I would say sorted-map can work there, just have a map like {1 {:data 42}, 2 {:data 43}}
Hum, that's kind of what I had, but if you have: {1 {:data 42}, 2 {:data 43}, 3 {:data 34, 4 {:data 34}}
and you want to move :data 34 after :data 42 ?
Which is where I thought, could make the key a double, so I would just turn: 4 {:data 34}
into {1.5 {:data 34}
haha

It just started to feel like I was stretching things a little far with that 😛 Thought there has to be something more obvious
yea… rrb-vector is my vote for sure… more idiomatic; less accidental complexity data around
Which I guess there isn't, this is actually quite a non trivial thing if you want to be immutable and try to approach O(1)
Ya, I heard some rumors that rrb-vectors has a lot of known bugs in its imlementation?
Somebody more familiar would have to chime-in… I am not up-to-speed on it; I’ve used it but it has been years, it wasn’t recently
rrb-vector does have at least one remaining bug that I know about, but as far as I know it requires fairly large vectors to cause it to occur. I wouldn't bet millions of dollars on that guess.
I have read the RRB vector paper, and done testing on about 4 different implementations of it. All that I have tested have bugs in the concatenation implementation, which is one of the trickiest parts to get right. I'm starting to doubt there is a way that is correct and guaranteed O(log N) time.
I would think that for a to-do list, you aren't necessarily needing nanosecond-level response to changes from a UI in the order, and the lists aren't thousands of items long, so perhaps a Clojure vector/list, even with linear time required to arbitrarily insert elements at internal positions, might not actually be noticeable performance-wise?
Ya, I mean I'm not even making a real Todo app, was just doing a short demo of writing one quickly in Clojure. First time I write a Todo app. Everything was going smoothly, but when I got to that part, I was a bit choked. Thinking... Hum, is there no way to immutably handle this case without constructing a whole new vector?
Performance question.
(defn -merge
([xsa] xsa)
([xsa xsb]
(concat
(butlast xsa)
(list (concat
(last xsa)
(first xsb)))
(rest xsb))))
I'm doing this 2^n times. With n == 20, it's taking ~2.6 seconds. Is there a way I can speed that up? Type hints? Some java native thing?If you write your own lazy-seq thing instead of using concat and friends like that you can take advantage of the struct of lazy seqs to do much better
Hmm. Instead of a butlast
followed by a last
on the same thing, I wonder if a (split-at (dec (count xsa)) xsa)
would be faster. Kill two birds with one stone. I'll try out the lazy-cat too.
(fn f [a b]
(lazy-seq
(let [f (first a)
r (rest a)]
(if (seq r)
(cons (first a) (f (rest a) b))
(cons (concat f (first b))
(rest b))))))
so you end up both not being lazy, and doing multiple traversals, where want you want to do a lazy traversal is such a way that those lazy traversals can be stacked, so no work is done until you force the final product, which does all the work and only does a single traversal
In the absence of hitting bugs in the rrb-vector library, that is the kind of thing it is made to be fast at, i.e. lots of split and concat. operations.
When a function looks like this, how does it work? For example; it seems like there are two bodies. Does it pattern match on one of them?
(defn env->UserCredentials
"Create a UserCredentials instance from environment variables, defaulting to
CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, and ACCESS_TOKEN_SECRET"
([]
(env->UserCredentials {:consumer-key "CONSUMER_KEY"
:consumer-secret "CONSUMER_SECRET"
:user-token "ACCESS_TOKEN"
:user-token-secret "ACCESS_TOKEN_SECRET"}))
([env-mapping]
(let [credentials (map-values #(System/getenv %) env-mapping)]
(assert (every? some? (vals credentials))
(str "Failed to read credentials from env; one of "
(vals env-mapping) " could not be found."))
(map->UserCredentials credentials))))
it will choose a body based on the number of arguments passed in (the function’s arity)
Awesome and why does this function name have a ->
in it? Is it some kind of convention for something?
yes, just convention
@tiotolstoy the multi-arity is a common way for providing defaults (like in the example above); you could even have completely different behavior, but that’s less common (maybe even weird and not recommended)
Yep; personally in love with pattern matching on function arity. Just wanted to confirm that’s what I was looking at! @raspasov
Why can you not have many expressions inside the function body of main?
(defn -main
[& arg]
(for [user_id following_ids]
(mute_user user_id)))
If I included (def user_id ….)
above for
it would error out with some error about inline-def
Def in clojure always creates a top level global definition, not a definition in whatever scope you are in, so it almost always a mistake to nest it in some other scope
@tiotolstoy The main reason for that is that in #beginners folks either are beginners or have opted-in to helping beginners so the patience and tolerance is a lot higher there. The expectation for #clojure is that folks have a solid knowledge of the language and tend to be asking more esoteric questions that are likely to have opinion-based answers...
I’m running into a weird macro-related problem. Here’s a minimal example to reproduce:
(def data (constantly true))
(defmacro def-fn [] `(defn ~'foo [] ~data))
(def-fn) ; throws java.lang.IllegalArgumentException: No matching ctor found for class clojure.core$constantly$fn__5672
Any idea what’s causing this and how I can work around it? In the actual code I don’t control data
, so any eventual workaround would have to live in def-fn
.you could change the macro to be more dynamic
(defmacro def-fns [] `(do ~@(for [i (range (count things))] `(defn ~'foo [] (get things ~i)))))
yeah okay that’s not a bad idea
your updated example will expand into
(defn foo [] #function[clojure.core/constantly/fn--5672])
then compiler need to evaluate it but the body is already evaluated into classalright
In my actual application though, I actually want to do something more like:
(require '[schema.core :as s])
(def data (s/constrained s/Any (constantly true)))
(defmacro def-fn [] `(s/defn ~'foo [x :- ~data]))
(def-fn) ; throws java.lang.IllegalArgumentException: No matching ctor found for class clojure.core$constantly$fn__5672
I don’t see how I can make something like that happen without resolving data
in the macro
just leave data
unquoted
(defmacro def-fn [] `(s/defn ~'foo [~'x :- data]))
also note the ~'
before x
awesome, thanks!
what if I also want to generate multiple things?
(def data [(s/constrained s/Any (constantly true))])
(defmacro def-fns [] `(do ~@(for [x [data]] `(s/defn ~'foo [~'x :- ~x]))))
(def-fn)
(let’s pretend I’m coming up with unique names for each thing)
same trick as I described before
(defmacro def-fns [] `(do ~@(for [i (range (count data))] `(s/defn ~'foo [~'x :- (get data ~i)]))))
to make fn names unique - just generate the symbol based on i
or any other unique identifier and use it
amazing
thanks a lot!
actually….
that works, but I can’t understand why 🙂
(defmacro def-fns [] `(do ~@(for [i (range (count data))] `(s/defn ~(symbol (str "foo-" i)) [~'x :- (get data ~i)]))))
as an examplewhich part is the most confusing?
actually, I played around with this a bit and I think I’ve got it now, thanks 🙂
the core is simple: the only difference between macro and function — macro works during Read not Eval in Read/Eval/Print/Loop
cool! happy hacking!
I guess the main thing for me was that I didn’t realize I could just pass arbitrary expressions as schemas to s/defn
,
yeah, thanks again :)
how popular is memoization in clojure? I am looking at a case: There is a service that returns some configuration details, the service is http and stateless. I see in the code they call the service during bootstrap, repack the values into more usable structure and keep that in atom. I was thinking this should be memoized, but then I still have a bunch operations on raw data happening everytime someone wants a particular piece of config, so I would have to memoize all helper functions that access the config?
Enough to have a general purpose function in the core: https://clojuredocs.org/clojure.core/memoize 🙂
yeah I am aware of that function, I am seeking guidance in how to organize such thing with the situation explained above... I know I can easily memoize some http response, however afterwards there is some processing of the data into a more resonable structure - so I guess I should memoize this also?
The http service can be stateless but that doesn't mean it's referentially transparent. I assume it will return different configurations when called over time. Memoization can be used as some sort of caching technique but with caching you end up with a different problem - when do you invalidate the cached results.
For the other part:
> I still have a bunch operations on raw data happening everytime someone wants a particular piece of config
As long as you have these functions in the shape of (f config raw-data)
and the functions are pure - yes you can memoize those. Keep in mind that the memory usage will grow linearly with the number of invariants so it's speed/memory trade-off.
i'm a relative macro noob, but i'm trying to write some wrapper macros around spec and finding it... challenging. specifically, i'm trying to dynamically generate the keys that i want to define specs for, and i'm being confronted with s/def itself being a macro, and not capable of understanding variables example, this works:
(defmacro def-spec-test1 [k]
`(s/def ~k string?))
the macroexpand shows that it is forwarding to the s/def macro, and properly expanding k
into the argument i provide.
however, for sake of demonstration, let's say we want to dynamically generate some keys, or reassign them:
(defmacro def-spec-test2 [k]
`(let [k# ~k]
(s/def k# ~string?)))
this then expands into
(let* [k__29626__auto__ :foo/bar] (clojure.spec.alpha/def k__29626__auto__ #function[clojure.core/string?--5410]))
which makes sense but of course is not what i intended. i suspect there's probably a dynamic inside the let
block that i'm unaware of, but is it possible at all to do something like this ?you can generate keys inside macro but outside of expansion form
(defmacro def-spec-test2 [k]
(let [key (generate-key k)]
`(s/def ~key ~string?)))
where generate-key
is a function returning a keyword
Is anybody here using any of the ‘complementary’ util libraries, such as Specter, Medley, or Tupelo? If so, how’s it working out for you?
Using Specter on the backend for a few tasks, works just fine. Initially was using it on the frontend as well, but stopped because its size is unacceptable for something that works in browsers.
I've seen a bunch of places that just copy the functions of interest (such as map-vals
from medley).
alright, thanks 🙂
the size aspect is interesting.. I hadn’t considered that
Does anyone here have experience with DynamoDB and https://github.com/cognitect-labs/aws-api ? I am using the Scan
operation, but I get Items
in the response in the DynamoDB format, was wondering if there was a simple way of unmarshalling like in the JavaScript sdk?
might consider using https://github.com/Taoensso/faraday or reviewing it to see how it handles the problem
ok, will give it a try, thank you!
Amazon provides augmented SDKs for certain services (some examples are KMS, DynamoDB, Kinesis). Those service-specific libraries handle service-specific concerns (e.g. key-stretching and caching in KMS, consumer state management in Kinesis)
aws-api
does not attempt to handle things that are not written down in Amazon's API service descriptors. The DynamoDB storage payload format is not in those descriptors.
you can make a helper function that converts a clojure map {:foo "bar"}
into
{"foo" {"S" "bar"}}
, which Dynamo payloads require and vice versa
AFAIK, this is the only special handling that DynamoDB requires (besides respecting rate-limits, which applies to all AWS apis, but moreso to DDB) @thedanpetrov Happy to continue discussion in #aws
A History of Clojure by Rich Hickey https://clojure.org/about/history
Ooo I’ve been looking forward to reading this for some time! 👀
Cool; publicly available!
Is it possible to use derive
for multimethods when the accessor is a string?
e.g. (derive "square" "rect")
Or if not, is there an efficient way to coerce the string accessor to a keyword? e.g. (get-area {:shape "square", :w 10, :h 10})
and have that use the (defmethod get-area :rect (fn [{:keys[ w h] (* w h ) }
if you need to derive based on a string in the data, I'd call keyword inside the defmulti dispatch function
because derive
needs a keyword or symbol, it doesn't work with string
eg. (defmulti get-area (fn [{:keys [shape]}] (keyword shape)))
it seems it needs a fully qualified keyword though?
(defmulti get-area (fn [{:keys [shape]}] (keyword (str *ns*) shape)))
(derive ::square ::rect)
(defmethod get-area ::rect (fn [{:keys[ w h] (* w h ) })
(get-area {:shape "square" :w 100 :h 100})
without ::
i get java.lang.AssertionError: Assert failed: (namespace parent)
when trying to use derive
well, you added the ns to the keyword in the defmulti - are you sure it doesn't work without it?
oh I see, derive rejects a non-namespaced keyword, I'd forgotten this!
What's a nice way to def with metadata? I want to re-export some functions from a different namespace.
you can specify a metadata map as an arg to intern, you could literally apply the metadata of the original to the re-export on definition
user=> (intern *ns* (with-meta 'j (meta #'clojure.string/join)) clojure.string/join)
#'user/j
(ins)user=> (doc j)
-------------------------
user/j
([coll] [separator coll])
Returns a string of all elements in coll, as returned by (seq
coll),
separated by an optional separator.
nil
*edit - better examplebut re-exporting has many gotchas, and it's often more straightforward to design your namespaces differently rather than having a facade
what about this? (haven't tried it) https://github.com/ztellman/potemkin#import-vars
@noisesmith > it's often more straightforward to design your namespaces differently rather than having a facade Can I dig deeper in this line of thinking somewhere?
this discussion covers it well I think, with @tbaldridge representing the viewpoint I'd advocate https://groups.google.com/forum/#!msg/clojure/Ng_GXYSrKmI/q0cxahB3BAAJ
you will likely recognize a few names in that discussion
also my example with intern
above works around the complaints in the thread about go to source finding the wrong location, and transitive dependency bugs
(though I don't even recommend that - I just think it's better than the magic import-vars attempts)
that's not what is advocated in that thread by Timothy at all
You don't see how the consequence would be larger namespaces? (I don't have a problem with it)
> Anything not in the API that should be unpublished to users is in other namespaces that are imported and wrapped by vars in the main namespace. the difference is that it's done by making a new definition defining the API, not re-exporting the var from another ns
the idea of having a layer that doesn't expose details that you don't want people relying on is fine (it's a fundamental principle of structured programming) - the difference is a tactical one, the mechanism for doing that layer
Is there a more reasonable way to add a self-referencing multi-arity multimethod than to either:
- Create a separate multi-arity function and then use that function, e.g. (defmethod f :x ([_ a] (other-f a)) ([_ a b] (other-f a b)))
- Call (.addMethod multifn ...)
manually
?
And I'm not sure about the second option - feels like clojure.lang.MultiFn
may be an implementation detail.
yeah, I would find that very suspicious
what is "self-referencing' about this?
I meant turning something like
(defn x
([a] (x a 1))
([a b] ...))
into a multimethod.if every arity calls other-f
(defmethod f :x [_ & args] (apply other-f args))
I think that aligns precisely with the intent
I think you really want to do this in the dispatch method
what would that look like?
or I guess I would probably wrap it around the multimethod
To make it more concrete - I'm adding a new function to HoneySQL. Yet another alternative is to use the ugly [x y & args]
arity and check the args
manually in the body for the third value.
but you could do something like this:
(defmulti foo (fn [& args] (second args)))
(defmethod foo nil [a] (println "1 arity" a))
(defmethod foo :default [a b] (println "2 arity" a b))
(foo 5 2)
(foo 5)
I don't know what you want to dispatch on
but multimethods that take varargs can dispatch to methods with different arities
well you can invent any dispatch value you like and tag it
if you are extending honeysql, the multi dispatch isn't actually something you can control right?
are you doing different things for different arities or just patching missing values?
they can, although I don't think that helps you in any way here
@U064X3EF3 Your question about different things made me realize that I can just wrap a single-arity function in a multi-arity multimethod:
(defn other-f [a b]
...)
(defmethod f :x
([_ a] (other-f a 1))
([_ a b] (other-f a b)))
Yeah, definitely bedtime.no idea why you wouldn't do that in other-f itself
@U0NCTKEV8 Doesn't work: "Unable to resolve symbol: f-x in this context".
@U064X3EF3 Sorry, no idea what you mean. I don't call other-f
anywhere. The library code calls f
for me, I don't control it.
user=> (defmulti f :foo)
#'user/f
(defmethod f :x other-f
([_ a] (other-f a 1))
([_ a b] (other-f a b)))
#object[clojure.lang.MultiFn 0x7b44b63d "clojure.lang.MultiFn@7b44b63d"]
user=>
user=> (defmulti f (constantly :x))
#'user/f
(defmethod f :x other-f
([_ a] (other-f a 1))
([_ a b] (other-f a b)))
#object[clojure.lang.MultiFn 0x301d8120 "clojure.lang.MultiFn@301d8120"]
user=> (f 1 2)
Execution error (StackOverflowError) at user/eval140$other-f (REPL:2).
null
user=>
So the right way to do this would be to
(defmethod f :x other-f
([_ a] (other-f nil a 1))
([_ a b] (do-whatever-to a b)))
OK, I still don't get why
(defmethod f :x f-x
([x] (f-x x :y))
([x y] (println "f-x" x y)))
gives me Unable to resolve symbol: f-x in this context
when I'm trying to use it as (f :x)
.The next to last code block with _
and other-f
works. The last code block with f-x
doesn't. Restarted REPL before trying either.
$ clj -r
Clojure 1.10.1
user=> (defmulti f (fn [x & args] x))
#'user/f
user=> (defmethod f :x f-x ([x] (f-x x :y)) ([x y] (println "f-x" x y)))
#object[clojure.lang.MultiFn 0x72458efc "clojure.lang.MultiFn@72458efc"]
user=> (f-x :x)
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: f-x in this context
user=> *e
#error {
:cause "Unable to resolve symbol: f-x in this context"
:via
[{:type clojure.lang.Compiler$CompilerException
:message "Syntax error compiling at (1:1)."
:data #:clojure.error{:phase :compile-syntax-check, :line 1, :column 1, :source "NO_SOURCE_PATH"}
:at [clojure.lang.Compiler analyze "Compiler.java" 6808]}
{:type java.lang.RuntimeException
:message "Unable to resolve symbol: f-x in this context"
:at [clojure.lang.Util runtimeException "Util.java" 221]}]
:trace
[[clojure.lang.Util runtimeException "Util.java" 221]
[clojure.lang.Compiler resolveIn "Compiler.java" 7414]
[clojure.lang.Compiler resolve "Compiler.java" 7358]
[clojure.lang.Compiler analyzeSymbol "Compiler.java" 7319]
[clojure.lang.Compiler analyze "Compiler.java" 6768]
[clojure.lang.Compiler analyze "Compiler.java" 6745]
[clojure.lang.Compiler$InvokeExpr parse "Compiler.java" 3820]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7109]
[clojure.lang.Compiler analyze "Compiler.java" 6789]
[clojure.lang.Compiler analyze "Compiler.java" 6745]
[clojure.lang.Compiler$BodyExpr$Parser parse "Compiler.java" 6120]
[clojure.lang.Compiler$FnMethod parse "Compiler.java" 5467]
[clojure.lang.Compiler$FnExpr parse "Compiler.java" 4029]
[clojure.lang.Compiler analyzeSeq "Compiler.java" 7105]
[clojure.lang.Compiler analyze "Compiler.java" 6789]
[clojure.lang.Compiler eval "Compiler.java" 7174]
[clojure.lang.Compiler eval "Compiler.java" 7132]
[clojure.core$eval invokeStatic "core.clj" 3214]
[clojure.core$eval invoke "core.clj" 3210]
[clojure.main$repl$read_eval_print__9086$fn__9089 invoke "main.clj" 437]
[clojure.main$repl$read_eval_print__9086 invoke "main.clj" 437]
[clojure.main$repl$fn__9095 invoke "main.clj" 458]
[clojure.main$repl invokeStatic "main.clj" 458]
[clojure.main$repl_opt invokeStatic "main.clj" 522]
[clojure.main$repl_opt invoke "main.clj" 518]
[clojure.main$main invokeStatic "main.clj" 664]
[clojure.main$main doInvoke "main.clj" 616]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.lang.Var applyTo "Var.java" 705]
[clojure.main main "main.java" 40]]}
user=>
OK, then I don't understand.
And then why does the code with other-f
work? It seems to be almost the same.
That name is bound to just to the function that implements that method, so invoking it does not go through the multimethod
Whoa, hold on. letfn
and defn
made me sure that providing a name to fn
would also make it possible to self-reference it. That's probably the first really suprising unpleasant fact that I learn about Clojure.
No, I take it back - my brain is half asleep and fn
does indeed make it possible to self-reference it.
So, macroexpanding that defmethod
gives me
(clojure.core/fn f-x
([x] (f-x x :y))
([x y] (println "f-x" x y)))
And it works by itself.
But it doesn't work if used in .addMethod
..?@seancorfield hi, I am trying to use your depstar
library
this work
clojure -A:depstar -m hf.depstar.uberjar app.jar
java -cp app.jar clojure.main -m api.core
with AOT not:
clojure -Spom
clojure -A:depstar -m hf.depstar.uberjar app.jar -v -C -m api.core
java -jar app.jar
Error: Could not find or load main class api.core
Caused by: java.lang.ClassNotFoundException: api.core
Maybe this is too late hour, but I don’t see what I am doing wrong.
Besides of that do you think this is good idea to use AOT on production? Considering I am running jar
file with the same version of java, because I use Dockerfile.while I am thinking about paying model dependent on start app time and processing time AOT start to be more interesting. I can build app even with Java 14 or 11
the java version shouldn't make a difference - clojure doesn't use javac to emit bytecode
well, in theory it could make a difference because the compiler uses reflection to resolve methods
and sometimes you get subtile behavior changes across jvm version like arrays of methods being returned in different orders, etc
and new java features actually let you compile totally different classfiles for different java versions
You're the second or third person to trip over this in the last week so I'm going to update the depstar
README to clarify that.
If you create a project with clj -A:new app api.core
then clj-new
adds (:gen-class)
for you, just like lein new
and boot new
do for app
projects.
(if you use clj-new
to create your app
project, your deps.edn
will already have an :uberjar
alias to save you some typing)
Do you recommend to use AOT in Dokcerfile so when I am sure java version is the same? I am not an expert in this topic. totally. It can gives me better performance maybe, so it sounds interesting. I think I didn’t try AOT before, so I have no opinion.
Whole-application AOT in order to improve startup time is a valid use.
AOT is preferred on the operations side of things, especially with serverless and containerized applications in the JVM.
because you are generating and persisting bytecode to disk it can cause issues with clojure's more dynamic features. you can usual avoid those issues if do things like only aot for deployed artifacts and not locally while testing and running repls
but then sometimes things will load fine in clojure, but break when you aot them, so then sometimes you check things in because it looks good and works locally, and then go to build and boom
While I hear a lot of this things I don’t know any specific case which really happen to somebody
ah in the context of PDF generation… wkhtmltopdf generate much different PDF (everything looks so bad) in docker, than in my local environment. Even while this is the same version of wkthmltopdf. I think I will try this alternative solutions faster, than I thought 🙂
what distro? i've hit this before in node. it's tricky to get all the right fonts and styles in place.
this is much more broken, than only fonts. wrong sizes, broken pages, wrong width of content
My first suspicion would be that you need to install all the required fonts, as it is using bad fallbacks. Some of those fonts might be non-free so unavailable from debian, you might need to hand install.