This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
I am glimpsing a book and saw this part say our database table contains the following fields unit "" street "1 Main Street" city Toronto post_code nil country "Canda" Expected output 1 Main Street, Toronto, Canada Clojure code
user=> (defn concat-fields [& fields]
(clojure.string/join ", " (remove empty? fields)))
#'user/concat-fields
user=> (concat-fields "" "1 Main Street" "Toronto" nil "Canada")
"1 Main Street, Toronto, Canada"
I am surprised, that Clojure is that easy to handle this.
I used ChatGPT to generate diff languages below as comparison.
Java
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class AddressFormatter {
public static String formatAddress(String unitNumber, String street, String city, String postalCode, String county) {
return Stream.of(unitNumber, street, city, postalCode, county) // Create a stream of address parts
.map(Optional::ofNullable) // Wrap each part in an Optional
.filter(Optional::isPresent) // Filter out absent values
.map(Optional::get) // Extract the present values
.filter(s -> !s.isEmpty()) // Filter out empty strings
.collect(Collectors.joining(", ")); // Join using a comma and a space
}
public static void main(String[] args) {
String formattedAddress = formatAddress("123", "Baker Street", "London", "NW1 6XE", null);
System.out.println(formattedAddress);
}
}
Python
def format_address(unit_number, street, city, postal_code, county):
# List of address components
components = [unit_number, street, city, postal_code, county]
# Filter out None or empty strings
filtered_components = [comp for comp in components if comp]
# Join the components with a comma and a space
return ', '.join(filtered_components)
# Example usage
formatted_address = format_address("123", "Baker Street", "London", "NW1 6XE", None)
print(formatted_address)
Ruby
def format_address(unit_number, street, city, postal_code, county)
components = [unit_number, street, city, postal_code, county] # Array of address components
# Remove nil values and empty strings
filtered_components = components.compact.reject(&:empty?)
# Join the components with a comma and a space
filtered_components.join(", ")
end
# Example usage
formatted_address = format_address("123", "Baker Street", "London", "NW1 6XE", nil)
puts formatted_address
So according to this book, I simply have to learn the functions in standard library. Once I learn to associate data transformation with specific functions, many problems can be solved by simply putting them together in the right order.
Is this how Clojure works? If yes, quite impressive though.
Anyway I noticed if postal code is integer, passing in will throw an error below.
user=> (concat-fields "" "1 Main Street" "Toronto" 10010 "Canada")
Execution error (IllegalArgumentException) at user/concat-fields
Don't know how to create ISeq from java.lang.Long
How do I solve this while remain concise? or do I have to check if each field integer and convert to String?You might coerce things to strings before calling empty?
on them like (join ", " (remove (comp empty? str) fields))
Be wary of conciseness when working with code that other people will read, its definitely possible, and likely, to write something in Clojure that only you will understand
But in general yes a deep understanding of clojure.core will help you write concise solutions that would have many more lines of code in other languages
wrong order I think, map str then remove empty 😄
You can avoid the clojure.string require with interpose (->> fields (map str) (remove empty?) (interpose ", ") (apply str))
Tested as suggested Example 1
user=> (defn concat-fields [& fields]
(clojure.string/join ", " (remove #{"" nil} fields)))
By the way what is #{"" nil} means? It seems like a set. But how does it replace empty?
Example 2
user=> (defn concat-fields [& fields]
(clojure.string/join ", " (remove (comp empty? str) fields)))
Example 3
user=> (defn concat-fields [& fields]
(->> fields (map str) (remove empty?) (clojure.string/join ", ")))
Still lost on the comp and interpose, too beginner 😅.
However Example 3, seems quite easy to understand if the code gets complex. I assume this is the way used by experienced Clojure dev?Well, an actual date formatter in the wild would probably be a little more involved. But yeah, the form of that looks pretty good IMO
You would probably want to be more structured in what you'll accept as address fields for instance
Yeah, in all these other impls, you're referring to the fields by name, Stream.of(unitNumber, street, city, postalCode, county)
, etc. So the semantics aren't exactly the same between the two
sorry the set example was wrong, I edited my message
concat-fields
is an interesting contrast in how you can do the formatting without referring to any names in the data, which can be great. But most of the time for addresses you'll want to refer to the fields in and out explicitly
(comp empty? str)
is equivalent to (fn [x] (empty? (str x)))
And, honestly, for all those examples, because there's only a half dozen components, sequencing over them is pretty unnecessary. You can just go from structure to structure directly, for most of those impls
Is there a specific example for this @U050PJ2EU -> But most of the time for addresses you'll want to refer to the fields in and out explicitly
@U02FVPF04A1 thanks for sharing too.
You're more likely to something like this in the wild:
(defn normalize-addres [{:as ctx :keys [unit-number, street, city, postal-code, county]}]
(str (when unit-number
unit-number)
(when street
(str (when unit-number ", ") street))
(when city
(str ", " city))
(when postal-code
(str ", " postal-code))
(when county
(str ", " county))))
#_
(normalize-addres
{:unit-number 10 :street "Mulberry" :city "Downtown" :postal-code 11111 :county "Parish"})
;=> "10, Mulberry, Downtown, 11111, Parish"
Some map data is coming through on some request map context and you're taking that context that may have lots of data in it and taking out only the necessary bits to build the address. Then you build that thing very explicitly. No need to get fancy.
I'd probably refactor that above example a few times before settling on something, but that's a general gist
I see. So I guess the way of coding, is quite similar to other OOP languages, as they are also being explicit on this too.
Explicitness and implicitness are tools you have to use carefully and we use either/or depending on the situation
That fact that we're letting our address fields flow parallel with unrelated data in the context, we're being very implicit. But for our address formatter, we're very explicit. And that above function should probably have a lot more failure modes, for when various of those fields are missing.
One thing that's a bit of a gotcha if you aren't aware of these aspects of Clojure (I got bit by this early on):
1. Sets implement clojure.lang.IFn
which means they can be called as functions, in which case they return the argument if it belongs to the set, and nil
otherwise.
2. nil
and false
are the only "falsey" values in Clojure, and all other values are "truthy".
This means that sets can only be used as predicates when they don't contain falsey values in them. contains?
is the right thing to do in such cases.
Wonder the ClojureStream podcast will have more contents this year or resumed? Last podcast was back in 20th Dec 2023. Honestly, quite enjoyed listening to it as a beginner. https://clojure.stream/podcast
I recommend https://clojuredesign.club/ which has been quite active this year as well as having an extensive back catalog. Discussion in #CKKPVDX53
@U05254DQM Any idea whether I can run this podcast in spotify? I can't seem to find it inside.
I cancelled Spotify a long time ago. The podcast is available for free in YouTube Music and several other places. There's also a link on the website that should work with podcast players. Or you can listen on the site too.
@U8A5NMMGD Is the podcast on break while you work on course material?
@U05254DQM can't seem to find in youtube music. I guess only way is to watch on website. Usually I listen podcast while driving. Not sure the experience like listening from a mobile browser 😅
I suspect with a lot of these technical podcasts, you might want a REPL open to try out some of the things they talk about...😁
@U04V70XH6 hmm, its audio though, i hope i know what to do when i open the REPL 😅
I only listen to podcasts on a desktop where I can get live captioning -- I can't "listen" to podcasts without that -- so I always have access to a REPL. Clojurians should always have access to a REPL...
Eric, I am unsure how to add it to YouTube music. I had the podcast via Google Podcasts app, but that shuts down on 24 June, but gave me an option to move the subscription to YouTube Music. Most Podcast apps should allow adding the podcast by it's https://clojuredesign.club/index.xml.
https://clojuredesign.club/ is a good one to listen in the car, it's very much about thinking about how to approach the design of Clojure. No repl required while listening, although they do cover lots of repl workflow tips that can be used once you get to your desk.