I'm learning Clojure and working on a simple file parsing script.
I have a file in the form of:
pattern1
pattern2
pattern3
pattern1
pattern2
...
where each line has a few values (numbers) that I extract.
If I was to write this in Java for example, I would do something similar to:
Map<String, Integer> parse(String line) {
//using Optional in this toy example, but could be an empty map or something else to
//signal if first one was actually matched and the values are there
Optional<Map<String, Integer>> firstMatched = matchFirst(line);
if (firstMatched.isPresent()) {
return firstMatched.get();
}
//...do the same for 2 remaining patterns
//...
}
Now what would be a an elegant or idiomatic way to do something similar in Clojure?
I guess I can use cond, but since there's no binding in the test expression, I'll have to parse the line twice:
(defn parse
[line]
(cond
(re-find #"pattern-1-regex" line) (re-find...)
(re-find #"pattern-2-regex" line) (re-find...
I could also use if-let, but that will be a lot of nesting since there are 3 different options. Imagine with 7 different pattern how that would look like.
Any suggestions? Obviously Java solution is an imperative one and I can do a "return" whenever I want, so what would be the Clojure/FP way of dealing with this simple branching.
i would go with some simple function to return the first matched pattern, filtering over the patterns seq:
(defn first-match [patterns]
(fn [line]
(some #(re-find % line) patterns)))
this one returns the function, that would return the first match, testing the line:
user> (def mat (first-match [#"(asd)" #"(fgh)" #"aaa(.+?)aaa"]))
#'user/mat
user> (mat "aaaxxxaaa")
;;=> ["aaaxxxaaa" "xxx"]
user> (mat "nomatch")
;;=> nil
otherwise you could use some simple macro for that. maybe like this:
(defmacro when-cond [& conds]
(when (seq conds)
`(if-let [x# ~(first conds)]
x#
(when-cond ~@(rest conds)))))
user>
(let [line "somethingaaa"]
(when-cond
(re-find #"something" line)
(re-find #"abc(.*?)def" line)))
;;=> "something"
for the preceeding example that would expand to something like this (schematically)
(if-let [x__8043__auto__ (re-find #"something" line)]
x__8043__auto__
(if-let [x__8044__auto__ (re-find #"abc(.*?)def" line)]
x__8044__auto__
nil))
more examples:
user>
(let [line "nomatch"]
(when-cond
(re-find #"something" line)
(re-find #"abc(.*?)def" line)))
;;=> nil
user>
(let [line "abcxxxxxxdef"]
(when-cond
(re-find #"something" line)
(re-find #"abc(.*?)def" line)))
;;=> ["abcxxxxxxdef" "xxxxxx"]