代码观察:Clojure的破坏性

2021-06-14 21:36:22

如果我们已经了解如何使用与它们一起使用的数据结构和核心函数,有助于我们在简洁,直观的方式中帮助我们为Clojure Collection中的物品中的物品建立绑定。

在这里,简明扼要意味着破坏性的表达式比仅使用Clojure' S核心功能来检索数据结构的部分的核心函数。

在这里,直观意味着clojure' s的实施和我们自己的直觉是对齐的。如果他们现在不是,他们应该是通过实验' s结束。

系统:Linux 5.11.0-18-generic#19-ubuntu smp fri 5月7日14:22:03 UTC 2021 x86_64 x86_64 x86_64 gnu / linux

下面的表格已在Clojure' s repl中进行了评估。打印到STDOUT的线是前缀的; (出去)。

默认情况下,评估结果崩溃。你和他们鼓励在你自己的求职中运行这些例子,或者在精神上决定偷看之前的结果。

我们将探讨Clojure的语法和#39; S破坏性的语法允许我们替换核心Clojure函数的调用。首先,我们将考虑特定于顺序和关联数据结构的特定破坏性支持的深度。当我们的进步时,我们' ll说明这些类别之间的常见,以及其他clojure特征具有相关的语法。最后,我们' ll回顾了支持破坏性的绑定背景的宽度。

顺序数据结构维持其项目的排序。因此,破坏它们允许我们以指定的顺序检索项目。

(DEF COLL(MAP(COMP CHAR(Partial + 65))(范围26))); => #'用户/ collcoll ;; => (\ a \ b \ c \ d \ e \ f \ g \ h \ i \ j \ k \ l \ m \ n \ o \ p \ q \ r \ s \ t \ u \ v \ w \ x \ y \ z);;通过使用SEQ,而不是具体列表或向量,;;我们证明了哪些功能或不需要;特定的具体收集。(COLL);; => clojure.lang.lazyseq.

以下函数和宏在Clojure' s核心API中允许您将顺序数据结构分开:

调用时,Clojure集返回所提供的值' s中包含的值,否则否则

顺序破坏使我们提供了一种方法来将值拉出序列,而无需直接使用这些方法,但破坏性不支持所有这些功能的所有行为。

;;基本操作:(第一Coll); \ a(第二coll); \ b(最后加尔); \ z(兰德 - 第n Coll); \ k ;;在没有康多拉德和朋友的情况下,;;少数内置的上述组合物:( ffirst [Coll Coll]); \ a(ffirst [(反向Coll)Coll]); \ z(fnext coll); \ b ;;基于;;具体持久性数据结构:(尝试(偷看Coll)(捕获异常e(.getmessage e))); => " claslajure.lang.lazyseq不能投下课程clojure.lang.ipersistentstack ..."(偷看(申请Coll)); \ a(peek(进入()coll)); \ z(PEEK(VEC COLL)); \ z(第n(Vec Coll)2); \ c(第n(申请Coll)2); \ c(nth(进入()coll)2); \X;;设置为其成员的函数:(#{\ A} \ a); \ a(#{\ a} \ b); nil(一些#{\ a} coll); \一种;;用于条件绑定的实用程序宏(First Coll)`:(当第一[字母Coll]字母);; => \ a(第一个[字母[false]字母);; => false(第一个[字母()]字母);; =>零

如果我们认为文字向量被污秽使用并让订购绑定使用,请不要让我们惊喜,用于破坏顺序数据结构的语法也采用了文字向量。

每个结合的左侧 - 通常只是Clojure符号 - 当使用文字向量时被视为连续破坏性。 [?],[_?]和[__?]表单绑定? (第n Colle 0),(第n Colle 1)和(第n Colle 2)的值分别值。

(让[__0 __1 __2 __3 __4 __5 __6 __7 __8 __10 _11 _12 _13 _11 _15 _16 _111 _19 _20 _21 _22 _23 _24?] coll]吗?)?)

重要观察:破坏性绑定零,而不是在&#39时扔掉异常。去找我们'重申。

在进展到更高级示例之前,让' s宏eppand我们的允许了解使用了什么Clojure函数:

