This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-02-17
Channels
- # announcements (1)
- # aws (40)
- # babashka (37)
- # beginners (305)
- # chlorine-clover (15)
- # cider (5)
- # cljs-dev (40)
- # clojure (62)
- # clojure-europe (13)
- # clojure-nl (4)
- # clojure-spec (10)
- # clojure-sweden (2)
- # clojure-uk (59)
- # clojurescript (9)
- # core-async (13)
- # cursive (5)
- # data-science (2)
- # datascript (2)
- # datomic (29)
- # emacs (8)
- # fulcro (58)
- # lambdaisland (9)
- # leiningen (2)
- # lumo (3)
- # mid-cities-meetup (1)
- # midje (1)
- # off-topic (28)
- # shadow-cljs (32)
- # spacemacs (3)
- # sql (5)
- # tools-deps (1)
- # tree-sitter (1)
- # vscode (2)
- # yada (2)
Sometimes I feel a bit bad that I'm using ->
with too many nested ->>
like situations where almost everything is ->> except for one ->. But heck, I still do it
Mixing threading macros makes code hard to read IMO. I try to follow Stuart Sierra's advice to the letter.
fellow beginners: if you are interested in using the Component lib as I have been the past couple of days i highly recommend this video as well as the skeleton i linked before
This example uses Component and has the Application Component injected into the request: https://github.com/seancorfield/usermanager-example
@seancorfield I just tried your user-manager repo, it's great to approch Component, thx.
A beginner question : How can I handle the code change like lein-ring > auto-reload?
?
OK I find a direction thank to your doc strings and the Component doc https://github.com/stuartsierra/component/blob/master/README.md#entry-points-for-development
@UFBL6R4P3 I never used lein ring
because I don't like the "magic" of plugins: I prefer to start/stop the server directly in code (as that example shows) so it is easy to start/stop inside a REPL. I also avoid all "watch"-based workflows and auto-reload/auto-refresh. That stuff can easily get people into a mess because when it breaks (and it often does for people), most folks have no idea how to fix it and end up restarting their REPL anyway.
I'm with Stu Halloway and Eric Normand on avoiding all that stuff and working instead with a very simple REPL-based workflow and a very tight edit-eval cycle (often without even saving files: I eval every single change I make, as I make it).
@seancorfield One more time, thank you for your workflow details. In mean time, I had also find some information from you on Clojureverse 😉 https://clojureverse.org/t/ring-with-clojure-tooling/4732/3?u=prestancedesign
So each controller can get at the application, the database, whatever.
@tjb had you seen my usermanager repo before?
OK, cool. I didn't scroll back far enough to see if it was a source 🙂
Glad I could help!
(our scroll back is pretty short!)
Hi I'm enjoying clojure more and more in learning. I ask about a contat/conj result. In easy first step, I wanted to get a string array for command line like.
user=> (concat (conj ["/usr/bin/ssh"] host) cmdline)
("/usr/bin/ssh" "" "exit" "0")
I needed to add arguments from array. Then it was unexpected output.
user=> (concat (conj (concat ["/usr/bin/ssh"] args) host) cmdline)
("" "/usr/bin/ssh" "-p" "22" "exit" "0")
after all I got my wants
user=> (concat (concat (concat ["/usr/bin/ssh"] args) [host]) cmdline)
("/usr/bin/ssh" "-p" "22" "" "exit" "0")
I wonder what is important when using concat/conj.
thanks@seancorfield do you mind if i ask you a question regarding jdbc.next
here?
@tjb Fire away!
@naoki86star The key thing is that conj
adds items in the "appropriate" way for the type of collection. conj []
will add at the end. conj ()
will add at the beginning. concat
produces a list, not a vector.
Thanks for your advice.:man-bowing: First I started to use conj
just on two word. I agree your example using concat
simply when gathering some parameters.🙂
my config looks like this
(def ^:private pg-db
"Connect map we will use to connect to PostgreSQL"
{:dbtype "postgresql"
:dbname "patentfitness"
:host "localhost:3306/patentfitness"
:user "USER_HERE"
:password "PASSWORD_HERE"})
(defn get-patent
"Get a patent"
[req]
(let [db (-> req :database)]
(println db)
(sql/query db ["select * from patent"])))
For your final example @naoki86star you could say
user= (concat ["/usr/bin/ssh"] args [host] cmdline)
You need to add postgresql driver explicitly into your dependencies
org.postgresql/postgresql {:mvn/version "42.2.9"}
@tjb yup, like @valtteri says: you need the PostgreSQL driver as a dependency.
next.jdbc
doesn't add any database drivers -- you have to do that.
Also, your :host
is wrong. It should just be the hostname. You have the port and the database name in it too.
PostgreSQL uses port 5432 by default. MySQL uses port 3306.
I think you want
(def ^:private pg-db
"Connect map we will use to connect to PostgreSQL"
{:dbtype "postgresql"
:dbname "patentfitness"
:host "localhost"
:user "USER_HERE"
:password "PASSWORD_HERE"})
everything works, thanks @seancorfield and @valtteri!!
If there's anything that can be clearer in the next.jdbc
docs, feel free to open issues on GitHub about it.
Hi , now studying on repl. When I changed to other namespace on repl, I annoyed for a while.
app.core=> (use app.sample)
Syntax error (ClassNotFoundException) .....
I've found to put a apostrophe before name-space string. I wonder what is this apostrophe? (ns app.sample)
is okay...The apostrophe is also called a quote, and 'expression
can also be written (quote expression)
. Such an expression is prevented from being evaluated.
use
is a function, and like all functions in Clojure, all arguments are evaluated before the function is called.
ns
is a macro, which you can't tell just from looking at the name, but using (doc ns)
will tell you if it is a macro. Macros can have their own custom rules for whether they evaluate their arguments, or not. ns
does not, and this is the difference between needing a quote for use
, but not for ns
I see. 💡💡 Thanks for unveiling my details that I didn't understand! These help me not only to let me know more but also will help to solve similar my trouble.🙂
Hey! Quick question about what's what I should write in my ns
declaration when using Java interop.
The following appears to work:
(ns test.java-interop)
(defn ->pgobject [v]
(doto (org.postgresql.util.PGobject.)
(.setType "json")
(.setValue (json/write-str v))))
(->pgobject {:x 1 :y 2})
;; => #object[org.postgresql.util.PGobject 0x133a7eee "{\"x\":1,\"y\":2}"]
is this what I should be doing, or should I add some :import
things in the ns declaration?You can do it like that, because java classes are automatically imported.
The purpose of :import
is to avoid typing package name multiple times.
@seancorfield I just tried your user-manager repo, it's great to approch Component, thx.
A beginner question : How can I handle the code change like lein-ring > auto-reload?
?
Hi everyone,
how can you write tests which pass values between each other?
do you have a pseudo code example of your situation? It sounds like something I would normally avoid doing - I always learned that tests should be strictly independent of one another.
rest service for example: 1- create a record 2- update a record 3- get record 4- delete it
you need to get the id from step 1.
to pass to 2, 3 and 4
I try and do this by first testing the creation
Then assuming the creation works
Use the creation to test updating
And the rest
Not sure if there's a better way to do it though
IMO you have 5 tests there, one for each of your 4 steps and an end-to-end test, each can be run in their own separate database transaction (setup/test/rollback) EDIT: If you don't own the REST service can each step just go into one big test?
What happens if you cause a bug in test 2? Tests 3 and 4 will be flagged as failing as well because they depend on 2. If you're working on the Delete functionality do you now have to run Test 1, 2, 3 every time you change Delete?
On the other hand, in order to test deletion, there actually needs to be some value in the db, and if that is not working, you won't be able to test deletion. But I second not trying to make tests depend on each other.
It comes down to what you're testing with each test, and why, which is annoying because it means you have to think really hard 🤯
Mm. I was thinking one might be testing "too low level" if one intends to test a db write, instead of a piece of functionality.
Fair enough. If it's a REST API you control there's probably DB crud tests already written!
Hi guys
I need help to where to start with clojure
I'd recommend to start with this MOOC http://iloveponies.github.io/120-hour-epic-sax-marathon/index.html Do all the exercises and you'd be fine.
i don't speak a very beautiful english 😄
Are you looking for a recommendation on a "getting started" guide or stuck on something?
https://www.braveclojure.com/foreword/ is a good online book to start with http://www.4clojure.com/ practice some of the fundamentals
I’m a little confused with threads. From what I read you can have 2 threads per core. I’m using core.async async/thread
. It seems I can run more than the math of 2x cores would allow. I would expect some kind of error here. What is it doing under the hood?
your hardware has a maximum number of threads it can run in parallel, simultaneously, but there is no limit to how many threads your hardware can run by juggling threads and switching between them quickly. In practice many threads are used for lightweight tasks that don't take much cpu time, such as waiting for input, so threads need to be able to be juggled to be useful
Three different concpts: 1. Your CPU has for example for hardware threads 2. Your OS runs hundreds of threads (one or more per app you're running) 3. With core.async you can use "green threads" that don't even use an OS thread (less overhead). In practice, you might be able to run hundreds of thousands of these.
@doby162 i looking for recommendation with getting started, books, etc,
Other than @tzafrirben's good suggestions, I would also suggest https://clojure.org/guides/getting_started But probably brave clojure first.
I'm migrating fom PHP, i belive i have difficulty
@levi.costa1 I come from PHP too. I begin to learning Clojure/ClojureScript since 8/9 month and the trip is awesome! So welcome! 🙂 Some links : • https://exercism.io/tracks/clojure (you can compare the results exercices with different language that you know like PHP for learning basics ) • If you're interested with front-end and SPA development, there are some videos for free : https://www.learnreagent.com/
Any cleaner way to get-specific-map
from vector-of-maps
depend on specific value
atm I do:
(first (filter #(= (:key %) val) [m1, m2,....])
but this is not readable IMO and looks bad in the project
using a thread macro?
(->> [{:key 1} {:key 2} {:key 3}]
(filter #(= (:key %) 2))
first)
you can also replace #(= (:key %) 2))
with a “real” functionit'll look better but still looking for some other way not to use the filter/predicate/first work (too much)
hmm i'll stay with this for now I guess
@USPQF75AS, the (first (filter ...))
pattern is very common in Clojure for what you're doing. I recommend using it, especially if you are a beginner, since you will see it often in other's code.
However, you may also be interested in the some
function, which returns the first truthy result of a fn you pass.
(let [ms [{:key 1} {:key 2}]
val 1]
(some #(when (= (:key %) val) %) ms))
=> {:key 1}
btw, is there any way to unpack
from a sequence? in python I'd do something like res1, rest2 = foo()
as in this case I'd want to pick more than 1 item (right now we are picking first
You can destruct a sequence if this is what you mean (I'm not very familiar with python)
(let [[a b c] [1 2 3]]...
Another sweet pattern I learned about recently, which is also more extensible, is
#(= (:k %) v) => (comp #{v} :k)
It's especially strong if you want to add other options the value can be, just add members to the set.
cons: always. In this case, creating a set. If v
is constant, however, you can define the entire composed function outside. Also, set lookup is slightly slower. You gain readability and flexibility, pay slightly with performanceCan someone give me an opinion on this style of writing functions using let down to the return? I've found myself doing this and it helps me organize the code conceptually as well, but I probably could have used some kind of macro to chain things as well I asume. Example:
(defn map-with-changed-element
"Returns a map with element identified by id modified by query-map"
[id maps query-map]
(let [args (walk/keywordize-keys query-map)]
(let [element (list-filtered-by-id id maps)]
(let [allowed-keys (select-keys args (keys element))]
(let [new-element (merge element allowed-keys)]
(assoc-in maps [(keyword id)] new-element))))))
Threading macros can be nice, but it is also very useful to have names for the intermediate values, just like you have done. But the let
form doesn't need to be nested, and using a single let
will make it more readable. Each binding in a let
can refer to the previous bindings.
(defn map-with-changed-element
[id maps query-map]
(let [args (walk/keywordize-keys query-map)
element (list-filtered-by-id id maps)
allowed-keys (select-keys args (keys element))
new-element (merge element allowed-keys)]
(assoc-in maps [(keyword id)] new-element)))
Oh, that's cool. I did not realize I didn't have to nest them. Thank you!
Sure! It is a nice feature.
@UTHL3A325 wait until you learn about multiple bindings in for
/ doseq
! it's really useful! (TLDR, you won't need to write nested loops)
At first glance this looks like a good opportunity for the threading macro https://clojure.org/guides/threading_macros
Are there any benefits of using defrecord
instead of a simple map
Let's say i'd like to implement a singly-linked-list
in clj.
I could either choose to define (defrecord Node [v next])
or do the same with a map {:v v1 :next n1}
Ofcourse it's more readable but with the clojure notion of representing entities as maps, how is defrecord any useful?
I don't know if what you're implementing specifically requires a singly linked list, but clojure already has one out of the box, the cons
cell.
(cons head tail)
returns the equivalent of
{:v head :next tail}
With the added bonus of already implementing the ISeq
interface, meaning first
and rest
work on it, returning the value and tail, respectively.there are a number of nuanced tradeoffs
records have a strong type, which can be useful to hook into protocol dispatch
@levi.costa1 I come from PHP too. I begin to learning Clojure/ClojureScript since 8/9 month and the trip is awesome! So welcome! 🙂 Some links : • https://exercism.io/tracks/clojure (you can compare the results exercices with different language that you know like PHP for learning basics ) • If you're interested with front-end and SPA development, there are some videos for free : https://www.learnreagent.com/
they are implemented as a class with fixed known fields - generally that can be more efficient in memory and field access than generic maps
but that also means modifications yield a new record instance, not a map with structural sharing - the impacts of that vary, it really depends whether that's better or worse
So as a rule of thumb, what should I go for
there is no one answer - it really depends on understanding the tradeoffs
i usually progress (using data around performance or evidence of some other form) from maps to records or types
for making a new data structure like a linked list, that's actually what deftype is designed for
(although I'm not sure why you wouldn't just use lists for that)
just needed a quick use-case before I post a question here 🙂
i'm actually trying to implement a zipper data structure
https://clojure.org/reference/datatypes has a lot more on record vs type
so i'd go for the record
thanks!
I have a SEQ of MAPS I want to drop EVERYTHING after ITEM X from the seq. how would you do that?
made it with reduce
@matthewlisp Maybe drop-while
helps? https://clojuredocs.org/clojure.core/drop-while
it doesn't because i don't want to check anything, i want to drop everything that exists after item X
take is problematic because i would have to specify a fixed number, and since i'm working with a result from a 3rd party service... i don't trust it
reduce can do it when i check for the last
value from the empty collection that it's using
Here is what @hindol.adhya said (take-while and add back) using concat
:
(let [coll [1 2 3 4]
val 3]
(concat (take-while #(not= val %) coll) [val]))
=> (1 2 3)
I like to write (take-while (complement #{val}) coll)
. It is a matter of preference I guess. Just throwing it out there as a possibility.
Yet another way: (take-while (partial not= val) coll)
there's a take-until jira ticket out there with a variety of impls
for a bunch of reasons (some mentioned there), I'd say odds are low that this will ever make it into core, so take what you need :)
Can you give an example of what you mean by type metadata? And what kind of data you would want to convert into that?
I think that I've misused the term, but I want something like this
{:a 0, :b "aa"} -> {:a Num, :b String}
You can call type
on any Clojure value to get its type and you can use types as values -- but you'll likely want to massage the various numeric types to a common base type.
using type
I get PersistentArrayMap
, but I want something more specific, seems like calling type
on each value would do what I want
there are one or two libs to infer specs from examples
which seems in the ballpark
@UTU1FCN73 Yeah, I meant you'd need to walk the data structure and call type
on the values of it.
Don't need a library, its a one liner:
(reduce-kv (fn[m k v] (assoc m k (type v))) {} {:a 0 :b "aa"})
How would I implement a spec that depends on another variable other the the actual value? For example, I’d like to define a spec for:
(defn first-element [sequence default]
(if (or (empty? sequence) (nil? sequence))
default
(first sequence)))
(s/fdef first-element
:args (s/cat :sequence seq? :default any?)
:ret any?
:fn ...)
In a static typed language (with some support to generics) I’d do something like:
first-element<T>(sequence: Seq<T>, default: T): T
you can check that in the fn spec here
but spec doesn't have parameterized specs
humnn got it! So do you thing any?
is a reasonable spec or is there a better way of representing default
in specs?
@fran.fhb Note that seq?
probably isn't what you want there: it means "does this thing implement ISeq
?" and lots of things that behave as sequences do not. Also, empty?
will return true
for nil
so you don't need both checks there.
You'll have to "step back" from static types when thinking about Clojure to some extent. Sequences (and collections) are often heterogeneous -- and Clojure does value-based equality checks on collections so you'll find, for example, that (= [1 2 3] (map inc (range 3)))
which may be surprising if you're thinking in terms of static types.
Also, first
calls seq
on its argument so it can return the first element of anything that is seqable?
and something that implements ISeq
is treated specially, as an optimization.
From the docs for first, I guess, I could assume coll?
would be a better predicate, right?
That checks whether something is an IPersistentCollection
-- seqable?
is more general.
user=> (coll? "Hello")
false
user=> (first "Hello")
\H
user=> (seqable? "Hello")
true
user=> (seqable? [1 2 3])
true
Sequences are an abstraction in Clojure, rather than a concrete type. Lots of things can be treated as sequences, because seq
works on a lot of things.
sequence?
might be closer, right?
@UEENNMX0T what is sequence?
I'm talking about built-in predicates that can be used in Spec.
user=> (seq? [1 2 3])
false
user=> (first [1 2 3])
1
this is a good example actually of exactly the kind of constraint we've been working on wanting to have better tools for in spec 2 around function specs
a big difference from types and parameterized types is that spec is much more about sets of values
the thing you really want to say here is that the result is exactly one of the values from the input
it's not just that the input is a "coll of ints" and the result is an "int". it's the result is one of that set of values in the input collection.
Ohh that makes so much more sense now, thanks! I’ll follow the development of specs closer! thanks!
Risking my head, I just have to ask anyone who knows more - reading the leaves and the wind, one conclusion would be that Clojure asymptotes towards something like what Haskell does... that is, a ghost in the machine that acts on function definitions (and stuff?) that tells you if you’ve messed up. JavaScript emerges as TypeScript. Python, bastion of no types, gets types. Clojure gets something. Is this all inevitable? Is this always the path?
Not sure I follow... Spec isn't a type system -- it's more powerful in some ways and it's available at runtime for data validation and conformance.
It's true that some languages are getting optional/gradual type systems either within the language or as a dialect -- but I don't think that's inevitable (or even desirable, in many cases). Clojure has a dynamic system for specifying predicates that hold for values at runtime which is rather different.
core.typed
is an example of a library that provides an optional, external way to "type check" Clojure code but it is a research project (and it is in the process of being fairly substantially reworked, as I understand it, based on lessons learned from earlier incarnations and research into how data types flow through code).
Another view is that both Python and Clojure do have types, but it is not the symbols that name parameters and local variables that you associate with types, but all values at run time do have types, and very restricted ways that one can be converted to others. Haskell, C, Java, and many other languages associate types with names, and endeavor to check at compile time that you only do operations on those names that are allowed on values/objects of those types.
Where Clojure and Haskell are more like each other than different, is their strong emphasis on manipulating immutable values using pure functions. Haskell is in many ways more strict than Clojure is about this -- you can very easily write code that mutates data in Clojure, without having to put a special type signature on it like you would have to in Haskell. Except for a few languages (e.g. Erlang, and I think maybe Elm and Elixir), most languages are what I would call 'mutable data by default', meaning that while they can have libraries implementing immutable data structures, those libraries are not the default used by most developers of those languages, so if you try to develop in those languages with mostly immutable data, you cannot use most of the libraries of the language.
Clojure variables are immutable by default. At least locals, and while vars are not, the convention is so strong here to not re-def that I'd consider it being immutable by default.
Are you saying that immerjs is a library that lets you use most existing JavaScript libraries, using immutable data structures?
If so, that surprises me. I would expect that in JavaScript, there are many libraries that assume objections, collections, etc. that you pass to them can be mutated, and would not work if you tried to pass them an immutable version of that object/collection/etc.
And that they are among the most widely used JavaScript libraries.
But I will be the first to admit to avoiding JavaScript programming myself, and passing this on second hand as heard from others.
immer.js doesn't prevent other JS code from modifying the "immutable" data -- it's just an API that implements an explicit transformation that doesn't modify the original, but instead produces a new version of the data.
@U0CMVHBL2 what library assumes that? if one such exists, is either a pretty bad/obscure or is some framework instead of a lib
What Andy means is that because JS primitives can modify data structures, you can't "just use a lib" that provides immutable data structures without avoiding passing that data to those primitives.
@U4R5K5M0A immer does not prevent any data from being modified by other code.
The data structures you use immer with are not immutable: it's just providing a formal API for expressing "change" that creates new versions of data.
If you express a change from A to B with immer, and then pass the result to an arbitrary JS library, nothing prevents that library from mutating B.
I think you're misunderstanding what Andy and I are saying...?
But JS libraries can and do objects you pass in to them. Immer doesn't prevent that.
Every single JS library you use would have to be written using Immer to avoid mutation.
libraries operations are like any other operation, if you don't want a library to mutate, you simply just use the library api inside immer
You're missing the point.
The data is not immutable. Any library code can migrate it.
Using Immer to avoid that is a programming discipline.
That's not immutability.
yes, if you adhere to immerjs, you have to use it eveywhere you process data, but I don't see how that is not immutabitliy, or that you can't use JS libraries because they are not compatible
the initial issue was that if you work an immutable lib in JS you can't use most libraries, this not true, as immer proves
We'll have to agree to disagree.
but immer is just a tool to prevent mutation to happen, it is a discipline. if you handover that structure to some lib, and it mutates something it destroys the assumption. as was said, that's not guarantee.
Exactly.
I guess a better way of saying what I am trying to say is that you can use immutable data structures in just about any programming language. In many of them, the language and/or commonly used libraries do not encourage it, so you are fighting the tide trying to consistently use immutable data.
programming with immutability means programming with absense of assignment operator I would say
e.g. pull in one more dependency that uses commonly used library X, and your carefully constructed land of immutability is now encroached upon.
With Clojure and Haskell, immutability is the default -- you are not fighting against the tide. Yes you can use Java libs in Clojure programs that assume mutability throughout, in both internal implementation details and in its API, but it isn't fighting against the tide to avoid pulling those in, in most Clojure programs.
@UBLU3FQRZ @seancorfield both of you have it backwards, if a library mutates, immer will give you a new version period. you don't have to adapt libraries to use immer
@U0CMVHBL2 sure, but immer makes this seamless, is just one api call and why is so good
"Immer automatically freezes any state trees that are modified using produce
. This protects against accidental modifications of the state tree outside of a producer. This comes with a performance impact, so it is recommended to disable this option in production."
It is unlikely I will use JavaScript any time in the near future. I have seen libraries in other default-mutable languages that expect to be passed mutable data structures, and part of the implementation is such that if you gave them some kind of defensive-copy-on-write data structure with implementations of operations like 'insert' or 'remove' on a list, they would keep using the original object's pointer/reference, not the copied one.
e.g. mylist.insert(new_elem), with no return value, is a common signature in many languages for inserting a new element in a list. The caller has no way to get back a new list, and assumes it can do further mutation operations on the one it was given.
The caller of such an API has no way to use a copied-on-write version in later calls.
(FWIW: Newer JS APIs such as Object.assign
and Object.freeze
make working immutably easier . But you are right in that mutation is pretty much the default in JS)
@U0CMVHBL2 this is not true for immer, you will always received a completely new list
Is there a JavaScript API call for some kind of list/collection that is commonly used, that looks something like 'mycollection.insert(new_element);`, that does not return another collection, and the caller can now use mycollection
to refer to the updated collection?
Not in immer, but something that might be completely outside of immer, but widely used in JavaScript code?
@UDF11HLKC yes, as others said you can achieve immutabililty more easily with modern JS, but immer makes this much less of a chore
If there is something like that in JavaScript, and some JS library X uses calls like that in it, and makes those assumptions, then do you believe you can call that library with a collection created via immer, and X will still work?
@U0CMVHBL2 yes, absolutely, that's the whole premise of immer
you can mutate all you want inside immer, but it will finally give you back a new version
..but you cannot work with the collection in regular way. you have do it in callback to produce function. that's very different mode of operation.
I think Immer is an exception as far as immutability and languages go. I see it as a trick to make existing JS code work when wanting to use immutable data structures. It's cool in that it does this with a very small amount of code by using proxies. But it's probably a mistake to generalize about the language issue based on the existence of Immer.
Immer does look interesting. What happens if you do this though:
const todos = [ /* 2 todo objects in here with their done = false */ ]
const nextTodos = produce(todos, draft => {
draft[1].done = true
})
todos[0].done = true
console.log(nextTodos[0].done)
// and
nextTodos[1].done = false
console.log(nextTodos[1].done)
If I understood the doc. The first one mutates, because immer only freezes the parts you proxied through produce. So its not making it fully immutable. And the second one throws an error in dev, and mutates in prod. In dev, it will have frozen that object, which means that if this is something you pass to a lib you don't own and which expects to mutate it, it will fail, which was @U0CMVHBL2 's concern I believe.My other question is, it seems the only advantage over ImmutableJS is that you can still use imperative mutation syntax to describe the changes which means better IDE support?
Every operation (including any libraries) you perform inside immer will produce a new version of the input collection
if you modify that result outside immer, of course it will mutate depending on the operations you make
if you want to never mutate the original collection and always produce a new one, you do all the transformation inside immer, including third party library calls
Right, so that's the problem. Its not immutable by default. This will work fine with React, because React chooses to push immutability, and has a lot of functional APIs.
Maybe for practical JS, its not a big deal. On the front end, you don't need that many libs anyways beyond React. But I think in Node you'd face that issue a lot more.
Of course is not immutability by default, its JS after all, immer is a way to achieve full immutability, at the cost of doing transformation inside a single function call
as long as you call the 3rd lib inside the immer container, the original collection won't be mutated, and you will receive a new version
Its possibpe that's the case, but that would mean there is something I didn't understand about how it works. From the docs, I don't get that impression though. It seems only calls to produce create a copy, but standard calls will still mutate (or throw in dev if frozen)
you can apply all the mutations you want inside immer, immer will make you sure the original input is untouched and after you have applied all the mutations/transformations,immer will give you back a completely new version of the final result
function transferMoney(person1, person2) {
produce(person2, draftState => {
draftState += person1.money
}
}
Though now I think you could do:
produce(person2, draft =>
{ transferMoney(person1, draft) }
Still requires a lot of care from your part. And might even require some careful knowledge of the inner working
A real transfer money would modify both, can produce work on more than one coll at a time?
function transferMoney(person1, person2) {
money = person1.money
person2.money += money
person1.money -= money
}
It doesn't seem it can, but maybe a future version could. If it added support for transactional produce like that as well, I'd say its a pretty great idea
Thanks for showing me. Its a clever trick. I think we might see more of this in the future.
Doesn't that example require writing transferMoney from scratch? If transferMoney was in a library that you didn't want to (or couldn't) modify, is this approach still usable for transferMoney as written?
yeah, it fixes the problems with immutablejs, you keep your native JS APIs (instead a learning a bunch a new ones) and don't have to transform between immutablejs objects and JS objects all the time for interop
If their performance claims are true, its definitly a big improvement. I can see it getting lots of adoption
Yes, if you're stuck using JS, or you want to use it for some reason, then immer is a good way to improve things.
of course, clojure is way better, but keeping in mind the constraints of JS, this is a clever and ergonomic solution
Right, but I wonder what else that could benefit. ClojureScript also struggles at the boundary between immutable collections and js interop
If ClojureScript colls could proxy themselves as if they were mutable like that only within the context of a special closure
Note that in spite of immer performing relatively well, it still requires a shallow copy of every object that has changed. So if you don't need to copy between clojurescript and native objects, for example, then you'll get better performance with clojurescript's persistent data structures, since they're optimized for this. There is a big difference between using optimized immutable data structures and just making shallow copies.
I mean, yes and no. Things are contextual. Since JS is single threaded, you can effectively operate in this transient mode at all times
And I was under the impression ImmutableJS implemented similar fast persistent data-structures
although immer uses structural sharing your point may be valid, the issue for converting between js objs and clojure data structures is so prominent that there are many third party solutions out there with different tradeoffs each one
If you use immer to pass some immutable object into some functions, and that thing you pass in is referenced a dozen places within existing mutable objects, the returned value is new, and not referenced from those dozen places?
I am not saying I want something that can do that. Just looking for the edges of what it does/doesn't do.
Yes, the returned value is new. That's what I meant by saying that it has to copy the objects that have changed. I mentioned the pitfalls to get more ideas of the edge cases.
@U0CMVHBL2 yes, inside immer you are working with a draft of the original input collection, whatever you do to that draft immer will returned a new collection with all the operations you applied to the draft