~technomancy/fennel

3 2

match pattern destructuring with variables

Details
Message ID
<CALr1zcjfqH93qVdO9PGjCa_Bk=oVrLOZEsNw+VhYr2fR0A3GGA@mail.gmail.com>
DKIM signature
missing
Download raw message
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.
Details
Message ID
<2C7F85CA-DA4E-4F16-BF59-EBB0903807B1@gmail.com>
In-Reply-To
<CALr1zcjfqH93qVdO9PGjCa_Bk=oVrLOZEsNw+VhYr2fR0A3GGA@mail.gmail.com> (view parent)
DKIM signature
missing
Download raw message
>;; 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
Details
Message ID
<CALr1zcjuzOmhAatK4b4zqP6idB9MNr3H3QE7UHMt_C1dXK2OHA@mail.gmail.com>
In-Reply-To
<2C7F85CA-DA4E-4F16-BF59-EBB0903807B1@gmail.com> (view parent)
DKIM signature
missing
Download raw message
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
Details
Message ID
<158B58E2-9D44-49E5-BE4B-C9AAC927EAD1@gmail.com>
In-Reply-To
<CALr1zcjuzOmhAatK4b4zqP6idB9MNr3H3QE7UHMt_C1dXK2OHA@mail.gmail.com> (view parent)
DKIM signature
missing
Download raw message
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
Reply to thread Export thread (mbox)