(Let * [vec__17860 coll?1(clojure.core / nth vec_t_17860 0 nil)?2(clojure.core / nth vec_t_17860 1 nil)] [?1?2])

所以Clojure' S核心Nth函数为连续破坏性提供动力。当NOTH提供COLL和索引时,当该索引超出界限时,如果传递了第三个未找到的值,则NTH将抛出异常,则NTH将返回它:

(尝试(nth" hal" 3)(捕获异常e(.getmessage e))); => "字符串索引超出范围:3"(nth" hal" 3 ::未找到);; => :用户/未找到

我们可以进一步推断出来,由于破坏性利用第n,NTH接受的任何数据结构都可以以同样的方式破坏。在下面的示例中,请记住,以用来建立新的收藏,因此列出并与其制作的标志有相反的排序。

(让[[?](减少str coll)]?); \ a(让[[?](进入[] coll)]?); \ a(让[[?](进入()coll)]?); \ z(让[[?](进入阵列Coll)]?); \ a(让[[_k v](first {:a \ a})] v); \ a(让[[?](进入(clojure.lang.persistentqueue coll)]?); \ a(让[[?](java.util.arraylist。coll)]?); \ a(让[[?] nil]?);零

请记住,Clojure' S组不是顺序数据结构,不能与第n个或连续破坏性一起使用。

展开下面的部分,以便查看第n的突出方面,揭示了它支持的价值的种类。

//从SRC / JVM / Clojure / Lang / RT.JavaStatic Public Object第n(Object Coll,Int N){if(Coll Instansialof索引)返回((索引)coll).Nth(n);返回nthfrom(util.ret1(coll,coll = null),n);静态对象nthfrom(对象coll,int n){if(coll == null)返回null;否则if(coll suiscessof char sequence)返回character.valueof(((charsequence)coll).charat(n));否则if(coll.getClass()。Isarray())返回反射器.prepret(coll.getClass()。getComponentType(),Array.get(Coll,N));否则if(coll instanceofOccess)返回((列表)coll).get(n);否则if(coll instanceof匹配器)返回((匹配器)coll).group(n);否则if(coll instanceof map.entry){map.entry e =(map.entry)coll; if(n == 0)返回e.getkey();否则if(n == 1)返回e.getvalue();抛出new indexoutofboundsexception(); }如果(Coll instansialof Sequential){ISEQ SEQ = RT.Seq(Coll); coll = null; for(int i = 0; i< = n& seq!= null; ++ i,seq = seq.next()){if(i == n)return seq.first(); }抛出新的indexoutofboundsexception(); }否则抛出新的UnsupportedOperationException(" nth不支持此类型:" + coll.getClass()。获取ismplename());}

现在我们了解如何为单个项目创建绑定,让'■提取子序列的进度。 Clojure&#39中的以下函数和宏允许您从顺序数据结构中提取一个或多个子序列:

我们' ll看到破坏性也可以取代其中一些核心功能。如果您需要在这些形式上进行复习,请展开以下部分。

;;基本操作:(拿3 Coll); (\ a \ b \ c)(下降23 coll); (\ x \ y \ z)(计数(拿1000000 coll)); 26(花3(下一个Coll)); (\ b \ c \ d)(取3(休息)); (\ b \ c \ d)(持续3 coll); (\ x \ y \ z)(take-last 3(butlast coll)); (\ w \ x \ y)(下拉 - 最后23 coll); (\ a \ b \ c)(第二个(分裂23 coll)); (\ x \ y \ z)(潜艇(减少str coll)13 15); "没有"(Subvec(Vec Coll)23); [\ x \ y \ z] ;;在没有康多拉德和朋友的情况下,;;一些上述内置组合物:(服用3(Nfirst [Coll Coll])); (\ b \ c \ d)(取3(nnext coll)); (\ c \ d \ e)(nthnext coll 23); (\ x \ y \ z)(硝酸盐财政23); (\ x \ y \ z);基于;;具体持久性数据结构:(尝试(弹出coll)(捕获异常e(.getmessage e))); => " clasljure.lang.lazyseq不能投下到课堂clojure.lang.ipersistentstack ..."(take-last 3(pop(vec coll))); (\ w \ x \ y)(需要3(pop(进入()coll))); (\ y \ x \ w)(拿3(pop(申请coll))); (\ b \ c \ d)(拿3(pop(进入(clojure.lang.persistentqueue coll)))); (\ b \ c \ d);基于谓词函数:(下降#(<(int%)88)coll); (\ x \ y \ z)(第二个(分裂#(<(int%)88)coll)); (\ x \ y \ z)(上行时间#(<(int%)68)coll); (\ a \ b \ c);;有趣的额外额外:( Take-N 4 Coll); (\ a \ i \ q \ y)(随机样品0.1 coll);七个执行: (\ k \ m \ x); (); (\ q \ t \ z); (\ e \ f \ g \ o); (\ b \ f \ g \ h \ w \ x); (\ h \ q \ w \ y); (\ k \ w \ y)

如果我们插入A&amp,则记住defn支持可变数量的参数;接着是符号,它绑定到传递给我们函数的额外参数的列表中,它不应该让我们感到惊讶,同样的语法用于在破坏性时绑定变量数量的最终项目:

; (\ b \ c \ d \ e \ f \ g \ h \ i \ j \ k \ l \ m \ n \ o \ p \ q \ r \ s \ t \ u \ v \ w \ x \ y \ Z)

; (\ c \ d \ e \ f \ g \ h \ i \ j \ k \ l \ m \ n \ o \ p \ q \ r \ s \ t \ u \ v \ w \ x \ y \ z)

与DEFN中的VARARGS支持一样,我们不能在&amp之后尝试添加任何额外的位置绑定;及其符号。以下内容不编译,因为宏展开失败了:

(尝试(宏expand'(让[[_&休息?另一个] coll]?另一个))(捕获throwable t(.getmessage(.getvause t))); => "呼叫clojure.core /让我们没有符合规范。"

让'看看一个成功的宏观展开,看看如何&用法转换为Clojure函数:

(Let * [vec__17698 coll seq__17699(clojure.core / seq vec__17698)first__17700(clojure.core / first seq__17699)seq__17699(clojure.core / next seq__17699)_1 first__17700 first__17700(clojure.core / first seq__17699)seq__17699(clojure.core /下一个SEQ__17699)_2 first__17700?休息SEQ__17699]?休息)

基于我们的破坏表达式的单项检索的数量,对相应的呼叫数量和下一个用于建立位置和最终的?休息绑定。

在我们的例子中,到目前为止,我们使用了一个已经绑定的Coll,但是为了额外的亲密,我们可以在一个破坏性表达式中绑定它的新收藏和破坏部分。对于此功能,破坏性份额与expers的股票语法,它期望关键字:后跟别名。这是一个雇用我们所有三个方面的示例,我们观察到的所有三个方面:

(让[[1吗?2&休息:作为字母](MAP(Comp Char(Partial + 65))(范围26))] [?1?2(计数?休息)(count字母表)])

作为我们理解这项工作的最终测试,让'查看最后一个示例的宏展开:

(宏eplexpand'(让[?1?2&休息:作为字母](MAP(Pargial + 65))(范围26))] [?2?1(计数?休息)(数字母表)]))))

(设* [vec__17703(map(pargial + 65))(范围26))seq__17704(clojure.core / seq vec_17703)first__17705(clojure.core / first seq__17704)seq__17704(clojure.core / next seq__17704)?1 first__17705 first__17705(clojure.core / first seq__17704)seq__17704(clojure.core / next seq__17704)?2 first__17705?rest seq__17705字母vec__17703] [吗?2?1(计数字母)(count字母)])

如果我们可以破坏平面序列,我们可以将与嵌套的技术相同吗?请注意,在此示例中,我们将Coll在向量中进行COLL:

(让[[__?] [coll]] ;; ^ ^ ^ ;; | | | | | | | | | | __Destucture`Coll`,采取第3项,这是“\ C`; | | ;; | | | | | | | | | | | | | | | | | | | | | | __Desturecure` [coll]`,拍摄的第一个项目是`coll`; | ;; | __所有`let`绑定的外部[...]?)

(允许[[_& rest-1:作为alpha1] [_ _ _& rest-2:作为alpha2]​​] [coll coll]] [?rest-1?rest-2(count alpha1) (count alpha2)])

[(\ b \ c \ d \ e \ f \ g \ h \ i \ j \ k \ l \ m \ n \ o \ p \ q \ r \ s \ t \ u \ v \ w \ x \ y \ z)(\ e \ f \ g \ h \ i \ j \ k \ l \ m \ n \ o \ p \ q \ r \ s \ t \ u \ v \ w \ x \ y \ z)26 26]

(Let * [vec__16915 coll _(clojure.core / nth vec_t_16915 0 nil)_(clojure.core / nth vec_t_16915 1 nil)?(clojure.core / nth vec_t_16915 2 nil)]?)

注意_的多个绑定。除了选择返回延迟序列的功能外,急切地评估Clojure作为一种语言。绑定到下划线符号没有任何特殊行为,可避免评估下一个表格;它只是一个广泛的公约,通知人类读者忽视绑定。在上面的宏展开代码中的每个绑定将被评估,因此如果您正在破坏可能触发副作用的懒惰序列,请记住这一点。

; (出)1; (出)2; (出)3; (出)4; (出)5; (出)6; (出)7; (出)8; (出)9; (出)10; (出)11; (出)12; (出)13; (出)14; (出)15; (出)16; (出)17; (出)18; (出)19; (出)20; (出)21; (出)22; (出)23; (出)24; (出)25; (出)26; (出)27; (出)28; (出)29; (出)30; (出)31; (出)323

如果我们从(范围1 100)提供的懒人SEQ中检索第三项,为什么我们的懒惰序列的前32项是评估的?因为Clojure' s懒惰的集合是为了性能原因而被束缚,块大小为32:

我们可以通过将Lazy-SEQ与CIM组合如下所示,实现完全惰性的序列(单独实现的每个项目):

(Defn完全延迟范围 - 侧面效果[n](Lazy-SEQ(CAN(DO(PRINTLN N)N)(完全延迟范围副作用(INC N)))))(让[[_ _?](完全懒惰范围 - 副作用1)]?)

作为一般原则,它是将副作用与懒人收藏界相结合。不仅破坏性不仅屏蔽了你的担忧,而且因为它被呈现为最终用户语法,因此掩盖了确切的评估,它很重要,因为重视破坏性表达中的每一个绑定都很重要。

Clojure' S关联数据结构提供了有效的方法来插入和检索键值对。因此,破坏它们也专注于指定所需值的键。

;;注意不同的关键类型游戏:心]:策略/避免})

虽然Clojure地图支持任意关键类型,但是破坏性为关键字,字符串或符号的键提供特殊支持。

如果您有一个类似于我们的混沌卡示例的不同关键类型的混合的地图,则可以指定:键,:syms和:strs在单一的破坏性表达式中。

除了简单:ID条目外,我们的卡还具有命名空间合格的关键字键。我们有两种技术,我们可以应用于这样的关键词:

两种方法都有不同的人体工程学考虑因素。有些人喜欢并排保留关键字的命名空间和关键字的名称,如在更容易的文本搜索的第一个示例中;其他值在第二个例子中显示干燥机方法。

在发现您可以使用以下符号或关键字之前,我一直在康复10年超过10年,其中:键解析。注意:ID在此示例中:

;;个人注释:这是有效的,但请不要这样做。(让[{:键[:ID]}卡] ID); => 42.

据我所知,符号普遍代表所有其他Clojure语境中的绑定,而是破坏性的一个角落。使用关键字对于绑定为那些读取代码添加了不必要的精神开销,他自然期望在绑定上下文中找到符号。谢天谢地,同样的环孔不适用于:strs。

在关键名称存在的情况下(例如,该名称在其被破坏的上下文中困惑)或当密钥不是关键字,字符串或符号时,我们可以访问另一种形式的关联破坏性:

这种配方首先可以证明令人困惑,但它与我们首先探索的顺序破坏性言论密切相关。为了顺序破坏性,我们能够依靠隐式"钥匙"序列中的位置0,1,2等。对于地图,我们必须明确关于我们想要绑定到符号的键的关键,在这种情况下,在这种情况下;代码"

(让[{:卡/钥匙[套装排名]:担任卡} {:卡/西装:Spade:Card / Rank:女王:ID 42'特殊?假"代码""&#34 ; QS" [:游戏:心]:Tactic /避免}] [诉讼等级(数卡)])

与顺序破坏性一样,如果我们尝试在关联数据结构中不存在的密钥处提取值,我们的绑定将是nil,而不是抛出异常:

关联破坏性通过允许我们通过提供:或进入我们的破坏性表达式来允许我们定义除NIL以外的默认值来进一步支持这一步:

介绍:或可以为破坏性表达式内绑定的所有符号提供默认值,而不仅仅是指定的符号:键:键:

(让[【:钥匙[卡/套装/排名卡/野外吗?]另一个:缺席场:或{西装:心脏狂野?假另一个"默认和#34;}}卡]其他])

请注意:或者是关于提供绑定的默认值。如果你不在破坏性表达中绑定它,或者不知道如何处理它:

(尝试(eval'(let [{:keys [id]:或{siate:heart}}卡)[id suit]))(捕获异常e(.getmessage(.getvause(.getvause e)))); => "无法解决符号:在此上下文中适用于此环境"

我们可以使用序贯数据结构使用损坏来潜入联想数据结构。我们' ll在本节中使用以下卡+定义:

(def卡+ {:suit {:id:spade:color:black}:排名{:ID:女王:value {:hearts ## - inf:blackjack 10}}:游戏[:hearts:blackjack:扑克]})

key:keys,:strs,和:syms期望文字关键字,字符串和符号,我们'如果我们想创建嵌套值的绑定,我们必须使用其他语法进行关联破坏性:

在这里,我们使用了外部绑定的{符号:a-key}:内部绑定的键。如果我们深入了解:

({{{{{{lexy [blackjack]}:value}:rank}卡+]; ^ ^ ^ ;; | | | | __grab的“:Blackjack`键,相当于`(夹住卡+ [ :秩:value:blackjack])`;; | | ;; | | |; __grab的“:value`键,相当于`(夹住卡+ [:排名:值])`;; | ;; | ;; | ;; | ;; | ;; |; __GRAB在“卡”+“Blackjack中的”:等级“密钥的价值)

支持任何级别的破坏性组合,包括联合和顺序破坏性的组合,无论是否找到适当的数据结构:

(允许[{id-1:id:keys [foo]:或{foo" bar"} {suit:id}:suit} {id-2:ID [_ _ _ _第三游戏]:游戏}:AS卡] [(ASSACAD CARD +:ID 1)(ASSIC卡+:ID 2)]] [ID-1 ID-2 Foo套装第三游戏(计数卡)])

让'考虑这个非琐碎榜样的宏观展开,看看是什么样的破坏性:

(宏alpand'(let [[{id-1:id:keys [foo]:或{foo" bar"} {suit:id}:suit} {id-2:id [_ _第三游戏]:游戏}:作为卡片] [(ASSAC卡+:ID 1)(ASSAC卡+:ID 2)]] [ID-1 ID-2 Foo套装第三种游戏(计数卡)]))

(设* [vec__18794 [(assocack +:id 1)(关卡+:id 2)] map__18797(clojure.core / nth vec_t18794 0 nil)map__18797(如果(clojure.core / seq?map__18797)(clojure.lang.persistenthashmap / create(clojure.core / seq map__18797))map__18797)id-1(clojure.core / get map__18797:id)map__18798(clojure.core / get map__18797:suit)map__18798(如果(clojure.core / seq?map__18798)(if(clojure.core / seq)( clojure.lang.persistenthashmap / create(clojure.core / seq map__18798))Map__18798)Suit(clojure.core / get map__18798:ID)foo(clojure.core / get map__18797:foo" bar")map__18799( clojure.core / nth vec_t18794 1 nil)map__18799(如果(clojure.core / seq?map__18799)(clojure.lang.persistenthashmap / create(clojure.core / seq map__18799))id-2(clojure.core / get map__18799 :ID)vec__18800(clojure.core / get map__18799:游戏)_(clojure.core / nth vec_t_18800 0 nil)_(clojure.core / nth vec_t_18800 1 nil)第三游戏(clojure.core / nth vec__18800 2 nil)卡vec__18794] [ID-1 ID-2 Foo Suit第三游戏(计数卡)])

甚至抛开了语假名称,我们的数据形状与我们的垃圾箱之间存在更清晰的句法关系

......