meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
wilkerlucio 2021-03-21T00:01:54.013300Z

sure

wilkerlucio 2021-03-21T00:02:02.013500Z

this works as expected:

(m/find {:pricingInfos [{:rentalInfo      {:period                  "MONTHLY",
                                             :warranties              [],
                                             :monthlyRentalTotalPrice "4520"},
                           :yearlyIptu      "278",
                           :price           "3690",
                           :businessType    "RENTAL",
                           :monthlyCondoFee "830"}
                          {:yearlyIptu      "278",
                           :price           "990000",
                           :businessType    "SALE",
                           :monthlyCondoFee "830"}]}
    {:pricingInfos (m/scan
                     {:price        ?rental-price
                      :businessType "RENTAL"}
                     {:price        ?price
                      :businessType "SALE"})}
    {:sales-price  (Integer/parseInt ?price)
     :rental-price (Integer/parseInt ?rental-price)})
=> {:sales-price 990000, :rental-price 3690}

wilkerlucio 2021-03-21T00:02:20.013700Z

but in case the rental goes missing, it fails completly:

(m/find {:pricingInfos [{:yearlyIptu      "278",
                           :price           "990000",
                           :businessType    "SALE",
                           :monthlyCondoFee "830"}]}
    {:pricingInfos (m/scan
                     {:price        ?rental-price
                      :businessType "RENTAL"}
                     {:price        ?price
                      :businessType "SALE"})}
    {:sales-price  (Integer/parseInt ?price)
     :rental-price (Integer/parseInt ?rental-price)})
=> nil

wilkerlucio 2021-03-21T00:02:39.013900Z

I would like to every match to be optional, and fill with nil in case the option isn't present

phronmophobic 2021-03-21T00:06:47.014200Z

I've been using something like:

(m/find {:pricingInfos [{:rentalInfo      {:period                  "MONTHLY",
                                           :warranties              [],
                                           :monthlyRentalTotalPrice "4520"},
                         :yearlyIptu      "278",
                         :price           "3690",
                         :businessType    "RENTAL",
                         :monthlyCondoFee "830"}
                        {:yearlyIptu      "278",
                         :price           "990000",
                         :businessType    "SALE",
                         :monthlyCondoFee "830"}
                          ]}
  {:pricingInfos (unordered
                  {:price ?sales-price
                   :businessType "SALE"}
                  {:price ?rental-price
                   :businessType "RENTAL"})}
  {:sales-price ?sales-price
   :rental-price ?rental-price})

phronmophobic 2021-03-21T00:07:06.014400Z

with unordered defined as:

