Chapter 13 Metatables and Metamethods

Usually, each value in Lua has a quite predictable set of operations.[其实就相当于C++ 里面每一类型都有自己预定义的operator, we can overwirte the default operator ]We can add numbers, we can concatenate strings, we can insert key–value pairs into tables,  and so on. However, we cannot add tables, we cannot compare functions, and we  cannot call a string. Unless we use metatables.


Metatables allow us to change the behavior of a value when confronted with
an undefined operation.
For instance, using metatables, we can define how
Lua computes the expression a+b, where a and b are tables
. [其实就相当于C++ 里面重载运算符号]

Whenever Lua  tries to add two tables, it checks whether either of them has a metatable and
whether this metatable has an __add field. If Lua finds this field, it calls the
corresponding value—the so-called metamethod, which should be a function—
to compute the sum.


Each value in Lua can have an associated metatable.

Tables and userdata  have individual metatables (也就是table, and userdata 每一个table,or userdata,都是不同,)

values of other types share one single metatable for  all values of that type.

> =getmetatable("abc");
table: 00572008
> =getmetatable("kfg");
table: 00572008   --可以看到string 的metatable 是shared.



Lua always creates new tables without metatables:

> b={};
> mt={};
> setmetatable(b,mt);
> =getmetatable(b);
table: 0057A368
> =mt
table: 0057A368  --可以看到table b的metable is mt.
> c={};
> =getmetatable(c);  --可以看到each table have individual metatables,所以每create a table, will assign nill to it.
nil
>

其实table 可以让你重定义matamethod, 可以理解,如果象number 再重定义其实真没什么必要了吧,+ - 还能有新的玩法??I do not think so , So I 可以理解为什么Lua 要这么做

From Lua we can set the metatables only of tables; to manipulate the metatables
of values of other types we must use C code
.1 As we will see later, in

Chapter 21, the string library sets a metatable for strings.(所有的string ,share this metatable) All other types by
default have no metatable:

Any table can be the metatable of any value; a group of related tables can
share a common metatable, which describes their common behavior;
a table
can be its own metatable, so that it describes its own individual behavior
.
Any
configuration is valid.


13.1 Arithmetic Metamethods

Now, we want to use the addition operator (‘+’) to compute the union of
two sets. For that, we will arrange for all tables representing sets to share a
metatable. This metatable will define how they react to the addition operator.

To keep our  namespace clean, we store these functions inside a table called Set.


--my implement version

----metatable and metamethod
local mt={};
local Set={};

Set.new=function(s)
   local newSet={};
   for _,v in pairs(s) do
      newSet[v]=true;
   end
   setmetatable(newSet,mt);
   return newSet;
end

Set.union=function(s1,s2)
    local newset=Set.new{};

-- do not have the Object orientation , table not the Object language's class, so here we can just call new{};

-- 这里没有对象类and like 之类的概念否则我们可以写成 new{}, no need Set.new{}
    for v in pairs(s1) do
    newset[v]=true;
    end
    for v in pairs(s2) do
    newset[v]=true;
    end
   return  newset;
end
Set.intersection=function(s1,s2)
    local newset=Set.new{};
    for k in pairs(s1) do
       newset[k]=s2[k];-- if s2[k] !=nill,then they are shared by s1 and s2, that mean they are intersect
    end
   return newset;
 
