meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
noprompt 2020-07-31T00:37:03.297800Z

It depends on what you mean by creating. I’m not sure what comments you saw in particular but I’m sure, whatever they are, my sentiment is probably the same regarding them — negative. Even in a statically typed setting where you can avoid the sort of bugs that come up in Clojure, they’re unappealing to me. Personally, I think the cognitive expense is too high. You have to learn how they work and then learn how they are used in practice.

ikrimael 2020-07-31T01:18:37.298100Z

tend to agree hence conflicted/searching for a better answer. the only other alternative i've found (and my current approach) is a boilerplate heavy conversion layer

ikrimael 2020-07-31T01:21:23.298300Z

for clarity: core problem is going from "unknown shape" => "known shape" => known shape modifications => "unknown shape"

ikrimael 2020-07-31T01:24:17.298600Z

"known shape" => "known shape modifications: where meander shines projecting the data modifications back out to the unknown shape => challenge

ikrimael 2020-07-31T01:29:23.298900Z

one thing i'm gonna try is just to see if in practice, i can switch the left/write clauses in meander and see if that automagically gives me the f^-1 projection lol, well that failed quickly. c'est la vie, battle to fight for another day

😂 1
chucklehead 2020-07-31T04:30:27.301300Z

hello all, I suspect I shouldn't be using scan here, but not sure what the right operator would be. The goal is to pull out multiple nested values/attrs from the sequence in the :content of the trackInformation element. When I uncomment the latitude element below, the search stops matching, but works if I e.g. comment out speed/altitude and only scan for latitude.

(-&gt; (<http://clojure.java.io/reader|clojure.java.io/reader> "./corpus/example.xml")
    (clojure.data.xml/parse :skip-whitespace true)
    (m/search
     (m/$ {:tag ::fdm/fltdMessage
           :attrs {:msgType "trackInformation"
                   :flightRef ?flight-ref
                   :acid ?aircraft-id
                   :airline ?airline
                   :depArpt ?departure-airport
                   :arrArpt ?arrival-airport
                   :sourceTimeStamp ?source-ts}
           :content ({:tag ::fdm/trackInformation
                      :content (m/scan
                                (m/$ {:tag ::nxcm/speed
                                      :content (?speed)})
                                (m/$ {:tag ::nxce/simpleAltitude
                                      :content (?altitude)})
                                #_(m/$ {:tag ::nxce/latitudeDMS
                                        :attrs ?latitude-dms}))})})
     {:message/source-ts (read-instant-timestamp ?source-ts)
      :flight/ref (Integer/parseInt ?flight-ref)
      :flight/aircraft-id ?aircraft-id
      :track/speed (Integer/parseInt ?speed)
      :track/altitude ?altitude}))
A trimmed down sample of the actual data is https://gist.github.com/casselc/09b1ed46a86b500cabd2e14ada1a3719.

jimmy 2020-07-31T15:28:49.301400Z

So I played with this a bit but couldn't get it directly working. The reason scan doesn't work here is that it assumes all its elements are sequential.

(m/search [1 2 3 4 5 6]
  (m/scan 3 4 ?x)
  ?x)

;; =&gt;

5

(m/search [1 2 3 4 5 6]
  (m/separated 3 4 ?x)
  ?x)

;; =&gt; 

5 6
But separated doesn't work either. Maybe something weird is happening with the nested $? I will try a bit later.

chucklehead 2020-08-01T12:02:10.301900Z

thanks, I think I understand scan a little better now. Played around a bit more and ended up with:

chucklehead 2020-08-01T12:02:10.302100Z