(m/defsyntax unordered [& patterns]
  `(m/app set
          ~(set patterns)))

phronmophobic 2021-03-21T00:09:27.014600Z

oh wait, that doesn't work if one is missing

phronmophobic 2021-03-21T00:18:29.014800Z

There's probably a way to do the exact transformation in one go, but I would probably just use something like

(m/search {:pricingInfos [{:rentalInfo      {:period                  "MONTHLY",
                                             :warranties              [],
                                             :monthlyRentalTotalPrice "4520"},
                           :yearlyIptu      "278",
                           :price           "3690",
                           :businessType    "RENTAL",
                           :monthlyCondoFee "830"}
                          {:yearlyIptu      "278",
                           :price           "990000",
                             :businessType    "SALE",
                             :monthlyCondoFee "830"}
                          ]}
  {:pricingInfos (m/scan
                  {:price ?price
                   :businessType ?type})}
  
  {:price ?price
   :type ?type})
;; ({:price "3690", :type "RENTAL"} {:price "990000", :type "SALE"})

wilkerlucio 2021-03-21T00:22:06.015200Z

thanks, I really love to see a way to do that in one go, because I have a lot of cases like this ,and being able to break the categories in a match like that somehow would be great

phronmophobic 2021-03-21T00:37:39.015400Z

this is a little verbose, but seems to work:

(m/find {:pricingInfos [{:rentalInfo      {:period                  "MONTHLY",
                                           :warranties              [],
                                           :monthlyRentalTotalPrice "4520"},
                         :yearlyIptu      "278",
                         :price           "3690",
                         :businessType    "RENTAL",
                         :monthlyCondoFee "830"}
                        {:yearlyIptu      "278",
                         :price           "990000",
                         :businessType    "SALE",
                         :monthlyCondoFee "830"}
                        ]}
  {:pricingInfos (m/or
                  (unordered
                   {:price ?sales-price
                    :businessType "SALE"}
                   {:price ?rental-price
                    :businessType "RENTAL"})
                  (m/let [?sales-price nil]
                    (unordered
                     {:price ?rental-price
                      :businessType "RENTAL"}))
                  (m/let [?rental-price nil]
                    (unordered
                     {:price ?sales-price
                      :businessType "SALE"}))
                  (m/let [?rental-price nil
                          ?sales-price nil] _))}
  {:sales-price ?sales-price
   :rental-price ?rental-price})

phronmophobic 2021-03-21T00:38:31.015600Z

there's probably a way to simplify it, possibly with m/app or m/defsyntax

wilkerlucio 2021-03-21T04:07:23.016Z

hello, I tried to run it now, but always getting nils

wilkerlucio 2021-03-21T04:07:24.016200Z

(m/find {:pricingInfos [{:rentalInfo      {:period                  "MONTHLY",
                                           :warranties              [],
                                           :monthlyRentalTotalPrice "4520"},
                         :yearlyIptu      "278",
                         :price           "3690",
                         :businessType    "RENTAL",
                         :monthlyCondoFee "830"}
                        {:yearlyIptu      "278",
                         :price           "990000",
                         :businessType    "SALE",
                         :monthlyCondoFee "830"}]}
  {:pricingInfos (m/or
                   (unordered
                     {:price ?sales-price
                      :businessType "SALE"}
                     {:price ?rental-price
                      :businessType "RENTAL"})
                   (m/let [?sales-price nil]
                     (unordered
                       {:price ?rental-price
                        :businessType "RENTAL"}))
                   (m/let [?rental-price nil]
                     (unordered
                       {:price ?sales-price
                        :businessType "SALE"}))
                   (m/let [?rental-price nil
                           ?sales-price nil] _))}
  {:sales-price  ?sales-price
   :rental-price ?rental-price})
=> {:sales-price nil, :rental-price nil}

phronmophobic 2021-03-21T04:25:30.016400Z

weird. I just copy and pasted the code from your message into my repl and it gave:

{:sales-price "990000", :rental-price "3690"}
I did just upgrade meander to meander/epsilon {:mvn/version "0.0.602"} . what version are you using?

wilkerlucio 2021-03-21T04:44:57.016600Z

this same, 0.0.602, on MacOS

phronmophobic 2021-03-21T04:46:20.016800Z

and you have:

(m/defsyntax unordered [& patterns]
  `(m/app set
          ~(set patterns)))
to define unordered?

phronmophobic 2021-03-21T04:46:51.017Z

I'm on MacOS as well

wilkerlucio 2021-03-21T04:48:03.017200Z

ah, I dont

wilkerlucio 2021-03-21T04:48:15.017400Z

and I just found another way to do it, with less verbosity 🙂

wilkerlucio 2021-03-21T04:48:17.017600Z

(m/find {:pricingInfos [{:rentalInfo      {:period                  "MONTHLY",
                                             :warranties              [],
                                             :monthlyRentalTotalPrice "4520"},
                           :yearlyIptu      "278",
                           :price           "3690",
                           :businessType    "RENTAL",
                           :monthlyCondoFee "830"}
                          {:yearlyIptu      "278",
                           :price           "990000",
                           :businessType    "SALE",
                           :monthlyCondoFee "830"}]}
    {:pricingInfos (m/and
                     (m/or (m/scan
                             {:price        ?rental-price
                              :businessType "RENTAL"})
                       (m/let [?rental-price nil]))
                     (m/or
                       (m/scan
                         {:price        ?price
                          :businessType "SALE"})
                       (m/let [?price nil])))}
    {:sales-price  ?price
     :rental-price ?rental-price})
=> {:sales-price "990000", :rental-price "3690"}

1
wilkerlucio 2021-03-21T04:48:33.017900Z

that covers all cases, and including new cases can be done with 1 new addition

wilkerlucio 2021-03-21T04:49:27.018100Z

that unordered trick is cool, I'm very excited with Meander 🙂

phronmophobic 2021-03-21T04:50:11.018300Z

the m/and is a neat trick too

wilkerlucio 2021-03-21T04:50:30.018500Z

next is learn how to abstract that in some defsyntax

phronmophobic 2021-03-21T04:51:02.018700Z

yea, was just going to say that it should be possible to add syntax to make it as short as the original attempt

wilkerlucio 2021-03-21T04:51:19.018900Z

yeah, my wishful syntax looks like this:

(m/find {:pricingInfos [{:rentalInfo      {:period                  "MONTHLY",
                                             :warranties              [],
                                             :monthlyRentalTotalPrice "4520"},
                           :yearlyIptu      "278",
                           :price           "3690",
                           :businessType    "RENTAL",
                           :monthlyCondoFee "830"}
                          {:yearlyIptu      "278",
                           :price           "990000",
                           :businessType    "SALE",
                           :monthlyCondoFee "830"}]}
    {:pricingInfos (list-pick
                     {:price        ?rental-price
                      :businessType "RENTAL"}
                     {:price        ?price
                      :businessType "SALE"})}
    {:sales-price  ?price
     :rental-price ?rental-price})

