sure
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}
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
I would like to every match to be optional, and fill with nil
in case the option isn't present
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})
with unordered
defined as:
(m/defsyntax unordered [& patterns]
`(m/app set
~(set patterns)))
oh wait, that doesn't work if one is missing
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"})
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
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})
there's probably a way to simplify it, possibly with m/app
or m/defsyntax
hello, I tried to run it now, but always getting nils
(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}
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?this same, 0.0.602
, on MacOS
and you have:
(m/defsyntax unordered [& patterns]
`(m/app set
~(set patterns)))
to define unordered
?I'm on MacOS as well
ah, I dont
and I just found another way to do it, with less verbosity 🙂
(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"}
that covers all cases, and including new cases can be done with 1 new addition
that unordered
trick is cool, I'm very excited with Meander 🙂
the m/and
is a neat trick too
next is learn how to abstract that in some defsyntax
yea, was just going to say that it should be possible to add syntax to make it as short as the original attempt
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})
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
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 oncegot the list-pick
working 😄
(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)))
still many scans, but in my case these lists rarely go over 5 items
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)))))
you can also use r.syntax/logic-variables
if instead of r.syntax/variables
if you only care about ?variables
and not !variables
cool
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)
(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})
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
I guess the defaults you wished for could make this easier
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)
})
yup, funny enough, that's exactly the direction I'm trying to automate
but I'm hiding the accumulators on the syntax itself
(trying to at least)
that would be cool
one of the other tools that is less documented, but powerful is m/cata
which can let you do some really interesting stuff
got it!
(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))))
ok. I think I'm starting to figure it out. That's neat!
I didn't realize you could use m/let
with m/and
to just bind variables at the end. That's a useful technique.
yeah, was a guess, glad it worked 🙂
(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"]]
?Yes.
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"]])
thx