Fork me on GitHub
#meander
<
2023-02-19
>
Carlo20:02:58

I have a question on how to make a match optional while still being able to capture the match if it's there. Example and description in 🧵

Carlo20:02:19

Here's some example data:

{:tag :note,
 :attrs
 {:default-x "148.88",
  :default-y "-10.00",
  :dynamics "71.11",
  :measure "1",
  :voice "P1"},
 :content
 ({:tag :pitch,
   :attrs {},
   :content
   ({:tag :step, :attrs {}, :content ("D")}
    {:tag :alter, :attrs {}, :content ("-1")}
    {:tag :octave, :attrs {}, :content ("5")})}
  {:tag :duration, :attrs {}, :content ("1")}
  {:tag :voice, :attrs {}, :content ("1")}
  {:tag :type, :attrs {}, :content ("16th")}
  {:tag :stem, :attrs {}, :content ("down")}
  {:tag :beam, :attrs {:number "1"}, :content ("continue")}
  {:tag :beam, :attrs {:number "2"}, :content ("continue")})}

Carlo20:02:04

Here's the pattern I'm using to match it, which makes good use of meander's $ operator:

(defn extract-essential-info [note]
  (meander/find note
    (meander/and
     {:tag :note
      :attrs {:measure ?measure :voice ?voice}}
     (meander/$ {:tag :step :content (?key)})
     (meander/$ {:tag :alter :content (?alteration)})
     (meander/$ {:tag :octave :content (?octave)})
     (meander/$ {:tag :duration :content (?duration)})
     (meander/$ {:tag :type :content (?type)}))

    {:tag :note
     :measure ?measure
     :voice ?voice
     :key ?key
     :alteration ?alteration
     :octave ?octave
     :duration ?duration
     :type ?type}))

Carlo20:02:07

My problem is that sometimes I don't have an :alter tag anywhere in the structure, and in that case the match fails. How would I say that if the :alter match fails, I'd like to have :alteration 0 in my map?

Carlo20:02:26

Possible solution, extending with another clause for when the pattern is not there, like:

(defn extract-essential-info [note]
  (meander/find note
    (meander/and
     {:tag :note
      :attrs {:measure ?measure :voice ?voice}}
     (meander/$ {:tag :step :content (?key)})
     (meander/$ {:tag :alter :content (?alteration)})
     (meander/$ {:tag :octave :content (?octave)})
     (meander/$ {:tag :duration :content (?duration)})
     (meander/$ {:tag :type :content (?type)}))

    {:tag :note
     :measure ?measure
     :voice ?voice
     :key ?key
     :alteration ?alteration
     :octave ?octave
     :duration ?duration
     :type ?type}

    (meander/and
     {:tag :note
      :attrs {:measure ?measure :voice ?voice}}
     (meander/$ {:tag :step :content (?key)})
     (meander/not (meander/$ {:tag :alter :content (?alteration)}))
     (meander/$ {:tag :octave :content (?octave)})
     (meander/$ {:tag :duration :content (?duration)})
     (meander/$ {:tag :type :content (?type)}))

    {:tag :note
     :measure ?measure
     :voice ?voice
     :key ?key
     :alteration 0
     :octave ?octave
     :duration ?duration
     :type ?type}))
but this feels wasteful and it's subject to combinatorial explosion, I was hoping in a smarter encoding

Richie18:03:00

Can you do something like this?

(m/rewrites '{:note ({:pitch ({:step ("D")}
                             {:alter ("-1")}
                             {:octave ("5")})}
                    {:duration ("1")}
                    {:voice ("1")}
                    {:type ("16th")}
                    {:stem ("down")}
                    {:beam ("continue")}
                    {:beam ("continue")})}

            (m/$ {:step (?key)}) {:key ?key}
            (m/$ {:alter (?alteration)}) {:alteration ?alteration}
            (m/$ {:octave (?octave)}) {:octave ?octave}
            (m/$ {:duration (?duration)}) {:duration ?duration}
            (m/$ {:type (?type)}) {:type ?type}
            (m/$ {:measure (?measure)}) {:measure ?measure}
            (m/$ {:voice (?voice)}) {:voice ?voice})
gives
({:key "D"} {:alteration "-1"} {:octave "5"} {:duration "1"} {:voice "1"} {:type "16th"})

Richie18:03:19

Actually this.

(->> (m/rewrites '{:tag :note,
                  :attrs {:default-x "148.88",
                          :default-y "-10.00",
                          :dynamics "71.11",
                          :measure "1",
                          :voice "P1"},
                  :content ({:tag :pitch,
                             :attrs {},
                             :content ({:tag :step, :attrs {}, :content ("D")}
                                       {:tag :alter, :attrs {}, :content ("-1")}
                                       {:tag :octave, :attrs {}, :content ("5")})}
                            {:tag :duration, :attrs {}, :content ("1")}
                            {:tag :voice, :attrs {}, :content ("1")}
                            {:tag :type, :attrs {}, :content ("16th")}
                            {:tag :stem, :attrs {}, :content ("down")}
                            {:tag :beam, :attrs {:number "1"}, :content ("continue")}
                            {:tag :beam, :attrs {:number "2"}, :content ("continue")})}

                (m/$ {:tag :step :content (?key)}) {:key ?key}
                (m/$ {:tag :alter :content (?alteration)}) {:alteration ?alteration}
                (m/$ {:tag :octave :content (?octave)}) {:octave ?octave}
                (m/$ {:tag :duration :content (?duration)}) {:duration ?duration}
                (m/$ {:tag :type :content (?type)}) {:type ?type}
                (m/$ {:tag :note :attrs {:measure ?measure :voice ?voice}}) {:measure ?measure :voice ?voice})
     (reduce into))
gives
{:measure "1", :voice "P1", :key "D", :alteration "-1", :octave "5", :duration "1", :type "16th"}

Richie18:03:39

Does that make sense?