Hello, I'm trying to understand how match pattern matching works when
the pattern is a variable in scope. Take the following examples and
results:
;; case 1
(let [pattern 1]
(match 1
pattern :matched-var
_ :no-match)) ;; -> matched-var
;; case 2
(let [pattern 1]
(match [1]
[pattern] :matched-var-in-list
_ :no-match)) ;; -> matched-var-in-list
;; case 3
(let [pattern [1]]
(match [1]
pattern :matched-list
_ :no-match)) ;; -> no-match
;; case 4
(let [pattern 1]
(match {:x 1}
{:x pattern} :matched-var-in-table
_ :no-match)) ;; -> matched-var-in-table
;; case 5
(let [pattern {:x 1}]
(match {:x 1}
pattern :matched-table
_ :no-match)) ;; -> no-match
What prevents match from matching a pattern that is a complete list or table?
This would be useful when my pattern may include a literal list or
table as part of a larger pattern. For example, in this LOVE script:
https://github.com/mogenson/fennel-game-of-life/blob/bcdd9100d59e0fb6e7194720c6fe0eb68f438283/main.fnl#L35
It would be great to use the alive and dead variables from the top of
the script as part of the match pattern.
>;; case 1>(let [pattern 1]> (match 1> pattern :matched-var> _ :no-match)) ;; -> matched-var
Here, the compiled code is just a check that 1 is equal to a value
of the variable named pattern
>;; case 2>(let [pattern 1]> (match [1]> [pattern] :matched-var-in-list> _ :no-match)) ;; -> matched-var-in-list
Here, the match checks if matched value is a table,
who's firse element is equal to a variable named pattern
>;; case 3>(let [pattern [1]]> (match [1]> pattern :matched-list> _ :no-match)) ;; -> no-match
Here [1] checked to be equal to a variable named pattern.
In Lua table comparisons are done by reference so it fails,
as these objects are different
>;; case 4>(let [pattern 1]> (match {:x 1}> {:x pattern} :matched-var-in-table> _ :no-match)) ;; -> matched-var-in-table
Same as No2 except it checks for named key not an index
>;; case 5>(let [pattern {:x 1}]> (match {:x 1}> pattern :matched-table> _ :no-match)) ;; -> no-match
Same as No3
Lua doesn't have deep comparison of tables and in order for match to
work it analyzes the pattern you give it. If it's a table, match generates
a check for table's contents.
You can see what's going on by compiling match to Lua:
local pattern = 1
local _1_ = {1}
if ((_G.type(_1_) == "table") and ((_1_)[1] == pattern)) then
return "matched-var-in-list"
elseif true then
local _ = _1_
return "no-match"
else
return nil
end
But for No3 case its:
local pattern = {1}
local _1_ = {1}
if (_1_ == pattern) then
return "matched-var-in-list"
elseif true then
local _ = _1_
return "no-match"
else
return nil
end
Hope this helps.
--
Andrey Listopadov
Thanks. I didn't know Lua compares tables by hash instead of value.
Now Fennel's behavior makes sense.
I wonder if it would be possible to construct a new list or table
within a match branch. Unfortunately the following example does not
work:
(let [pattern [1]]
(match [1]
[(unpack pattern)] :matched-unpacked-list
_ :no-match)) ;; -> Compile error: can't nest multi-value destructuring
On Tue, Sep 5, 2023 at 8:29 PM Andrey <andreyorst@gmail.com> wrote:
>>> >;; case 1> >(let [pattern 1]> > (match 1> > pattern :matched-var> > _ :no-match)) ;; -> matched-var>> Here, the compiled code is just a check that 1 is equal to a value> of the variable named pattern>> >;; case 2> >(let [pattern 1]> > (match [1]> > [pattern] :matched-var-in-list> > _ :no-match)) ;; -> matched-var-in-list>> Here, the match checks if matched value is a table,> who's firse element is equal to a variable named pattern>> >;; case 3> >(let [pattern [1]]> > (match [1]> > pattern :matched-list> > _ :no-match)) ;; -> no-match>> Here [1] checked to be equal to a variable named pattern.> In Lua table comparisons are done by reference so it fails,> as these objects are different>> >;; case 4> >(let [pattern 1]> > (match {:x 1}> > {:x pattern} :matched-var-in-table> > _ :no-match)) ;; -> matched-var-in-table>> Same as No2 except it checks for named key not an index>> >;; case 5> >(let [pattern {:x 1}]> > (match {:x 1}> > pattern :matched-table> > _ :no-match)) ;; -> no-match>> Same as No3>> Lua doesn't have deep comparison of tables and in order for match to> work it analyzes the pattern you give it. If it's a table, match generates> a check for table's contents.>> You can see what's going on by compiling match to Lua:>> local pattern = 1> local _1_ = {1}> if ((_G.type(_1_) == "table") and ((_1_)[1] == pattern)) then> return "matched-var-in-list"> elseif true then> local _ = _1_> return "no-match"> else> return nil> end>> But for No3 case its:>> local pattern = {1}> local _1_ = {1}> if (_1_ == pattern) then> return "matched-var-in-list"> elseif true then> local _ = _1_> return "no-match"> else> return nil> end>> Hope this helps.>> --> Andrey Listopadov
On September 6, 2023 4:49:45 AM GMT+03:00, Mike Mogenson <michael.mogenson@gmail.com> wrote:
>Thanks. I didn't know Lua compares tables by hash instead of value.>Now Fennel's behavior makes sense.>>I wonder if it would be possible to construct a new list or table>within a match branch. Unfortunately the following example does not>work:>>(let [pattern [1]]> (match [1]> [(unpack pattern)] :matched-unpacked-list> _ :no-match)) ;; -> Compile error: can't nest multi-value destructuring
Do not forget that match is a macro, and macros don't know anything about
runtime values. When you're writing the word "pattern" in the clause, match
simply sees the name "pattern", not an actual variable or it's contents, because
contents only exist at runtime, while macros run way before that.
So, as a rule of thumb, all patterns must be static. Referring a variable works because
it's still static, as the name is known. If you need to do some runtime checking, there's a
`where` keyword:
(let [pattern [1]]
(match [1]
(where x (= (. x 1) (. pattern 1))) :x-matches
_ :no-match))
The error you're seing is due to () share both calling and binding nature in fennel.
Match patterns interpret () as a way to match on multiple values, not as a call to
a function.
--
Andrey Listopadov