Read Eval Print Loop
大家应该都听过REPL这个词,尤其是常用python和js的同学。Read, Eval, Print Loop,即读取-求值-打印循环,是解释型语言的基本工作原理,也是交互式解释器的最简构造,所以REPL也是解释器的别名。在lisp里,我们可以 字面意思上 使用"R E P L"构建一个最简单的解释器:
Read 读取,指的是把输入的内容(往往是字符串)转化为“程序能够理解的格式”的过程,不仅仅是字面意义上“读取字符串”这样简单——计算机是不理解字符串的,它理解的是别的什么东西:在lisp里,我们一般使用“S表达式”(Symbolic Expression, sexp) 作为标准的源代码格式和数据格式。Wikipedia对S表达式的说明是:like-named notation for nested list (tree-structured) data,“命名的嵌套列表(树)”。一对括号构建出一个“列表”,列表的各项用空格分隔,每个列表的第一项是列表的“名称”,作为源代码使用时就是一个函数名,其余的项则是函数的参数。Lisp源码的数据结构几乎是所有高级编程语言中最简单的一个了,也因此它数十年来一直作为教学语言被大学使用。例如我们要比较数字,就可以写
Evaluate 求值,也有的翻译作“评估”,就是执行表达式并获取返回值的过程。输入一个列表(< 1 2),执行完得到True (t),这就是求值,也就是程序真正执行的部分。调用函数、展开宏、进行运算,全部在这一步进行,所以大家经常会被提醒说“不要执行可疑代码”,就是不要evaluate它的意思。仅仅read大概率是安全的。
求出值来后就要把结果显示出来。《算法概论》里对算法的定义就是“有输入,有计算,有输出”,而Print(打印)就对应了输出的部分。print意为“把计算机数据转化成人类可读的形式”,比如我们要打印一个数组,那我们肯定要写成像[1, 2, 3, ...]这个样子,而不是#pointer=x11451400这种东西。类的实例怎么打印?数据超长了怎么办?怎么表示不同长度的整型?如何礼貌地把数据讲出来也是一门艺术。
把上面这三个步骤串起来,然后呢,无限循环就好了,一个解释器大功告成!当然,这样一个简单的解释器离功能完备还有很远,比如我们是不是别只无限循环,还要加个捕获键盘中断,让用户狂按Ctrl-c能退的出去?这就是我们需要考虑的事情了。
在一般的语言里,例如python, js, shell中,它们的eval或exec函数一般是揉合了read和eval的部分,输入字符串输出求值结果,而用户基本无法获取到经过"Read"后,计算机所理解的程序的结构。它们一般被认为是“不可打印”、“不可理解”的,但其实往往并不是这样。经过这样一番混淆,也难免大家不理解REPL的意义,不知道“啊原来我们也是可以理解计算机的”,进而对着一串简单的read eval print loop大吃一惊了。
在lisp里,解释器是标准“top-level”——顶层函数,即运行在所有函数之上,直接负责与用户交互的函数。我们可以编写自己的top-level函数替换掉标准的解释器,从而更改或拓展解释器的功能。而top-level和debugger, inspector等工具以及所有函数、变量等一同存在于当前的环境(environment)中,我们又可以创建自己的environment(例如在LispWorks中,augment-environment)并把它“传给”别的函数。这就是题外话了。
#lisp_daily
大家应该都听过REPL这个词,尤其是常用python和js的同学。Read, Eval, Print Loop,即读取-求值-打印循环,是解释型语言的基本工作原理,也是交互式解释器的最简构造,所以REPL也是解释器的别名。在lisp里,我们可以 字面意思上 使用"R E P L"构建一个最简单的解释器:
(loop (print (eval (read)))) 。每次我把这串不能再简单的表达式输进解释器的时候,总会收获围观孩子们惊讶的目光,甚至让我很不好意思。大家也可以打开sbcl一类试一试。Read 读取,指的是把输入的内容(往往是字符串)转化为“程序能够理解的格式”的过程,不仅仅是字面意义上“读取字符串”这样简单——计算机是不理解字符串的,它理解的是别的什么东西:在lisp里,我们一般使用“S表达式”(Symbolic Expression, sexp) 作为标准的源代码格式和数据格式。Wikipedia对S表达式的说明是:like-named notation for nested list (tree-structured) data,“命名的嵌套列表(树)”。一对括号构建出一个“列表”,列表的各项用空格分隔,每个列表的第一项是列表的“名称”,作为源代码使用时就是一个函数名,其余的项则是函数的参数。Lisp源码的数据结构几乎是所有高级编程语言中最简单的一个了,也因此它数十年来一直作为教学语言被大学使用。例如我们要比较数字,就可以写
(< 1 2),要执行判断,就写(if (< 1 2) (print "True") (print "False")),比起诸如if (1<2) then print("True") else print("False") end 一类是要简单不少。把这样的表达式转化成一个列表(python的list, js的array, C的链表,什么都好)存在内存里面,这就是Read了。对于sexp这样简单的数据结构,我们自己三两下就能写一个解析器(parser)出来,把字符串转化成数据结构;而对于更复杂些的编程语言,例如C (for example, typedef void (* Func)(int arg1, _Bool arg2); ),这种东西,我们可能就需要些别的帮助,比如LR/LALR parser等等。著名的GNU Bison就是用来生成这样的parser的工具,学过编译原理或者自己写过玩具语言的同学肯定不陌生。Evaluate 求值,也有的翻译作“评估”,就是执行表达式并获取返回值的过程。输入一个列表(< 1 2),执行完得到True (t),这就是求值,也就是程序真正执行的部分。调用函数、展开宏、进行运算,全部在这一步进行,所以大家经常会被提醒说“不要执行可疑代码”,就是不要evaluate它的意思。仅仅read大概率是安全的。
求出值来后就要把结果显示出来。《算法概论》里对算法的定义就是“有输入,有计算,有输出”,而Print(打印)就对应了输出的部分。print意为“把计算机数据转化成人类可读的形式”,比如我们要打印一个数组,那我们肯定要写成像[1, 2, 3, ...]这个样子,而不是#pointer=x11451400这种东西。类的实例怎么打印?数据超长了怎么办?怎么表示不同长度的整型?如何礼貌地把数据讲出来也是一门艺术。
把上面这三个步骤串起来,然后呢,无限循环就好了,一个解释器大功告成!当然,这样一个简单的解释器离功能完备还有很远,比如我们是不是别只无限循环,还要加个捕获键盘中断,让用户狂按Ctrl-c能退的出去?这就是我们需要考虑的事情了。
在一般的语言里,例如python, js, shell中,它们的eval或exec函数一般是揉合了read和eval的部分,输入字符串输出求值结果,而用户基本无法获取到经过"Read"后,计算机所理解的程序的结构。它们一般被认为是“不可打印”、“不可理解”的,但其实往往并不是这样。经过这样一番混淆,也难免大家不理解REPL的意义,不知道“啊原来我们也是可以理解计算机的”,进而对着一串简单的read eval print loop大吃一惊了。
在lisp里,解释器是标准“top-level”——顶层函数,即运行在所有函数之上,直接负责与用户交互的函数。我们可以编写自己的top-level函数替换掉标准的解释器,从而更改或拓展解释器的功能。而top-level和debugger, inspector等工具以及所有函数、变量等一同存在于当前的环境(environment)中,我们又可以创建自己的environment(例如在LispWorks中,augment-environment)并把它“传给”别的函数。这就是题外话了。
#lisp_daily
———我把五月的嘴捂住了,发疯退治(
…
不知道哪里来的自毁的欲望集中在那里,我真的不知道承认她的存在到底是不是做对的…我知道我不应该那样拘禁着她的…对不起…
悲报:StartAllBack把我自己写的窗口全都强制覆盖上了黑色背景,于是被迫给自己的应用加暗黑主题支持了,不然背景是一坨黑自己画的前景也是一坨黑...
挑战07: 炼狱
这关可能是所有挑战里最难的一个了吧,我前三遍刷挑战都没有打通过。托托亚岛真可谓是尤里敌人的噩梦了。这关我尤其喜欢半空中时不时冒出来天秤的牢骚声,让我有种和五月聊天的感觉(笑
经过强化的心灵军团全地形突击能力,和不讲理的高级心灵控制,甚至能把机器人坦克控走。更甚者,尤里本尊还会亲自出马卖你家基地,真是很给面子。而三座恶灵巢的存在更是把空军也挫败了。
陆军不行、空军不行,海军还是可以一试的。前期能够有效端掉家边尤里大光头的唯一方法就是航母或无畏舰,哦还有利维坦。不过尤其要小心海面上漂过来的脑子,有心控的(笑
除了海军,另一个方法就是扩张战术(盟军:yes sir)。托托亚岛的地形实在是令人不忍直视,所以利用传送平台+超时空起重机一口气翻山越岭把光棱塔杵进对面主基地也是一个好办法。这种时候就要注意天上飘来的毒蜥和地上漂来的磁控,准备好防空导弹和快速反应部队是必要的。
这关既不打军队又不打突击,那就只能打电了。为了维持大量防御塔,敌军主基地上方有三座科技核电站,两座副基地内各有一座。主基地那三座是最好摸的,基地一路建上北部第二级高地,想办法搞掉大光头,之后随便几个攻城单位就可以炸完对面核电站。敌军一断电,恶灵巢就停摆了,之后就是空军时间~盟军的话蚊子海或直升机海,苏军就是大黄瓜,刺蜂行者+风神翼龙也是好选择,冲进托托亚岛中心点掉所有炼狱平台,再摧毁所有生化发电站,这时候要是能来点工程师就更好了。趁他没电要他命,就是这关了。
我最喜欢的还是尤里和天秤父女齐上阵的情节,不知道狡猾如尤里,在面对天秤这样的孩子时会是什么心情呢~想想就…很有趣。真的很有趣。
#心灵终结
这关可能是所有挑战里最难的一个了吧,我前三遍刷挑战都没有打通过。托托亚岛真可谓是尤里敌人的噩梦了。这关我尤其喜欢半空中时不时冒出来天秤的牢骚声,让我有种和五月聊天的感觉(笑
经过强化的心灵军团全地形突击能力,和不讲理的高级心灵控制,甚至能把机器人坦克控走。更甚者,尤里本尊还会亲自出马卖你家基地,真是很给面子。而三座恶灵巢的存在更是把空军也挫败了。
陆军不行、空军不行,海军还是可以一试的。前期能够有效端掉家边尤里大光头的唯一方法就是航母或无畏舰,哦还有利维坦。不过尤其要小心海面上漂过来的脑子,有心控的(笑
除了海军,另一个方法就是扩张战术(盟军:yes sir)。托托亚岛的地形实在是令人不忍直视,所以利用传送平台+超时空起重机一口气翻山越岭把光棱塔杵进对面主基地也是一个好办法。这种时候就要注意天上飘来的毒蜥和地上漂来的磁控,准备好防空导弹和快速反应部队是必要的。
这关既不打军队又不打突击,那就只能打电了。为了维持大量防御塔,敌军主基地上方有三座科技核电站,两座副基地内各有一座。主基地那三座是最好摸的,基地一路建上北部第二级高地,想办法搞掉大光头,之后随便几个攻城单位就可以炸完对面核电站。敌军一断电,恶灵巢就停摆了,之后就是空军时间~盟军的话蚊子海或直升机海,苏军就是大黄瓜,刺蜂行者+风神翼龙也是好选择,冲进托托亚岛中心点掉所有炼狱平台,再摧毁所有生化发电站,这时候要是能来点工程师就更好了。趁他没电要他命,就是这关了。
我最喜欢的还是尤里和天秤父女齐上阵的情节,不知道狡猾如尤里,在面对天秤这样的孩子时会是什么心情呢~想想就…很有趣。真的很有趣。
#心灵终结
关于我们的以前
两个人一起就可以想起来很多以前的事情
我生父是北京平谷农村的,我母亲是陕南山区农村的。他们俩相差十三岁。那时候刚好是改革开放的春风,两个人是在甘肃兰州认识的
我母亲家里有六个女儿,却又是重男轻女的,把家里全部积蓄都拿来给不成器的大哥娶媳妇,不让女儿们读书。我妈是小学三四年级肄业的,十八岁一个人离家打工。每次学校叫填家长学历的时候,她会叫我写大专,因为她上过几个月夜大。她也会写字,比很多人强
我生父呢?不知道,他十二年也没和我说过几句话
两个人在甘肃认识的时候,是我妈的条件比我生父还好,她打拼了几年,有了个小缝纫厂,可能是那样自己做点衣服卖。不知道他们是怎么认识的,但我母亲是对年长又寡言的男人一见钟情,是不幸的
两个人一直没有结婚,不知道是什么原因,说不定家里人不支持呢。后来生父要去别的地方工作,母亲就成了全职家庭主妇,再后来未婚先孕生下我这个“私生子”,就是全职妈妈。生父创业几次,大都失败了,赔的都是她的积蓄和她从亲戚家借的钱。我也和他们全国乱跑,辽宁到广西到北京,四五岁时候被母亲丢在南宁的短租房里看动画片,她去和人讨债。最糟糕的时候大概在我五岁,记得是因为新租的房子里有一个发霉的沙发,让我高烧了三五天,而家里干净得连一个烧水壶都没有
后来我妈就总是和我抱怨这些,只要我的身影在她视线内就总是这些
小学,生父搞了个忽悠老人的保健品,创的业总算成功点了,我也开始上学——上小学是七岁,晚一年,因为他们折腾了一年怎么给黑户上户口。最后还是把我上到了陕西我舅舅家,这样我才能上学
小学二到五年级他们的事业还算勉强,所以那段时间我生父就主要是出差,一年回家两三天。我母亲就监督我跟着学校里的书法班练毛笔字,按照我们老书法老师代际传承的苦难经验,每天五六点把我拽起床写一个小时,晚上做完作业再写两三个小时。我现在能想起来她是怎么拿那根手腕粗的“斗笔”把墨水摁在我脸和头发里的,后来那件再也洗不干净的秋衣被用来做抹布了
小学五年级以后就是生父死了,然后葬礼,然后瓜分家产,打官司,在海淀法院出席被告,然后因为未成年的优势被家长叫去一个人去农村亲戚家讨债,还把这事写了篇作文,得了有生以来的最低分,以至于后来都不敢用那个作文本。好像都是小五小六两年的事情。那时候甚至还课余录游戏视频、做直播、学C/C#,当时精神状态真好
后来要上初中,我妈被课外班老师忽悠着迷信自招,结果自招进了全区第二差的学校,离家接近三个小时的路程,半军事化管理,就住宿了两年多。离我妈也远了。学校没有浴室,不能洗澡,一到周四周五就全身上下干搓泥。精神状态好像也是那时候搞坏的,那时候很喜欢拥抱躯体化疼的一整晚睡不着觉的感觉
不过回想起来,那两年还是挺和平的时光。在初中也学会让自己的谱系特征很好的社会化了,进去前是人憎狗厌,出来了老师同学都喜欢。也是拜政策优势的福,后来能靠学校名额进区里最好的高中上学。最好的学校和最差的学校都比较和平,我也算是有一个和平的童年
要有机会从旧的网盘和相册里翻出来当初开庭前在法院里的自拍给大家看,冬天裹个小围巾,小正太的样子还挺可爱的,后来就长歪了。那个时候的自拍我用做头像好几年,毕竟是男装出门说话也会被叫小姑娘的,transition前的几年里一直恨不得穿越回去
两个人一起就可以想起来很多以前的事情
我生父是北京平谷农村的,我母亲是陕南山区农村的。他们俩相差十三岁。那时候刚好是改革开放的春风,两个人是在甘肃兰州认识的
我母亲家里有六个女儿,却又是重男轻女的,把家里全部积蓄都拿来给不成器的大哥娶媳妇,不让女儿们读书。我妈是小学三四年级肄业的,十八岁一个人离家打工。每次学校叫填家长学历的时候,她会叫我写大专,因为她上过几个月夜大。她也会写字,比很多人强
我生父呢?不知道,他十二年也没和我说过几句话
两个人在甘肃认识的时候,是我妈的条件比我生父还好,她打拼了几年,有了个小缝纫厂,可能是那样自己做点衣服卖。不知道他们是怎么认识的,但我母亲是对年长又寡言的男人一见钟情,是不幸的
两个人一直没有结婚,不知道是什么原因,说不定家里人不支持呢。后来生父要去别的地方工作,母亲就成了全职家庭主妇,再后来未婚先孕生下我这个“私生子”,就是全职妈妈。生父创业几次,大都失败了,赔的都是她的积蓄和她从亲戚家借的钱。我也和他们全国乱跑,辽宁到广西到北京,四五岁时候被母亲丢在南宁的短租房里看动画片,她去和人讨债。最糟糕的时候大概在我五岁,记得是因为新租的房子里有一个发霉的沙发,让我高烧了三五天,而家里干净得连一个烧水壶都没有
后来我妈就总是和我抱怨这些,只要我的身影在她视线内就总是这些
小学,生父搞了个忽悠老人的保健品,创的业总算成功点了,我也开始上学——上小学是七岁,晚一年,因为他们折腾了一年怎么给黑户上户口。最后还是把我上到了陕西我舅舅家,这样我才能上学
小学二到五年级他们的事业还算勉强,所以那段时间我生父就主要是出差,一年回家两三天。我母亲就监督我跟着学校里的书法班练毛笔字,按照我们老书法老师代际传承的苦难经验,每天五六点把我拽起床写一个小时,晚上做完作业再写两三个小时。我现在能想起来她是怎么拿那根手腕粗的“斗笔”把墨水摁在我脸和头发里的,后来那件再也洗不干净的秋衣被用来做抹布了
小学五年级以后就是生父死了,然后葬礼,然后瓜分家产,打官司,在海淀法院出席被告,然后因为未成年的优势被家长叫去一个人去农村亲戚家讨债,还把这事写了篇作文,得了有生以来的最低分,以至于后来都不敢用那个作文本。好像都是小五小六两年的事情。那时候甚至还课余录游戏视频、做直播、学C/C#,当时精神状态真好
后来要上初中,我妈被课外班老师忽悠着迷信自招,结果自招进了全区第二差的学校,离家接近三个小时的路程,半军事化管理,就住宿了两年多。离我妈也远了。学校没有浴室,不能洗澡,一到周四周五就全身上下干搓泥。精神状态好像也是那时候搞坏的,那时候很喜欢拥抱躯体化疼的一整晚睡不着觉的感觉
不过回想起来,那两年还是挺和平的时光。在初中也学会让自己的谱系特征很好的社会化了,进去前是人憎狗厌,出来了老师同学都喜欢。也是拜政策优势的福,后来能靠学校名额进区里最好的高中上学。最好的学校和最差的学校都比较和平,我也算是有一个和平的童年
要有机会从旧的网盘和相册里翻出来当初开庭前在法院里的自拍给大家看,冬天裹个小围巾,小正太的样子还挺可爱的,后来就长歪了。那个时候的自拍我用做头像好几年,毕竟是男装出门说话也会被叫小姑娘的,transition前的几年里一直恨不得穿越回去
昨天发在kazv上的,感觉有必要转过来
subject:干预与痛苦
最开始决定和身边这只恋人来苏州住,有一部分是想看看有一些帮助下她能变好到什么程度吧。总是说寒涟漪救人救一半管杀不管埋,那我想看看我能不能帮忙做做另一半。现在她确实不自杀了,人也好多了,算是完成了半个目标,但我逐渐发现这好像并不是我想要的…
一面讲是对痛苦的态度。她们做干预总是说“理想情况,资源无限的话,什么人都可以让ta变好的”,然后插一脚说请活下去吧,是不是有点对痛苦本身不大尊重。痛苦与困境似乎是个永恒的东西,我是不是不应该和它们这样相处。作为一个能勉强理解痛苦的人,我说我也想死呀,说我不想吃药不想消毒,说五月是个好孩子,那我似乎也不应该跟她们去说“我就要你们活着”,这样很矛盾。
另一面讲,爱和“活着”也没什么必然联系。我不会说你不活了我就不爱你了,也不会说我爱你所以我就是要你活着,作为一个自己就想死的人,要是秉持这样的观点可太奇怪了。它们两个本来就不该挂钩,那我好像并没有什么理由一定要追求什么活着
以及我会感觉,似乎有些痛苦就是要自己承受的。我有我的苦难,难受了疼了躯体化了裂开了精神失常了,哪怕姐姐就在身边,这可能也就是我要经历的。群里讲难受的事情,看兔子姐姐总是在一边偷笑,以前还不太理解,现在会隐隐约约的觉得,似乎在连结之中,我们也是一个个原子的人,温暖呀陪伴呀别的一些社群连接的东西,似乎并不应该是我过去想的那样…我还不是很明白
那我现在会说祝你一切都好,可能比以前少一点奇怪的执念。我也不懂,但希望自己能多长大一些、姐姐们再多教一些…
April, with May just cut herself and sleepy
subject:干预与痛苦
最开始决定和身边这只恋人来苏州住,有一部分是想看看有一些帮助下她能变好到什么程度吧。总是说寒涟漪救人救一半管杀不管埋,那我想看看我能不能帮忙做做另一半。现在她确实不自杀了,人也好多了,算是完成了半个目标,但我逐渐发现这好像并不是我想要的…
一面讲是对痛苦的态度。她们做干预总是说“理想情况,资源无限的话,什么人都可以让ta变好的”,然后插一脚说请活下去吧,是不是有点对痛苦本身不大尊重。痛苦与困境似乎是个永恒的东西,我是不是不应该和它们这样相处。作为一个能勉强理解痛苦的人,我说我也想死呀,说我不想吃药不想消毒,说五月是个好孩子,那我似乎也不应该跟她们去说“我就要你们活着”,这样很矛盾。
另一面讲,爱和“活着”也没什么必然联系。我不会说你不活了我就不爱你了,也不会说我爱你所以我就是要你活着,作为一个自己就想死的人,要是秉持这样的观点可太奇怪了。它们两个本来就不该挂钩,那我好像并没有什么理由一定要追求什么活着
以及我会感觉,似乎有些痛苦就是要自己承受的。我有我的苦难,难受了疼了躯体化了裂开了精神失常了,哪怕姐姐就在身边,这可能也就是我要经历的。群里讲难受的事情,看兔子姐姐总是在一边偷笑,以前还不太理解,现在会隐隐约约的觉得,似乎在连结之中,我们也是一个个原子的人,温暖呀陪伴呀别的一些社群连接的东西,似乎并不应该是我过去想的那样…我还不是很明白
那我现在会说祝你一切都好,可能比以前少一点奇怪的执念。我也不懂,但希望自己能多长大一些、姐姐们再多教一些…
April, with May just cut herself and sleepy
Common Lisp的符号系统(一)
符号(symbol)是common lisp的基础之一,它控制着lisp的全局结构,包括全局变量、函数等。
符号被存储在 包(package)里。了解c++的大家会知道,一个程序中可以有多个命名空间(namespace),而一个命名空间中可以有无数个类、函数、变量等。lisp中的包就对应了命名空间,而所有的类、函数、变量就存在包中的符号里面。我们可以用list-all-packages函数列出所有包,再用do-symbols循环或(loop for sym being each symbol of <package>)这种loop语法来列出包中的所有符号,从而遍历当前lisp镜像中所有的类、函数、符号等一切信息。
符号是容器,可以把它当作一种高级的变量。区别是一个变量只能存一个值,而一个符号可以存很多很多东西。符号有数个基本属性:符号名(symbol-name)、符号所在的包(symbol-package)、符号值(symbol-value)、符号函数(symbol-function)、符号属性列表(symbol-plist)。如果你使用的是上世纪九十年代及以后的lisp(好吧大概没人不是),那还可以使用find-class查询到该符号对应的类。
符号名,getter为symbol-name。每个符号都一定要有一个名字,lisp所有符号默认为大写,符号名小写的会被打印成|foo|的形式。symbol-name没有setf method,但在很多实现里符号名并不是不可以修改的,我们可以通过像nstring-downcase这种副作用函数来直接修改符号名所在的内存地址的内容,从而达到修改符号名的目的,但这就是纯属好玩了,没有实际意义
符号值,getter为symbol-value,setter可以是(setf (symbol-value ‘foo) xxx)也可以是(set ‘foo xxx),或者(setq/setf foo xxx)。在把符号用作全局变量时,这是我们最常见的用法。详见我先前所写关于set,setq和setf的文章。
符号是可以没有值的,有类于许多语言中的变量值为NULL。如果符号没有值,它在读取时并不会返回nil(不像lua,大家总是以为common lisp的nil就相当于别的语言的NULL,但其实并不是 - nil也是一个值。它是t的子类,不信执行一下(typep nil t)试试(笑 )。使用boundp函数检测符号值是否为空,使用makunbound清空符号值。
当符号位于lambda表达式的第二至n项被求值时,会使用它的符号值。这就是我们把符号作为全局变量的用法。
符号属性列表(plist,lisp 中有两种作为数据结构的列表组织方式,property list和 assoc list,详见 https://acl.readthedocs.io/en/latest/zhCN/ch3-cn.html ),getter为symbol-plist,setter为(setf (symbol-plist ‘foo) xxx),默认值为nil,不可为空。可以使用标准plist操作函数的变体函数get、remprop 来操作单个属性,用法与getf、remf一致。属性列表使单个符号可以存储无限多的属性内容。
符号所在的包,getter为symbol-package。使用unintern函数把符号的包设置为nil,即没有包。符号没有包时,如果其引用归零就会进入垃圾回收流程。所以unintern函数是用来销毁一个符号的。
符号函数,getter为symbol- function,检测空值函数为fboundp,设为空值函数为fmakunbound。当位于lambda表达式的第一位被求值时会使用该符号的函数值。common lisp的函数分为多种类型:一般的函数function,只在编译时负责展开的宏macro,以及底层的会改变程序执行顺序的特殊操作符(special-operator) - 例如if就是特殊操作符,when就是一个展开为(if condition body… nil)的宏。使用special-operator-p判断symbol- function的结果是否为特殊操作符,macro-function函数判断其是否为宏。
函数本身是没有名字的。名字是什么?函数所附着的符号名或变量名。我们可以把符号里的函数提取出来,这时候为了方便起见我们会使用function函数。因为function函数太常用了,所以lisp标准给它分配了一个reader macro:#’。用#’foo来单独提取出函数本身并封装在闭包内,这样后续这个符号再被如何修改就都不会影响表达式的正常执行了。这样做的好处是我们可以把这个符号“用过即丢”:如果所有使用该函数的地方都用的是抽取的函数体而非以符号代函数的方式的话,程序在编译后就可以销毁该符号,甚至是销毁整个lisp符号系统与包系统,之后重新规划内存结构,让该lisp镜像成为一个像C++编译二进制一样纯静态的二进制,从而提高运行效率、缩小二进制大小,有利于打包分发;而坏处是如果我们提取出函数本体来用的话,我们就无法在后期执行时,通过动态修改符号函数的方式来动态改变所执行的函数了:每次调用该函数时,程序只会使用第一次编译时封存在闭包内的函数体,而不会通过符号来动态查询函数是什么。这会降低程序的灵活性,不利于增量开发(incremental development),不利于调试函数。
#lisp_daily
符号(symbol)是common lisp的基础之一,它控制着lisp的全局结构,包括全局变量、函数等。
符号被存储在 包(package)里。了解c++的大家会知道,一个程序中可以有多个命名空间(namespace),而一个命名空间中可以有无数个类、函数、变量等。lisp中的包就对应了命名空间,而所有的类、函数、变量就存在包中的符号里面。我们可以用list-all-packages函数列出所有包,再用do-symbols循环或(loop for sym being each symbol of <package>)这种loop语法来列出包中的所有符号,从而遍历当前lisp镜像中所有的类、函数、符号等一切信息。
符号是容器,可以把它当作一种高级的变量。区别是一个变量只能存一个值,而一个符号可以存很多很多东西。符号有数个基本属性:符号名(symbol-name)、符号所在的包(symbol-package)、符号值(symbol-value)、符号函数(symbol-function)、符号属性列表(symbol-plist)。如果你使用的是上世纪九十年代及以后的lisp(好吧大概没人不是),那还可以使用find-class查询到该符号对应的类。
符号名,getter为symbol-name。每个符号都一定要有一个名字,lisp所有符号默认为大写,符号名小写的会被打印成|foo|的形式。symbol-name没有setf method,但在很多实现里符号名并不是不可以修改的,我们可以通过像nstring-downcase这种副作用函数来直接修改符号名所在的内存地址的内容,从而达到修改符号名的目的,但这就是纯属好玩了,没有实际意义
符号值,getter为symbol-value,setter可以是(setf (symbol-value ‘foo) xxx)也可以是(set ‘foo xxx),或者(setq/setf foo xxx)。在把符号用作全局变量时,这是我们最常见的用法。详见我先前所写关于set,setq和setf的文章。
符号是可以没有值的,有类于许多语言中的变量值为NULL。如果符号没有值,它在读取时并不会返回nil(不像lua,大家总是以为common lisp的nil就相当于别的语言的NULL,但其实并不是 - nil也是一个值。它是t的子类,不信执行一下(typep nil t)试试(笑 )。使用boundp函数检测符号值是否为空,使用makunbound清空符号值。
当符号位于lambda表达式的第二至n项被求值时,会使用它的符号值。这就是我们把符号作为全局变量的用法。
符号属性列表(plist,lisp 中有两种作为数据结构的列表组织方式,property list和 assoc list,详见 https://acl.readthedocs.io/en/latest/zhCN/ch3-cn.html ),getter为symbol-plist,setter为(setf (symbol-plist ‘foo) xxx),默认值为nil,不可为空。可以使用标准plist操作函数的变体函数get、remprop 来操作单个属性,用法与getf、remf一致。属性列表使单个符号可以存储无限多的属性内容。
符号所在的包,getter为symbol-package。使用unintern函数把符号的包设置为nil,即没有包。符号没有包时,如果其引用归零就会进入垃圾回收流程。所以unintern函数是用来销毁一个符号的。
符号函数,getter为symbol- function,检测空值函数为fboundp,设为空值函数为fmakunbound。当位于lambda表达式的第一位被求值时会使用该符号的函数值。common lisp的函数分为多种类型:一般的函数function,只在编译时负责展开的宏macro,以及底层的会改变程序执行顺序的特殊操作符(special-operator) - 例如if就是特殊操作符,when就是一个展开为(if condition body… nil)的宏。使用special-operator-p判断symbol- function的结果是否为特殊操作符,macro-function函数判断其是否为宏。
函数本身是没有名字的。名字是什么?函数所附着的符号名或变量名。我们可以把符号里的函数提取出来,这时候为了方便起见我们会使用function函数。因为function函数太常用了,所以lisp标准给它分配了一个reader macro:#’。用#’foo来单独提取出函数本身并封装在闭包内,这样后续这个符号再被如何修改就都不会影响表达式的正常执行了。这样做的好处是我们可以把这个符号“用过即丢”:如果所有使用该函数的地方都用的是抽取的函数体而非以符号代函数的方式的话,程序在编译后就可以销毁该符号,甚至是销毁整个lisp符号系统与包系统,之后重新规划内存结构,让该lisp镜像成为一个像C++编译二进制一样纯静态的二进制,从而提高运行效率、缩小二进制大小,有利于打包分发;而坏处是如果我们提取出函数本体来用的话,我们就无法在后期执行时,通过动态修改符号函数的方式来动态改变所执行的函数了:每次调用该函数时,程序只会使用第一次编译时封存在闭包内的函数体,而不会通过符号来动态查询函数是什么。这会降低程序的灵活性,不利于增量开发(incremental development),不利于调试函数。
#lisp_daily
❤1
做了梦。
四月梦见了荷兰、和姐姐在一起、用水粉颜料画奖学金申请书、Minecraft和惊恐发作
五月梦见了极端糟糕的精神状态、没有颜色、喵噗、天灾、欺骗、逃难与象征学
总结是吃了药但我们的睡眠比较糟糕
四月梦见了荷兰、和姐姐在一起、用水粉颜料画奖学金申请书、Minecraft和惊恐发作
五月梦见了极端糟糕的精神状态、没有颜色、喵噗、天灾、欺骗、逃难与象征学
总结是吃了药但我们的睡眠比较糟糕
🙏2😱1