1
phronmophobic 2021-03-21T04:52:15.019300Z

there's probably also a way set defaults for unbound logic variables. maybe it's worth filing an issue. I think it comes up regularly in the chat

phronmophobic 2021-03-21T04:54:55.019500Z

my wishful syntax is like:

(m/find {:pricingInfos [{:rentalInfo      {:period                  "MONTHLY",
                                           :warranties              [],
                                           :monthlyRentalTotalPrice "4520"},
                         :yearlyIptu      "278",
                         :price           "3690",
                         :businessType    "RENTAL",
                         :monthlyCondoFee "830"}
                        {:yearlyIptu      "278",
                         :price           "990000",
                         :businessType    "SALE",
                         :monthlyCondoFee "830"}]}
  {:pricingInfos (with-defaults [?rental-price nil
                                 ?price nil]
                   (unordered
                    {:price        ?rental-price
                     :businessType "RENTAL"}
                    {:price        ?price
                     :businessType "SALE"}))}
  {:sales-price  ?price
   :rental-price ?rental-price})
and it only scans through the list once

👀 1
wilkerlucio 2021-03-21T05:05:17.019700Z

got the list-pick working 😄

wilkerlucio 2021-03-21T05:05:19.019900Z

(defn collect-variables [pattern]
  (let [vars* (volatile! #{})]
    (walk/postwalk
      (fn [x]
        (if (and (symbol? x)
                 (str/starts-with? (name x) "?"))
          (vswap! vars* conj x))
        x)
      pattern)
    @vars*))

(m/defsyntax list-pick [& patterns]
  `(m/and
     ~@(map
         (fn [pattern]
           (let [vars (collect-variables pattern)
                 defaults (vec (interleave vars (repeat nil)))]
             `(m/or
                (m/scan ~pattern)
                (m/let ~defaults))))
         patterns)))

wilkerlucio 2021-03-21T05:06:00.020200Z

still many scans, but in my case these lists rarely go over 5 items

1
phronmophobic 2021-03-21T05:12:07.020600Z

the collect-variables works. there's also a builtin function that finds all the logic variables. For example:

(m/defsyntax mor
  "work around for 'Every pattern of an or pattern must have references to the same unbound logic variables.'"
  [& patterns]
  (let [vars
        (into []
              (comp
               (map r.syntax/parse)
               (map r.syntax/variables)
               (map #(map r.syntax/unparse %))
               (map set))
              patterns)
        all-vars (into #{} cat vars)]
    `(m/or
      ~@(for [[pvars pattern] (map vector vars patterns)
              :let [missing (clojure.set/difference all-vars pvars)]]
          (if (seq missing)
            (let [bindings
                  (into []
                        cat
                        (for [v missing]
                          [v nil]))]
              `(m/let ~bindings
                 ~pattern))
            pattern)))))

🙌 1
phronmophobic 2021-03-21T05:13:01.020800Z

you can also use r.syntax/logic-variables if instead of r.syntax/variables if you only care about ?variables and not !variables

wilkerlucio 2021-03-21T05:20:28.021300Z

cool

wilkerlucio 2021-03-21T05:21:04.021500Z