(defn xml-&gt;tracks
  [xml]
  (m/search
   xml
   {:tag ::tx/tfmDataService
    :content ({:tag ::tx/fltdOutput
               :content (m/scan
                         {:tag ::fdm/fltdMessage
                          :attrs {:msgType "trackInformation"
                                  :flightRef ?flight-ref
                                  :acid ?aircraft-id
                                  :airline ?airline
                                  :depArpt ?departure-airport
                                  :arrArpt ?arrival-airport
                                  :sourceTimeStamp ?message-ts}
                          :content ({:tag ::fdm/trackInformation
                                     :content (m/scan
                                               {:tag ::nxcm/qualifiedAircraftId}
                                               {:tag ::nxcm/speed
                                                :content (?speed)}
                                               {:tag ::nxcm/reportedAltitude
                                                :content ({:tag ::nxce/assignedAltitude
                                                           :content ((m/or
                                                                      {:tag ::nxce/simpleAltitude
                                                                       :content (?altitude)}
                                                                      {:tag ::nxce/blockedAltitude
                                                                       :attrs {:min (?altitude)}}
                                                                      {:tag ::nxce/visualFlightRules
                                                                       :attrs {:altitude ?altitude}}
                                                                      {:tag ::nxce/altitudeFixAltitude
                                                                       :attrs {:preFixAltitude ?altitude}}))})}
                                               {:tag ::nxcm/position
                                                :content (m/scan
                                                          {:tag ::nxce/latitude
                                                           :content ({:tag ::nxce/latitudeDMS
                                                                      :attrs ?latitude-dms})}
                                                          {:tag ::nxce/longitude
                                                           :content ({:tag ::nxce/longitudeDMS
                                                                      :attrs ?longitude-dms})})}
                                               {:tag ::nxcm/timeAtPosition
                                                :content (?track-ts)}
                                               .
                                               (m/or
                                                {:tag ::nxcm/ncsmTrackData
                                                 :content (m/seqable
                                                           .
                                                           (m/or
                                                            {:tag ::nxcm/nextEvent
                                                             :attrs {:latitudeDecimal ?next-lat
                                                                     :longitudeDecimal ?next-long}}
                                                            (m/let [?next-lat nil ?next-long nil]))
                                                           ..1)}
                                                {:tag ::nxcm/ncsmRouteData
                                                 :content (m/scan
                                                           {:tag ::nxcm/nextPosition
                                                            :attrs {:latitudeDecimal ?next-lat
                                                                    :longitudeDecimal ?next-long}})}))})})})}
   (merge {:flight/ref (Long/parseLong ?flight-ref)
           :flight/aircraft-id ?aircraft-id
           :flight/airline ?airline
           :flight/arrival-airport ?arrival-airport
           :flight/departure-airport ?departure-airport
           :track/speed (Integer/parseInt ?speed)}
          (when (and ?next-lat ?next-long)
            {:track/next-latitude (Double/parseDouble ?next-lat)
             :track/next-longitude (Double/parseDouble ?next-long)}))))

chucklehead 2020-08-01T12:02:10.302300Z

Not especially happy with the last bit, but haven't been able to figure out a better way that doesn't generate 'extra' entries. What I'm trying to express is that the element after timeAtPosition will be either a ncsmTrackData or ncsmRouteData tag, but if it's a ncsmTrackData it may or may not contain a nextEvent with a lat/long.

noprompt 2020-08-04T18:35:11.302600Z

@chuck.cassel I’m gonna take a swing at this. 🙂

chucklehead 2020-08-04T18:39:23.302800Z

if you haven't already I'd really appreciate a review of where I've gotten since then to see if it makes sense or there's a better/more efficient/performant/etc way to go about it. I just updated the https://gist.github.com/casselc/09b1ed46a86b500cabd2e14ada1a3719 with a copy/paste from my ns where I've been playing around with the data

chucklehead 2020-08-04T18:40:41.303Z

thanks for taking a look

noprompt 2020-08-04T18:44:45.303300Z

Do you have a deps.edn to go along with this?

noprompt 2020-08-04T18:45:30.303500Z

I’m pulling together the deps myself.

chucklehead 2020-08-04T18:47:21.303700Z

{:paths ["src" "resources"]
 :deps {org.clojure/clojure {:mvn/version "1.10.1"}
        org.clojure/data.xml {:mvn/version "0.2.0-alpha6"}
        org.clojure/core.async {:mvn/version "1.3.610"}
        environ {:mvn/version "1.2.0"}
        com.solacesystems/sol-jcsmp {:mvn/version "10.9.0"}
        meander/epsilon {:mvn/version "0.0.480"}
        datascript {:mvn/version "1.0.0"}
        clojure.java-time {:mvn/version "0.3.2"}
        }
 }

🎉 1
noprompt 2020-08-04T19:13:45.304Z

This is actually pretty incredible, I must admit.

noprompt 2020-08-04T19:14:07.304200Z

I think this is the first time I’ve seen someone use defsyntax this way.

chucklehead 2020-08-04T19:14:49.304400Z

that can't be good

noprompt 2020-08-04T19:15:08.304600Z

No it’s great actually.

noprompt 2020-08-04T19:15:24.304800Z

I’m just now realizing that it I need to expose something for expanding a pattern conveniently.

chucklehead 2020-08-04T19:18:51.305Z

I initially built it up as one giant find just trying to basically transliterate the patterns from the xml until it all sort ofworked, and then wanted some way to break up the individual pieces

noprompt 2020-08-04T19:19:10.305200Z

That makes perfect sense.

chucklehead 2020-08-04T19:19:15.305400Z

eventually I'll want to do some other message types and certain elements are reused

noprompt 2020-08-04T19:19:38.305600Z

Yah, and that’s a totally valid use case. I’m glad you’re messing with this.

chucklehead 2020-08-04T19:19:41.305800Z

I actually have good xsd for all of this so there's probably some way to meander that into exactly what I want