end
function Set.tostring(s)
    local container={};
    container[1]="{";
    for k in pairs(s)  do
       container[#container+1]=k;
    end
     container[#container+1]="}";
    -- return "{"..table.concate(container).."}" --we do not follow the book's version,because when use .. to contacte a string, need to reallocate for table.concate(container) , 2 times for this allocation, because at last there is ..
     return  table.concat(container,",");--of cause this will case {, setKeys ,} --too more comma appear, but not my mind
end

function Set.print (s)
  print( Set.tostring(s));
end

s1 = Set.new{10, 20, 30, 50}
s2 = Set.new{30, 1}
print(getmetatable(s1))
print(getmetatable(s2))

mt.__add=Set.union;
mt.__mul=Set.intersection;

s3=s1+s2;
Set.print(s3);
s4=(s1+s2)*s2;
Set.print(s4);

> dofile("lession13.lua");
table: 002EA660   ---
table: 002EA660   -- 可以看到创建的Set share the same metatable.
{,1,30,50,20,10,}  -- + operator
{,1,30,}                  --* operator
>


==================

For each arithmetic operator there is a corresponding field name in a metatable.
Besides __add and __mul, there are __sub (for subtraction), __div (for division),
__unm (for negation), __mod (for modulo), and __pow (for exponentiation).
We may define also the field __concat, to describe a behavior for the concatenation
operato
r  每一个算术运算符号都对应了一个field name in a metatable.


=============

s = Set.new{1,2,3}
s = s + 8

When looking for a metamethod, Lua does the following steps: if the first value  has a metatable with an __add field, Lua uses this field as the metamethod,

然后就将这两个s ,8 出入 __add 方法里面, 第二个operand 就不用管了,因为已经找到  independently  of the second value;

otherwise, if the second value has a metatable  with an __add field, Lua uses this field as the metamethod; otherwise, Lua raises   an error.


Therefore, the last example will call Set.union, as will the expressions
10+s and "hello"+s.
Lua does not care about these mixed types, but our implementation does.

If  we run the s=s+8 example, the error we get will be inside Set.union:
bad argument #1 to 'pairs' (table expected, got number)



--要想robust 就这么做拉:看看他们是不是用同一个metable

function Set.union (a, b)
if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
error("attempt to 'add' a set with a non-set value", 2)
end


Remember that the second argument to error (2, in this example) directs the
error message to where the operation was called
.


==============

13.2 Relational Metamethods

  __eq (equal to), __lt (less than), and __le (less than or equal  to).

There are no separate metamethods for the other three relational operators:
Lua translates a~=b to not(a==b), a>b to b<a, and a>=b to b<=a. 我想平时的运算数值的大小比较应该也是一样的,

没有大于号, a>b 只能交换一下位置,改为a,b ,也没有>= ,只有<= ....    只要记住,不等转为  = 再取反   , 大于或大于等于转为< <=

<= 为什么会存在的 , <= 本来可以转为  no + < , 比如  a<=b  其实可以转为 not (b<a), 如果a 大于b 不成立,那么 b就是<= b 的,但为什么要专门搞出个<= ,原因是某些情况下这样的交换会出问题。比如NaN, 

According to the IEEE 754   standard, NaN represents undefined values, such as the result of 0/0.

The   standard specifies that any comparison that involves NaN should result in false.  任何跟 NaN比较都是false,

This means that NaN<=x is always false, but x<NaN is also false. It also implies    that the translation from a<=b to not(b<a) is not valid in this case. 也就是如果NaN<=x 转为 not(x<NaN) ,then will become true 那就跟规范不符合了


In our example with sets, we have a similar problem. An obvious (and  useful) meaning for <= in sets is set containment: a<=b means that a is a subset  of b. With this meaning, again it is possible that both a<=b and b<a are false;
therefore, we need separate implementations for __le (less or equal) and __lt  (less than):

我想那怕是连C/C++ java 里面也应该是因为这个原因所以给出来 <= ,


=========MY implementaton.

mt.__le=function(s1,s2)   ---- set containment
     for k in pairs (s1) do
         if not s2[k] then
            return false;
         end
     end
     return true;
end
mt.__lt=function(s1,s2)  -- s1 total contain in s2,  all the element in s1 also in s2, but some element in s2 not in s1
      
    return s1<=s2 and not s2<=s1;
end
mt.__eq=function(s1,s2)
   return s1<=s2 and s2<=s1;
end


The equality comparison has some restrictions. If two objects have different
basic types or different metamethods, the equality operation results in false,
without even calling any metamethod. So, a set will always be different from a
number, no matter what its metamethod says.


s1<2 ,不用调用 metamethod 就是false,因为s1 and 2 are differenct type.


13.3 Library-Defined Metamethods

it is a common practice for libraries  to define their own fields in metatables. libraries, 比如我们平常用的function, tostring,print, dofile, 就是lib里面的,   而类似 +,- * / 就是Lua core 提供的 VM


Function tostring provides a typical example. As we saw earlier, tostring  represents tables in a rather simple format:

print({}) --> table: 0x8062ac0  --打印的是地址跟类型


Function print always calls tostring to format its outputtostring first checks whether the value has a __tostring
metamethod.

In this case, tostring calls the metamethod to do its job, passing
the object as an argument. Whatever this metamethod returns is the result of
tostring.  如果没有__tostring,那就自己打印类型跟地址作为结果


 
mt.__tostring = Set.tostring

s1 = Set.new{10, 4, 5}
print(s1) --> {4, 5, 10}

如果comment  mt.__tostring = Set.tostring ,那最后也只是打印类型and addre ss


Functions setmetatable and getmetatable also use a metafield, in this case  to protect metatables. Suppose you want to protect your sets, so that users  can neither see nor change their metatables.

If you set a __metatable field  in the metatable, getmetatable will return the value of this field, whereas
setmetatable will raise an error:

mt.__metatable = "not your business"
s1 = Set.new{}
print(getmetatable(s1)) --> not your business
setmetatable(s1, {})
stdin:1: cannot change protected metatable

--Set.new 方法里面也有setmetatable,  但并没有报这个error, why,很简单拉,刚开始的时候 那个table是没有metatable 的麻


In Lua 5.2, pairs and ipairs also got metatables, so that a table can modify
the way it is traversed (and non-table objects can be traversed).  --没看懂噢,,,


-------------------

13.4 Table-Access Metamethods

But Lua also offers a way to change the behavior of tables for two
normal situations, the query and modification of absent fields in a table.


The __index metamethod

I said earlier that, when we access an absent field in a table, the result is nil.
This is true, but it is not the whole truth. Actually, such accesses trigger the
interpreter to look for an __index metamethod: if there is no such method, as
usually happens, then the access results in nil; otherwise, the metamethod will
provide the result
.


利用这个特性可以实现继承:

prototype={x=0,y=0,width=100,height=100};
mt={};
function newWindow(w)
   setmetatable(w,mt);
 return w;
end
mt.__index=function(_,key) --the first dummy parameter _,that's the table itself which can not find the key.
    print(_);
   return prototype[key];
end
newWin=newWindow{x=100,y=100};
print("newWinAddress",newWin);
print(newWin.width);

====================

The use of the __index metamethod for inheritance is so common that Lua
provides a shortcut. Despite being called a method, the __index metamethod

does not need to be a function: it can be a table, instead. When it is a function,
Lua calls it with the table and the absent key as its arguments, as we have just
seen. When it is a table, Lua redoes the access in this table. Therefore, in our
previous example, we could declare __index simply like this:
mt.__index = prototype  --这种写法其实不就是相当于 extends prototype, 更让人觉得是继承的特


The use of a table as an __index metamethod provides a fast and simple
way of implementing single inheritance. A function, although more expensive,
provides more flexibility: we can implement multiple inheritance, caching, and
several other variations.
We will discuss these forms of inheritance in Chapter
16.


When we want to access a table without invoking its __index metamethod,
we use the rawget function. The call rawget(t,i) does a raw access to table t,
that is, a primitive access without considering metatables. Doing a raw access
will not speed up your code (the overhead of a function call kills any gain you
could have), but sometimes you need it, as we will see later

print(rawget(newWin,"width")); ---nil ,,will not access the __index method


The __newindex metamethod

    When you assign a value to an absent index (如果是existing index,则不会call __newindex) in a table, the interpreter  looks for a __newindex metamethod: if there is one, the interpreter calls it
instead of making the assignment. Like __index, if the metamethod is a table,
the interpreter does the assignment in this table, instead of in the original one.



prototype={x=0,y=0,width=100,height=100};
mt={};
function newWindow(w)
   setmetatable(w,mt);
 return w;
end
mt.__index=function(_,key) --the first dummy parameter _,that's the table itself which can not find the key.
    print(_);
   return prototype[key];
end
mt.__newindex=function(_,key,_newValue)
    print(_,key,_newValue);
    --_[key]=_newValue; you can not do this will call __newindex again and again...
    rawset(_,key,_newValue);--如果要默认的assigment,其实就不用实现__newindex 方法拉,这里just for demo
end
newWin=newWindow{x=100,y=100};
newWin.NewKey="1000";
print(newWin.NewKey);


------------------------------------------

Tables with default values

The default value of any field in a regular table is nil. It is easy to change this  default value with metatables:
function setDefault (t, d)
local mt = {__index = function () return d end}   -- 注意,以来我都认为创建一个closure 是 函数里面返回的函数,

-- 这个方法让我重新认识closure, 其实只要一个function created inside the parent function, and 引用到了parent function 的东西,那么Lua 就必须创建一个closure,
setmetatable(t, mt)
end

tab = {x=10, y=20}
print(tab.x, tab.z) --> 10 nil  -- 默认行为
setDefault(tab, 0)
print(tab.x, tab.z) --> 10 0  --利用metatable __index ,改变默认的行为

The setDefault function creates a new closure plus a new metatable for each
table that needs a default value. This can be expensive if we have many tables
that need default values
. I 

==============================

local mt={
__index=function(_,key)
  return _._____Mmydefaultvalue;
end
};

local function setDefaultValue(o,defaultValue)
o._____Mmydefaultvalue=defaultValue;  --关键这里我们利用自己的table store default,那样所有的table can share the same metatable,but have their default value,No need closure now.
setmetatable(o,mt);
end

ta={

};
tb={

}
setDefaultValue(ta,"abc");
setDefaultValue(tb,"cde");
print(ta.whatcount);
print(tb.whatcount); -- you can see ta,tb have shared metatable,but different default value for each one


An alternative approach for associating each table with its default value is
to use a separate table, where the indices are the tables and the values are their
default values.(_____Mmydefaultvalue 是怕重复我命名为这样,但其实这个key ,can be the table itself,)
However, for the correct implementation of this approach, we
need a special breed of table called weak tables, and so we will not use it here;
we will return to the subject in Chapter 17.

local function setDefaultValue(o,defaultValue)
--o.o=defaultValue;  --we can not do this like that.,need to use weaktable, will return back here .
setmetatable(o,mt);
end


===============to be here when read though the Chapter 17==============





Tracking table accesses


Both __index and __newindex are relevant only when the index does not exist in
the table. The only way to catch all accesses to a table is to keep it empty.
So,
if we want to monitor all accesses to a table, we should create a proxy for the
real table. This proxy is an empty table, with proper __index and __newindex
metamethods that track all accesses and redirect them to the original tabl
e.


=========my version

function setProxy(t)
  local proxy={};
  local mt={
  __index=function (_,key)
  print("you are try to aceess "..key);
  return  t[key]; -- you can  not write t.key, if then will try to insert a new key,his key is "key" , {key=???} , you need to use [key]
  end
  ,
  __newindex=function(_,key,value)
  print("you are try a update table",key,value);
  t[key]=value;
  end,


__pairs=function(_) -- if not implement __pairs, then when iterator the table,will get nothing.
          return  function (_,key) print("tracking table pairs",_,key) return next(t,key);end
          --  you can also write return  pairs(t), or next(t,key); but can insert any track code, so I write a function but we can return our iterator also
          end
 
  };


 
  };
  setmetatable(proxy,mt);
  return proxy;

end

t={a=1,b=2,c=4};
t=setProxy(t); -- 偷天换日
print(t.a);
t.a="1000";
print(t.a);


non empty key 我们是没办法track 的,没办法,只能利用一个empty table (proxy) , 然后利用closure ,metatable 的特性,当你访问一个key时候,其实是访问 the proxy, the empyt table.
then we redirect to the orignal table, and add some track code between this process



========== below version: share the same metable========

 local index={};
  local mt={
  __index=function (_,key)
  print("you are try to aceess "..key);
  return  _[index][key]; -- you can  not write t.key, if then will try to insert a new key,his key is "key" , {key=???} , you need to use [key]
  end
  ,
  __newindex=function(_,key,value)
  print("you are try a update table",key,value);
  _[index][key]=value;
  end,
 
  __pairs=function(proxy)
          return  function (_,key) print("tracking table pairs") return next(proxy[index],key);end
          --  you can also write return  pairs(t), or next(t,key); but can insert any track code, so I write a function but we can return our iterator also
          end
 
  };
 

function setProxy(t)
  local proxy={};
  proxy[index]=t;
  setmetatable(proxy,mt);
  return proxy;

end

t={a=1,b=2,c=4};
t=setProxy(t);
print(t.a);
t.a="1000";


for k,v in pairs(t) do
 print (k,v); -- now t is a proxy will be empty if we do not implement __pairs
end


===========

Read-only tables

function readOnly (t)
local proxy = {}
local mt = { -- create metatable
__index = t, -- no need a function ,if we do not want to track the accessing
__newindex = function (t, k, v)
error("attempt to update a read-only table", 2)  --just here raise an error
end
}
setmetatable(proxy, mt)
return proxy
end


 































































-- The underscore is typically used to start special values

-- like _VERSION in Lua.

 

print(_VERSION)

 

-- So don't use variables that start with _,

-- but a single underscore _ is often used as a

-- dummy variable. local _, x = string.find(s, p), 因为find 返回两个value,如果我们只是local x,则只是接收了第一个,所以需要一个dummy的 位置参数,, 方法参数里面也是如实 custMethod(_,a) ,我们想保留第一参数的位置,加以dummy 的形参.

















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值