my next challenged is how to deal when a binding is shared between cases, but may be blank (due to the OR binding when the item isn't there)

wilkerlucio 2021-03-21T05:21:06.021700Z

(m/find {:pricingInfos [{:rentalInfo      {:period                  "MONTHLY",
                                             :warranties              [],
                                             :monthlyRentalTotalPrice "4520"},
                           :yearlyIptu      "278",
                           :price           "3690",
                           :businessType    "RENTAL",
                           :monthlyCondoFee "830"}
                          {:yearlyIptu      "278",
                           :price           "990000",
                           :businessType    "SALE",
                           :monthlyCondoFee "830"}]}
    {:pricingInfos (wm/list-pick
                     {:rentalInfo      {:period                  ?rental-pay-period
                                        :monthlyRentalTotalPrice ?rental-price-total}
                      :price           ?rental-price
                      :yearlyIptu      ?iptu
                      :monthlyCondoFee ?fee
                      :businessType    "RENTAL"}
                     {:price           ?sales-price
                      :yearlyIptu      ?iptu
                      :monthlyCondoFee ?fee
                      :businessType    "SALE"})}
    {:sales-price  ?sales-price
     :rental-price ?rental-price
     :iptu ?iptu
     :condo-fee ?fee})

wilkerlucio 2021-03-21T05:21:39.021900Z

in this case, ?iptu and ?fee are the same, when present, but if there is no rental for example, then it would fail because it would be set to nil in rental, but with a value in sale

wilkerlucio 2021-03-21T05:22:39.022100Z

I guess the defaults you wished for could make this easier

phronmophobic 2021-03-21T05:32:48.022300Z

I also just remembered the return expression is just a normal expression which means you can do stuff like:

(m/find {:pricingInfos [{:rentalInfo      {:period                  "MONTHLY",
                                           :warranties              [],
                                           :monthlyRentalTotalPrice "4520"},
                         :yearlyIptu      "278",
                         :price           "3690",
                         :businessType    "RENTAL",
                         :monthlyCondoFee "830"}
                        {:yearlyIptu      "278",
                         :price           "990000",
                         :businessType    "SALE",
                         :monthlyCondoFee "830"}]}
  {:pricingInfos (list-pick
                  {:yearlyIptu      !iptu
                   :businessType    "RENTAL"}

                  {:price      !rental-price
                   :businessType    "RENTAL"}
                  {:price      !sales-price
                   :businessType    "SALE"}

                  {:monthlyCondoFee !fee}
                  {:yearlyIptu      !iptu
                   :businessType    "SALE"})}
  {:iptu (some identity !iptu)
   :rental-price (some identity !rental-price)
   :condo-fee (some identity !fee)
   :sales-price (some identity !sales-price)
   })

wilkerlucio 2021-03-21T05:36:36.022500Z

yup, funny enough, that's exactly the direction I'm trying to automate

wilkerlucio 2021-03-21T05:36:43.022700Z

but I'm hiding the accumulators on the syntax itself

🙌 1
wilkerlucio 2021-03-21T05:36:51.022900Z

(trying to at least)

phronmophobic 2021-03-21T05:37:35.023100Z

that would be cool

phronmophobic 2021-03-21T05:43:39.023400Z

one of the other tools that is less documented, but powerful is m/cata which can let you do some really interesting stuff

🆒 1
wilkerlucio 2021-03-21T05:52:08.023600Z

got it!

wilkerlucio 2021-03-21T05:52:20.023800Z

(defn logic->mutable [var]
  (symbol (str "!" (name var))))

(defn collect-variables [pattern]
  (let [vars*    (volatile! #{})
        pattern' (walk/postwalk
                   (fn [x]
                     (if (and (symbol? x)
                              (str/starts-with? (name x) "?"))
                       (do
                         (vswap! vars* conj x)
                         (logic->mutable x))
                       x))
                   pattern)]
    [@vars* pattern']))

(m/defsyntax list-pick [& patterns]
  (let [all-vars (into #{} (mapcat (comp first collect-variables)) patterns)
        all-let  (vec (interleave all-vars
                        (map
                          (fn [v]
                            (let [new-name (logic->mutable v)]
                              `(some identity ~new-name)))
                          all-vars)))]
    `(m/and
       ~@(map
           (fn [pattern]
             (let [[vars pattern] (collect-variables pattern)
                   vars-set (vec (interleave (map logic->mutable vars) (repeat nil)))]
               `(m/or
                  (m/scan ~pattern)
                  (m/let ~vars-set))))
           patterns)
       (m/let ~all-let))))

phronmophobic 2021-03-21T05:58:38.024300Z

ok. I think I'm starting to figure it out. That's neat!

phronmophobic 2021-03-21T06:03:30.024500Z

I didn't realize you could use m/let with m/and to just bind variables at the end. That's a useful technique.

wilkerlucio 2021-03-21T06:04:45.025Z

yeah, was a guess, glad it worked 🙂

1
2021-03-21T12:12:50.026400Z

(m/rewrite [["00:00" "01:00"] ["02:00" "03:00"]]
  (m/or [(m/pred string?) ... :as !time]
        [[(m/pred string?) ... :as !time] ...]
        (m/let [!time ["00:00" "23:59"]]))
  [!time ...])
shouldn't it return [["00:00" "01:00"] ["02:00" "03:00"]] ?

noprompt 2021-03-22T15:35:24.026500Z

Yes.

noprompt 2021-03-22T15:41:46.026700Z

It looks like theres something up with m/or ; I’ll take a look soon. For now (and I’m sure you’ve probably figured a work around) you can use

(me/rewrite [["00:00" "01:00"] ["02:00" "03:00"]]
  (me/or [(me/pred string?) ..1 :as !time]
         [[(me/pred string?) ... :as !time] ..1])
  [!time ...]

  _
  [["00:00" "23:59"]])

2021-03-22T17:37:35.026900Z

thx