chucklehead 2020-08-04T19:20:37.306Z

I'm fairly new to clojure altogether and have just been wrangling around with this data as a way to experiment/learn with something concrete

noprompt 2020-08-04T19:21:55.306200Z

Nice. Actually, I think you’d be the second person to join the channel in as many weeks who is both new to Clojure and to Meander. I couldn’t ask for better perspectives. 🙂

chucklehead 2020-08-04T19:22:01.306400Z

so far meander's describe the shape you have and the shape you want approach is my favorite clojure thing I've found

noprompt 2020-08-04T19:22:11.306600Z

I’m really happy to hear that.

noprompt 2020-08-04T19:23:21.306800Z

This example is pretty interesting. I’m going to toy around with it a little bit. I think it’s exposed some opportunities for improvement too.

chucklehead 2020-08-04T19:26:33.307Z

what I'd like to make is something where I could essentially write the matching/extracting patterns in a less verbose/hiccup-style syntax and expand that into the actual xml pattern prior to matching

chucklehead 2020-08-04T19:27:08.307200Z

but wasn't quite sure how to get there from here and started working on something else

noprompt 2020-08-04T19:42:12.307400Z

Do you have a sketch of what’s in your mind?

chucklehead 2020-08-04T20:27:44.307600Z

I guess I'd like to be able to write a pattern something like this to match that data:

[::fdm/fltdMessage {:msgType "trackInformation"
                    :flightRef (m/app Long/parseLong ?flight-ref)}
 [::fdm/trackInformation ; implicit separated(?) of content
  :start-of-seq ;made-up notation to anchor start of seq
  [::nxcm/qualifiedAircraftId]
  [::nxcm/speed (m/app Integer/parseInt ~speed)]
  [::nxcm/position
   [::nxcm/latitude
    [::nxcm/latitudeDMS {m/app dms-&gt;decimal ?latitude}]]]
  (m/or
   [::nxcm/ncsmTrackData ?some-data]
   [::nxcm/ncsmRouteData ?some-data])
  :? ; made-up notation for 0 or 1 of the preceding element. Implicit nil for bound vars?
  :end-of-seq]]

chucklehead 2020-08-04T20:29:34.307800Z

that conflates a few things I've been bumping into, but hopefully gets the idea across

chucklehead 2020-08-04T20:38:35.308Z

I struggled (I still struggle, but I used to, too) quite a bit with really grokking the semantics of the sequence operators/notation and optionality. With next lat/long for instance, trying to express that if it was present it would be in one of two mutually exclusive elements, but might not be there at all.

chucklehead 2020-08-04T20:40:03.308200Z

Eventually I scrolled far enough in the cookbook to see the optional value stuff and felt less alone at least

noprompt 2020-08-04T22:33:15.308400Z

(m/rewrite xml-edn
  {:tag ?tag, :attrs ?attrs, :content (m/seqable (m/cata !xs) ...)}
  [?tag ?attrs . !xs ...]

  ?x
  ?x)

chucklehead 2020-08-07T19:23:34.310900Z

Thanks, sorry to take so long to get back, hadn't had a chance to work on this project... Hopefully I'm not being obtuse about this. I was hoping to use the hiccup syntax as a sort of DSL to write the matching/capturing patterns. I was thinking I'd wrap the hiccup in some helper function/macro and use that wrapped form directly in the match pattern position for find/search/rewrite/etc and the helper would transform my hiccup-syntax pattern into it's map representation while preserving any logic variables, memory variables, or other operators in the pattern). i.e. I'd write something like:

(m/find example
          (from-hiccup-pattern
           [:message {:type "X"}
            [:detail {:field ?y}
             [:first (m/app Float/parseFloat ?content)]
             (m/$ [:second-nested {:q ?q}])]])
          {:y-val ?y
           :q-val ?q
           :parsed-content ?content})
and it would essentially expand to:
(m/find example
        {:tag :message
         :attrs {:type "X"}
         :content (m/seqable {:tag :detail
                              :attrs {:field ?y}
                              :content (m/seqable
                                        {:tag :first
                                         :content (m/seqable
                                                   (m/app Float/parseFloat ?content))}
                                        (m/$ {:tag :second-nested
                                              :attrs {:q ?q}}))})}
        {:y-val ?y
         :q-val ?q
         :parsed-content ?content})
I can see how I could use your rewrite to transform my input to a hiccup format that I could then match against (possibly as another step within the same rewrite?) . In my head I'd envisioned going about it the other way so that the hiccup to xml pattern expansion would happen at compile-time rather than xml to hiccup at parse time. I haven't used cata or memory variables yet, and not really all that familiar with macros/quoting so I suspect I'm overlooking a trivial way to do what I want, or possibly the distinction I'm worried about isn't accurate/doesn't matter with the way meander works.