This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-04-07
Channels
- # babashka (31)
- # babashka-sci-dev (10)
- # beginners (64)
- # biff (11)
- # clerk (5)
- # cljdoc (2)
- # clojure (84)
- # clojure-boston (3)
- # clojure-conj (2)
- # clojure-europe (11)
- # data-science (19)
- # datomic (118)
- # fulcro (3)
- # graalvm (3)
- # hoplon (6)
- # inf-clojure (146)
- # instaparse (5)
- # lsp (13)
- # malli (3)
- # off-topic (13)
- # pedestal (5)
- # proletarian (5)
- # re-frame (14)
- # reitit (12)
- # releases (1)
- # ring (19)
- # scittle (2)
- # shadow-cljs (155)
- # slack-help (3)
Hello good morning, what is wrong with my code? Can anyone take a look, please? It is filtering vectors by string and if matches put it into another vector. One more thing, is it possible to write same filter code (of course working one 🙂 )with postwalk?
Missing (if ..)
in the let
:
(defn filter-vector-func [coll ?s]
(reduce
(fn [x y]
(let [[first second :as all] y]
(if (str/includes? (str/lower-case all) (str/lower-case ?s))
(conj x all)
x)))
[]
coll))
postwalk will let you map/filter/reduce, but it takes a bit more effort because it's designed to operate on trees. one thing i find difficult about approaching this problem is that i don't really know what you are trying to do. it's good that your have input and output, that helps a lot. some high level idea of what the function is supposed to do is pretty important, otherwise i can't tell if you are doing something wrong or not. for example, (clojure.string/lower-case all)
do you know that this line of code is converting vectors to strings? this is really strange and just looks like a huge bug, but i can't tell if that's your intention or not, so i have to assume a lot about what you want the code to do. i'll give you an example of what i think your code is trying to do, with some improvements.
(defn filter-vector-func [coll search-str]
(->> coll
(filter (partial some (fn [str-vec]
(-> str-vec
clojure.string/lower-case
(clojure.string/includes? search-str)))))))
(filter-vector-func my-vec "a")
here is my attempti assume that you want to look at all strings, and if an item in the coll has any matching strings against the search string to return it as a result
Hello, @U0LAJQLQ1. I just want to filter a vec by string and return the matches.
(defn transform-maps-to-vector [map]
(postwalk (fn [x]
(if (map? x) (into [] cat x) x))
map)
)
I wonder if that can postwalk usable or not. Here postwalk walks inside all elements in coll and if it is a map transforms their outer parentheses to vector. So if postwalk walks in all elements then I can filter them too? I just thought like that. I hope I am not wrong 🙂walking tends to be difficult when the datastructure is a bit ambiguous. one problem with the structure you are using is that the whole vector looks just like it's children (a vector of vectors). so there requires a lot of guards to prevent the walk from doing the wrong thing. i'm playing around with it right now, but it's not simple, requires a lot of debugging.
Oh, I just wondered. If it consumes time please don't make yourself busy with it. It was just an idea in my mind. Because I tried but these codes didn't work. By the way, thank you so much for your help 🙂
(defn filter-vector-func [coll search-str]
(->> coll
(clojure.walk/postwalk
(fn [form]
(let [form (if (vector? form)
(vec (keep identity form))
form)]
(cond
(and (vector? form)
(every? vector? form)) form
(and (vector? form)
(not (every? string? form))) nil
(and (vector? form)
(every? string? form)
(->> form
(some (fn [str-vec]
(-> str-vec
clojure.string/lower-case
(clojure.string/includes? search-str)))))) form
:else form))))
(keep identity)
vec))
one problem with walk is that it doesn't make a good filter. vectors with deleted items have nils in them, which is something that you have to guard against for future walks, and i have to make guards against very similar structures. maybe it would be better with core.match, but i'm not very sure about that
[nil
["name" "ali"]
["surname" "veli"]
nil
["name" "batu"]
["surname" "can"]]
and example of what the structure looks like at the last step of the walk without the filtering of nils (vec (keep identity form))
when a datastructure is good for walking, it tends to be very good, and can also be very hard to transform via functions made for flat structures. but in this case it's the opposite
walking tends to be good when you want to do something with the inner structure of your data, but not touch other parts, and the parts you want to touch are easy to identify, but hard to navigate to.
it's also good for full transformations from 1 structure to another, typically without deleting stuff (like transforming a JSON schema to a malli schema). core.async macros do a lot of walking transforming code.
you can make trees out of vectors or maps... i guess other things too, but in clojure this is the more common case. here is an example of a tools designed more for vector trees https://github.com/cloojure/tupelo/blob/master/docs/forest.adoc
walk is good for maps, typically because they have structure that is easy to identify. but a completely self similar map, or map tree with no similar structure at all, would be hard to walk on
Thats very cool, I hope I can lean Clojure better in the future. Every day I feel more excited 🙂
one of the most useful things i have used walking on is delaying value assignment in arbitrary data. i put placeholders (symbols) in my nested maps, and build up large trees, then use postwalk-replace to change my placeholders to real values (typically lots of dupe values, like dates, or db relationships)
well, if you are interesting in walking, then you are already getting your feet pretty wet. after a bit of time struggling with clojure things just seem to fall into place and your intuition takes over. doesn't take too long either
If possible(easy to reach etc) can you show me a basic example of that useful part that you mention it?
(->>
[
{
:a 1
:date 'created-at
:b {:c 2
:date 'created-at}
}
{
:f 1
:date 'created-at
:g {:h 2
:date 'created-at}
}
]
(clojure.walk/postwalk-replace {'created-at #inst "2000"}))
there is an example where walk shines, really good for letting you make structures that need data, and don't require you to have data when those structures are made.(jdbc/execute! ds
["select * from contact where city = ?" "San Francisco"]
;; a one-to-one or many-to-one relationship
{:schema {:contact/addressid :address/id}})
What data should this return? contains address table one row?That execute!
call will return a vector of all rows from the contact
table that match city = 'San Francisco'
When you use a tool that does datafy
/`nav` on data structures, you would be able to navigate from the :contact/addressid
column of each row of that result to the corresponding row(s) of the address
table.
Portal is an example of such a tool. Reveal is another, and Cognitect's REBL is the O.G. of these tools (released when datafy
/`nav` were added to Clojure 1.10).
Hi, how do I combine a protocol and a record into multiple different implementations?
Example:
• I’m trying to find concise way to implement different “operations”, like getX, getY, replaceX, etc.
• All operations have a set of fields (with the same structure) and an individual implementation for resolving their functionality
So I assume I can do a defrecord
for defining the data structure and do a defprotocol
to define the resolving function:
(defrecord Operation [prefix])
(defprotocol Resolvable
(resolve-operation [this args] "Resolve data for a specific operation."))
Now, how do I combine Operation
and Resolvable
into multiple different implementations? Like one with prefix get
and one with prefix replace
, each with a different resolve-operation.I don't quite understand what you are trying to accomplish. The way you've organized the protocol and types, each operation would have the same implementation of resolve-operation
, but would have a different prefix
that might be used in that implementation. You also need to provide an implementation for your record type
(defprotocol Resolvable
(resolve-operation [this args] "Resolve data for a specific operation."))
;; Provide an impl for this type
(defrecord Operation [prefix]
Resolvable
(resolve-operation [this args]
(println "Operation for prefix:" prefix "called with args:" args)))
(let [get-op (->Operation "get")
replace-op (->Operation "replace")]
(resolve-operation get-op)
(resolve-operation replace-op))
You may want different types for the operations, like GetOperation
and ReplaceOperation
possibly? Also, it is idiomatic in Clojure to leave data unadorned so that it can be processed using the Clojure standard library. You might be able to use the existing standard library's update
function and take the "operation" as a function argument rather than a symbolic argument that you use to lookup the correct behaviorSorry for the edits. To use a record you need to create an instance, either with (->Operation <args>)
constructor function or with (new Operation <args>)
and there is syntax sugar for new
using a .
: (Operation. <args>)
Hi @U052PH695, thank you feedback ☺️ I see, so I would always apply the protocol directly to the record… which forces me to define the data structure for each operation separately :thinking_face: (There are going to be more fields) Basically I’m trying to combine the definition of data and functionality at the same time, with the performance benefits of both:
(defprotocol Resolvable
(resolve-operation [this args] "Resolve data for a specific operation."))
(defrecord GetOperation [prefix parent-type field]
Resolvable
(resolve-operation [this args]
"doing something"))
(defrecord ReplaceOperation [prefix parent-type field]
Resolvable
(resolve-operation [this args]
"doing something"))
(def get-op (GetOperation. "get" "..." "..."))
(def replace-op (ReplaceOperation. "replace" "..." "..."))
But this doesn’t look nice at all, implementation is scattered and things are heavily repeated 😕
I could simply do this:
(def simple-get
{:prefix "get"
:resolve (fn [this args] "do something")})
(def simple-replace
{:prefix "replace"
:resolve (fn [this args] "do something")})
But I’m not sure whether putting functions into data like this is idiomatic.Using maps is idiomatic (records and protocols are usually reserved for interop and performance optimizations). There is nothing inherently wrong with putting functions in maps (except functions don't serialize well), but it may be a sign that you are thinking about the problem in an object-oriented way where your objects contain both data and behavior. I'd suggest possibly using maps for data and keeping your functions separate just taking the objects as arguments
(def simple-get
{:prefix "get"})
(def simple-replace
{:prefix "replace"})
(defn resolve-op [op args]
(let [prefix (:prefix op)]
(condp = prefix
"get" "get implementation"
"replace" "replace implementation"
"default implementation")))
(resolve-op simple-get nil) ;=> "get implemenation"
(resolve-op simple-replace nil) ;=> "replace implemenation"
At that point you may not even need simple-get
and simple-replace
. Maybe "get" and "replace" could be another argument to the resolve function. If you wanted this to be more open to extension and not have a hardcoded switch table, multimethods would be another approach to have multiple implementations
(defmulti resolve-op (fn [op args] (:prefix op)))
(defmethod resolve-op "get" [op args] "get implementation")
(defmethod resolve-op "replace" [op args] "replace implementation")
(resolve-op simple-get nil) ;=> "get implemenation"
(resolve-op simple-replace nil) ;=> "replace implemenation"
I hope that helps a little. If your approach of putting functions in maps get's you what you want then I would go with that for now and can change it later if need be.> So I assume I can do a defrecord for defining the data structure and do a defprotocol to define the resolving function:
>
Haven't looked too close at your sample code, but it sounds like you are thinking of it a bit backward:
Records can (optionally) extend protocols. So, defrecord
is one way of defining implementations for methods named by defprotocol
.
> There is nothing inherently wrong with putting functions in maps (except functions don't serialize well), but it may be a sign that you are thinking about the problem in an object-oriented way where your objects contain both data and behavior. > Highlighting this comment. Common confusion when moving from OOP is to try and find ways to attach behavior to data. The ideal in Clojure is to try your hardest to fully decouple all your data from any sort of behavior. Generally you just want plain maps and vectors. Then use plain functions to work with that data.
Indeed I think I haven’t grasped the concept of protocols and records yet. Initially I thought they are a nice way to predefine an abstraction for functionality and data, but @U90R0EPHA your way of putting it helped my understanding. I tried too hard to combine those too concepts to make both data structure and function definition reusable, but @U052PH695 your example showed me that it’s far better to have just the reuse of the data structure and keep the functionality separate. Thank you!
There are going to be a lot of operations, currently 9 with each implementation in 8-12 lines of code. So I think a multimethod is more idiomatic? Is it more idiomatic to have all 9 data creations and 9 multimethod implementations in the same file, or put each data and multimethod implementation in a separate file?
Lacking more context to go on, I can still say the most idiomatic thing is always going to be plain functions acting on simple data structures. Without more detail about what your actual goal is, nobody can really say whether multimethods would be a worthwhile upgrade from plain functions.
Also, a reminder from @U07S8JGF7. If you “own” the code base, you might just not need the extensibility that protocols and multi methods have to offer, closed dispatch (if/case/cond) is often much easier to reason about (and read when debugging) than the open dispatch ways (protocols/multimetods).
I'm a bit confused with this simple zipper. Can anyone explain why there are two nil
s in the sequence?
(->> [:a {} [:b {}] [:c {}]]
(z/zipper vector? (partial drop 2) into)
(iterate z/next)
(take-while (complement z/end?))
(map z/node))
;; ([:a {} [:b {}] [:c {}]] [:b {}] nil [:c {}] nil)
Guess it's caused by some design friction in the zipper library. https://clojure.atlassian.net/browse/CLJ-1317
I haven't really used zippers, so going off the docs here
https://clojuredocs.org/clojure.zip/zipper
I believe it is because the branch?
function will sometimes return true
when the children
function returns an empty sequence (no children). One of the internal functions in the zipper library must be calling seq
on the returned children (which returns nil
for an empty seq) and those nil
results are getting passed to your make-node
function (`into`).
This happens because [:b {}]
and [:c {}]
are vectors, so your code says it's a branch, but (drop 2 [:b {}])
returns an empty sequence. I think this code may do what you are intending?
(->> [:a {} [:b {}] [:c {}]]
(z/zipper (fn [x]
(and (vector? x)
(> (count x) 2)))
(partial drop 2)
into)
(iterate z/next)
(take-while (complement z/end?))
(map z/node))
;; ([:a {} [:b {}] [:c {}]] [:b {}] [:c {}])
Note that the docstring of clojure.zip/zipper asks that the children argument return a seq of children. The rest of clojure.zip, however, expects nil to be returned when there are no children, as evidenced by this problem.
One could argue that this behaviour of the rest of clojure.zip should be fixed, but I think it makes sense and is convenient. Perhaps the docstring should be adjusted, though.
Reading that Jira issue, it sounds like the docstring for zipper
is misleading and the children
function should return nil
instead of an empty seq if there are no children. So this code does what you want also I think
(->> [:a {} [:b {}] [:c {}]]
(z/zipper vector?
(comp seq (partial drop 2))
into)
(iterate z/next)
(take-while (complement z/end?))
(map z/node))
Yeah, not only is that part of the docstring innacurate, but your first solution seems to contradict the docs as well: > branch? is a fn that, given a node, returns true if can have > children, even if it currently doesn't.
I can't see the convenience in a children
fn that must return nil. ()
expresses the idea of "no children" pretty well, IMO.
But I guess technically the docs are consistent with themselves, since by definition, a seq can't be empty.
Ideally I think the zipper would call seq on the result for you. However, I doubt this will ever be addressed other than by improving the doc string because it would be a breaking change in behavior
What I'm doing wrong with this macro:
12 (defmacro launch-nested-repl-on-error [& body]
13 `(try ~@body
14 (catch Exception e
15 (swap! nesting-level inc)
16 (start-nested-repl e)
17 (swap! nesting-level dec))))
It expands to:
(try (conj {} 1) (catch java.lang.Exception playground/e (clojure.core/swap! playground/nesting-level clojure.core/inc) (playground/start-nested-repl playground/e) (clojure.core/swap! playground/nesting-level clojure.core/dec)))
What's wrong is that it tries to expand e
to playground/e
, but how can I tell it not to?
The error is Can't bind qualified name:playground/e
.The #
suffix means "use gensym to create a unique symbol"
Or you can force a fixed name with ~'e
(but it's more idiomatic to use gensym for local bindings).