From cb8dc55344a37d0ed3cdb7351a2a9ffd521f0115 Mon Sep 17 00:00:00 2001 From: stanch Date: Tue, 9 Jul 2024 23:29:14 +0000 Subject: [PATCH] deploy: 37ddf66c969774454abbc4753bf44bc533cf467b --- 404.html | 2 +- ...ftree-89a18ab93544f7f9592b99af3dca2ca0.png | Bin 0 -> 88656 bytes ...ftree-eb6c2fd2b4464cf68d9c3fe04e8ad231.png | Bin 90254 -> 0 bytes ...5de9a.0c9c5e17.js => 22c5de9a.1b68f151.js} | 2 +- ...9ed0f.5a9d3f91.js => b3d9ed0f.69be21ab.js} | 2 +- ...n.301f31c4.js => runtime~main.56fa2c0d.js} | 2 +- docs/GettingStarted/index.html | 2 +- docs/Guide/index.html | 2 +- docs/index.html | 2 +- docs/talks/Immutability/index.html | 10 ++++---- docs/talks/Visualize/index.html | 22 +++++++++--------- docs/talks/index.html | 2 +- index.html | 2 +- 13 files changed, 25 insertions(+), 25 deletions(-) create mode 100644 assets/images/reftree-89a18ab93544f7f9592b99af3dca2ca0.png delete mode 100644 assets/images/reftree-eb6c2fd2b4464cf68d9c3fe04e8ad231.png rename assets/js/{22c5de9a.0c9c5e17.js => 22c5de9a.1b68f151.js} (87%) rename assets/js/{b3d9ed0f.5a9d3f91.js => b3d9ed0f.69be21ab.js} (99%) rename assets/js/{runtime~main.301f31c4.js => runtime~main.56fa2c0d.js} (97%) diff --git a/404.html b/404.html index f5a4a1a..94080db 100644 --- a/404.html +++ b/404.html @@ -4,7 +4,7 @@ Page Not Found | RefTree - + diff --git a/assets/images/reftree-89a18ab93544f7f9592b99af3dca2ca0.png b/assets/images/reftree-89a18ab93544f7f9592b99af3dca2ca0.png new file mode 100644 index 0000000000000000000000000000000000000000..933a02202d133ab281a3000c279e26488abc62e5 GIT binary patch literal 88656 zcmeFZbySpL*EfnHqJUr!(u#yMN=mCJ4k-=NQc^>Ai-@4q5F@R0cMUNEA|)vuGYs7f zF?4?SfX{i>dDr^>KWDA?Vga+5tM=Z%-S-gkR!R025e*R@9^Ng`8yQtRJc3O;JbalO z*MYyBmy+@TzplTRmzBXg$9?^1$d1Otdw>U$d9LoBw1)C<);DW7-|BTs9q?!e{k%ze zO-7nmuSOC7u~vfS+sKF6LDteRCJsf4JH;`~6uu-5??1eNWHxrk|9Er?P#%1i*al9wh0g{%*M=1sj zy*JT$ki%VTDECpfw8fPRHjNI=#FOI5$a+MI?XL5}4i6!c%(<`o18*6h^w+le>J1dZ z2o51=2UK8b&id0zD&Wsf3i( zF0;49ab$&pX+_pXRDNZAySrX_x+U)4K%T(yH%|sO}Oq{GQh;)Gu8sTTk>WMVBsW z2#}J&g;XarH=O;27eJKPD8rasqFW@5i?T{hWToWe^qb96V=Ej{{XxAIsA`|XB$mSS zo@d6IMF|4K$h?sP?V1eRtC5^(E@sE!)LC~_3N%rpo91Z@8SDy!*|!sb2(9g zgnRKvEnhW@4?1eE^b%Zfu;lC8MndM6#92o52gDg#1J#JbHeI1NN3Iil0TblVgx9%; z@yJ|5$tG@>5K~$Cant^K!A>fSfQkD?ZaJFjyWzuS69{SKj<9ed0zCGyg?>I>#AP_U zd#$yylWGCJaeyU#BRHV}QGzgQy@i1fKUzCxQ&82da)8q3X;?ahz>`(n**`=Y^811L znpNvkZdO|srbkJgSVf^@K)2CQuL~ zctAcKWxW++jLI-WhBwjr;|pbMik=_Vw+V=uJ!D5RvppS8-%T+1y8qqW^cNCNH zNZ*q`5~Ei>Ry3qWoZ!l^zs4wcrUNoz4*M{sS^p>>G*YF{XaK1P9kQ~s6n4pA;1$q} zB+C}V*i{m(mC*$BWIx=!n117hkJTrG6cLxbeXdQl?n=K8p5o@E!~^G!uU1fXeG_oE zB$Lxt!@>#9Qo+oFTnKnI=hg++8{ghG6~()KlL7wNGQAUCwQ-YbZOGP13!af(^|@K@ z1+kwIgShEJCabOU@)7c*4&w*gME=TyjK*J!%=EKhV$-9KU1ye<{W%5BGpk@WKE=45 zKa*+}${;~P<=T%9;5}|A81f4tr^;o9DC<)y?+b^jKN{fWu87DwC$S^4Q``{R|LxSO zkff=%o;86wz2&C=ct9`S8G9yH5L4g~dUbl)aWYCiWd7`%#=}W-HzoRh;EahTz(;X1>k}3%E z0a0XI{Uk{E-G}VtT$G2-{?y@?!mk!3?S<2FF80uE34Mm>-rMm1GeYHUt%ej+Ix6~ zE|@F5^8}wUvf^w@chxDYM^bCSO-N8slHXx`?OAenIFvbln}vR@pC7P|N|OeB>X-_h zUB@~#2C+B&`4dnjn3_3)R?2g3@$9gjjHYVe-mj-(CTigbGaXFDZ6yKMc+lx&X=3DC z+<~ZaJ^WQS@!I{kl)&zdr%7tAVbW5lGT))yuy|ETXXb0~)h|T+ickUqYbg4ptwR5+ zV>V@KWfj~k7jhadVfP~4`G?Li^(=b}mbnAvO1|lp*wQ=-f&^Pbc~9iV7g(ah6Hl98 z+c69c-SfF52`Yh%kVhG zSKrFngWU4Ot`oxJg3g56ckjlt-F^o9xIwJLzQ$Is<0Ji_uKmbVuY+!0cx6(YG6WT7fCcR z@bH0OoATh=jrC08VYj&IU46d&?MhC{GY%ia+w$D8%w)UIbN4r1*d1XaIn9e&mG<#3 zxI=QKtC6t3qTq^Q3RG=#m<}}ZY2x*n!K+toR$DXb)`<&GBz063wo|JlB>9d@qMsMa zAf$MnysH*PQRji$PBdKM(RK6mK1E8hM!z6V`B1KjAg|)GW(?mU4gR|=Pog_CHompI zCo%o8At8ZgpA03c|JnSl)4oU2o*irVDu>f*-IUe&lf}2sk<2i{4Y>wCD$fBo*{%vi zoy4ey?*%XXb4;#F_9=PP09qmk)i>||`z-%n>KpVbSdBSI$Z?|FHo{paMd952IF~?| zRq1?18^ox-PEL;|74xMlAsA2^kjl7UKDJgn;6tms|vfEf7V*3_=^ zm#{+&mNINp=IANYy%Jsl5P@(0`hLO(^?f)7%d*$N4CYzNMBCex6_qox^mBU@gd^mrsp96uZU4cJ=QMjbh$i z`$^9VG9EqIhu0HJtk+>}M5T!gmpP;^RbmilZxkTasQdhW0?r*>_rwHA#nF1aMTfiV zi4$(~YUEZy%!T=O;CAZtn5$vd=pu1@uT?DMtiR|DIDt#+!=ox^zT<}G9Y#&%&<=B2 zmfmF*D3}p4&P!I_TQQUVa^x(TvKWwP-MaN3KNExrZ*7LdXjUT9<}72m+;8%488@NQ z`LFhF8hXR(9ofWMQgr#Mg=%3UeeKa!kiMUGRoG2Nn0d9SESuGso(Sf zYu^tX!A`P*2s8yT*MgnPnA;Q(%uW8SyIo6_xU+o(+C3lS-1!iKW*8JH2e{nAMX)T`R=|qFl{*gAU|^sUf^y^bF2K_g6O@ zwyjpb>x9TGJkLY9;R2rd=BiK4XJ*C&VqT0|-XFA1_jithin8ZL-_PvKblC1IaveB6 zw`gm5Eua)`n4zw z=$|Qa8zij^)b$O(#olwcj`R<4f-&~3m#}Dj+eG5M_IHKh*g+BSF>bI4WhWx@UT173 zrDvEao7|CIyhee?+Aw{mJ)OuY@uTWbq6*ut)-e$z3EO&EeecEMd#ZPv?tHrmb0QAqjXvze zh$>=^UgcT=ZP@YCp50Q=s^hO})rH(smdewq(u^Iq=MyxuiM|a$ zh^O`Gr}A4P{`s?Ku!UmX(j{o&x)+vbr)t9)*5%f#KiR30Ng{k{Y?{$85u3 zA|}j5Hk-Jv1~Hs1Q^UvBbqlo3ubOOaZUV5ddjq z+kuDME^a5gZzsg{xN72z=eKGAJ@>$H->C{ex4^!Qu$a?&Xb@F)^+q=?a@D2x-VS;s z{)IbkXK)-@%YxFE|7X?9F^PD1djdcn$HJ;Vep`TkFxpVl6wvU!%^E?Mhhe=YJS-@IJ=14wP~oIhY`InPBG{L?ktK8|iH7E6q+KNXEHIUJfB zb|4&Vdrhcl=(D z;`ujZZWDO_=QY!ptCIhDeMjaBH>kLo+& z571}w@&io^Rg?3T5YsH~d+5Ss7_J)k?fO5h?f=bB4-`jrmzuBvhiz_0v6+W(?sI3i z^5Dmlm-_j@O8W(2BiQaVlz>y;6Xa-z7~!%%KP*!Iw}W%oRY`w|j~yG4Tx&)nLSZ)U z_E5sBm-<=z{0d`Z%0q?|J=?Ry#WHiy{zXK}fA7+^;w{ku8t(O}5}~_UHrJcg`%BmZ zIqzovl0O9fY|Gm`meB}C~MK` zTKZLG)u(Gfbi4$3w==WbOc-Vr5j%LNq=uHh=crLCXXxFI&=zwV!}5%s3S;E?(wC}9jFsa zyRlNlMNC?m_|6dZIxk1PownT7a57x7_Nz;CC&P2^P5ioO6YCs2=f+A@e4G?-c+c}7 zqJSC;SFg@k!ze7`Q0JdB^Qa1s$##Rk$PZ0PTRxbnhCCf`(a6l+$p-dlXU2}NKqi92 z?w>vcAq1)onIRNV`5wbxIMz!Oeh(mfP22PMvWh=9fS zTqvIkpg+g3tT>VRyFyR3;e7Zudt7>q0zvXoYRG5^x=Dh?-ngmIX0Vp%O z^Rbg%@4eVnfzj)$k|gGFXUJKF1tn;M=d9D1PC5d)8qF=qZ-$YA#F%nl1RF-zCH)^i zkx$s!{45KHKN(Pod`26mcR~m{-q}5<`IxS+S{_Wzsi4eN>tLhS9QSh0*TgPj9elEy zzc4KC`2tb~p^#0OlM|E}o50FUi{7egswWGK&y?C1iH7Maes@3}za*0)PuM@AVuQrc z+(C$acyns>okls<+1l_32NHi20srO|nP0xIu(B{`^$&ZA3KmU$3)|_;LnB(T4c~J~ zy2BE&%me}NeK?HvVqlDqmNRREd2*N6OI2`O)7hys1Uyy6wO|WI16~q{Q9ATB;S%l2RrfpA$d@ePE_{sH0VLS)AFa;+?9^DzwM70p@R$5 zihHWG*W^HL@^vu+13Y4c9z2A=Y-%+y){BZv?qst=xgfu#p?wW7<97~Ac?#Qjy z1ASLaxFEueOwKbsf5v4vcZ6}y7|Wd|(P>Gv7a$wJSvf5niJb^mwhlMqw0O8M%# zozGo7&Xz^Y!UUb=#GDZme@Dg9^U6cvjJiz3eh&!9TwJ8Q#LkArA*4{R>=u-yes&*Q z-AOEfBsKDVHbok?eLL7HE6C5*c6eejqq_IN|Nie|KFMe2MqgXbnRTMbk&njK8h1U7 z;J-{QtdGS;N4QjbF%IgEgegZFA4ItY=y|omG7PKCtxmV1wo-V5P4?oh+p|BwarJ&4 z&hFoRF>*gThGsgEKbhs_HZzOfS$XXp-LscpJ84q^QdyEkG82|=EfnYp@L8XnM$ z)|y|IfbPN@8X}JG@pBl&tzJLeV@f(UjaMilw#t{&H1GaiT@8!1KSqsv0v0DD%v*I*{bpiJIF%&dXAM+!XNwi-kA-PU=m0X#jjlJ^G#MrSDZ zf_PK5t~~v+-!$l5XZqZzNmiH(c3?OmjE26^gtH&Ixz-2zbTRF&G03vGDeelRBX?Ry zql**nlh6t^nIXn%GBPF>Yn(KB4@cpQ7jCYO5QxIknz~IPwlortJ5yxFaPuEqLOQ4h zHDi-g;k+b+raS~sbSG-L#dHqQJoBJ9gL**KtOMocS_(67SR`g1DR4Y4 zsGr@EZ&HDbj3|#CsUGDVb?OlSCwXHFQ9dANT|RJ`__y61->lV+1GgO(sn$#jL)~}q z!sb(-8YL6u;&v6eXy(&`th}AYfSPPgdpiVcV{vbyUVN|Mu4D(GFjEBq0&E`$3@KD{gB zZyzkgu3&loh8Gt}vx%u(7*VzOkj|5lm-OaY2Jz-!7B@E4PHu~=il}x9-xDpO*eeVjM&U& zJrd1693@;5#&AFmOR>|px`_Pyq=3Nr0hYCHXC}cA>RNJ!P>ebZ6{2OUEFeR*?rhg& zh`V(|GfL+1t000>+@R1l?7XTImo|H5Zzw6yJX3sL*hz+Zd)ob6hbdc(hu*e7uP78| zF8a0%T{-cjK^$Dv;kc1?fP@-Vsca{u2v&N#ZFHY{Z;Ud0GlaAz`wkh%9R`Si>2DB-{83#Qn;lru-fcW zChpeI=g=giZ|fp_Ra#^s5MH^2n!J>xU$iY`m?Ky+H72J z$BwG}aY^RXLP9|W%M&EG;c{nI%{)kq*X54dL9;mlmHUSX)9##lamsP*PgTaaHIj~q zUXB8hw4&Obp)2**#Q42bFox|M1>b4Er?`!TFbF|MBq|1iRr8sXZOeGXh?%pxU9gp6VORsIktMj=>5{I1yU=ZPPm$i5*mwG!?e>e^=Z=o9eHo_}m5OJ^UL!D*U+0ZWxP zN?BnH9m>U6hDIdb9)&W)yvuB|1q-n(Xcc=W%g_p}$B0&UVFagmYX&;uc>ZS)0-{r@ zz}qsr_40b9J1;xKSN)a#^Y~>VD4Ge2-yIyLY-jRF745owCmzm2>^EY5k~rCo-kI?y z^?^4OxwW?|smgL*-g;YFzvhoJBHHqwXmY_xqI;PGY*@0&D ze1p4qh0C9aB^N_SELDvUKSYzRe`s})UEEl9>W5m?Bd#Ay4r=P3@OQwcw>x-H4}-G* z?9jt7G3toYl{DyaVkX;}Mbpqu4nD#xT#D&qCJrTBXOIglhOC1^X(j=~vI1=AfhnRKS{ z2UTl(vs^ZKHh!EuvWK^*Z}qC zt3?CSPHKN=q`&qmWZHEn=)@QIDf163^2ZifzZ4Y4Em<48D46cNbuHVzYS(xESZ8at z&nA|cfPo>q55cp1mQdThZz}RW__GxlH-B{CqS(6h^We+hYC%Dp4p$GAsaicq-Ok4@ zo}&B-=ft=;VGB_c+5b7;grKUgMXug0$C%MV+K{wvP9D&q4*e_9cVZLe70l^7I3yi{J zm5DT}ZE~XNh~x;d)7qEZ6e(OfAbpJv7uz0^%R7?L00GNq0saPcHH`!VIRpz(=qT*s zp-D~h+icBycD|;3zGZ&?BXO?Z@R<6ti-@;G0jzO-+2q*4OC;`6laWkxFvh>YI4!5o zKJpjR3VJucy)mK@%dKPwebb}MJ@x?_5yc#RE*e!ZT8f@)8&Z#zkUTKCtYSB&aNun4 z{STA}(-87MlS=3miS!wp{Xsbx>z`A(4s+OvNAxLER6&h*wh|J~HS!EoTbrp?r&<(W zD3yC>3$c;6bxDa)?{5-syt(W^!4x~xu&^dOmdQ1~U)@|#@<=!QP>HcI! zZ=AG104|v{z~~2{BU^EX^LkTw`ZQna!;u>GC1~aPqmrrNzUFN{3OerP?knLUHn@5M zK5{W~N-iwc+S$2{ap8@sGxq1BzkWr0xAk%xxCb8M4tQ`C!0Y{Sy&;}+OQWXtxA68k zPOJPsN;c@eZDy_8qhFV;$K+L)jOB?(;P~{t3df16ytkYYFM~5b=7b~WN5pocx`TOS z4q(6Vwz=b#F76!v8Ym}5ngLL5cq}Wf#+r%B$s%3yU~=S3vp|W&;LCwiP{4E%lT&i^ ze^e7!85H`Zjvb=>+6&RioNHUF_!NHftm8dpdFwc#9Mom96VD0#?pS)cU*8pe1F+!3 z@rMTqU&5uVU(Yu|td$$2UTO@ca*})ll>Sf8`7NAj@t07;wMY2gt}xVQv}WeB(8ng% zW7c+jQsht@{Z>IoXi(66`UFFw%gWGL9oORvdhz=3fu}i2bUP+%4hbt~+q_ZpTJ$0f zCzB&_ady$~LXQE6CNF{!$oD2^Q5xC~>G2KBWc!tkO2qod^1k=^B`@)Ko8n>@4_nJa zq4=W63P1hfCA*A`d>z%jk~&go1s9)>%Ju}ssEwN!KLc^^51_(^8~%@tfi(4DuKAYm zDzlF4u@yAtOr~F1bX-eI|0r(|OZI43JyvpX3@Be+OmD(r_22lvb|l{40_OPp{@=~V zepaze*^DPh%*TI1zkDT6-j2Gl{bK)zA8{=pX(<6A8(3S$RS=pCnc_6SEG(7d`zPyG z9!B3VuAiIXgJq}Gno$QQp*SCIl~FrT7Q@5qlbuyUVBX3HvB_(yYH2v4l*FJra703| zTq<;@p4qIspsU*cf$D{~{LC?oYf~+yyO;aDh3FkwXO&-2pk5V7*@?b=Yd92qcEZ%=yH{pK$-iQ^I{c)MzV$G@*2r?Gy*llwP3j1;{(crPS z62{r(+>qhvlpnK91WHG(|6HTSDlIF=jBlAxeqMkkIzwT}>|PhP<@7PCVV212tTu>>yvWw#CKHG(VG@1e)Gy%+H$Xq=yd&R5nGDRSv5OK$4GyKW9 zQm)loB&FeR~BbMXoa!~?HCR%<1JKzu$JHLCFD4dgL_HriX4?gmgH5!wj%OSy8xb6dR z+zJF$6qy}QwW+bgB>Mch5ZitG3l>GzB|qq34x^=9c@*C>V0g(Rjs&Z`KMcyelleeV zKRry&#!&2hQ_@Y(fI;poEko^YP%OXT@q%$--3s8qs@Krq@4b-%JQPP}wskkTYBc&c z0rQ7i8G7C=jk>qin?kL#!$Ey^?*`5t*t@IuMAJ z`)P?e!R`W)=^ucPYAN;|)@3*cR+1iawrts!-&IP3ClP+qrvTtG=(m<69hVH!K@{~N9Ms$TUrJwk;&GK3Nl*+!h6qN1-^qcw6U)U0(5#tNJe?FJx z4I;w#ll1YzAz5I|&(&4WNv$~%LjGNHR3|aym2kKsxn@rcvcGyn3tCXkcaY3%O_M^y z4*GxF$-P*>PM*H|bAd{}O z4+COuriYQXQ5i<+RtkU(X_U_M*_^THK6wEn6&TQGSn&$*Lh8%P`@Ym_yH1(JeQ3ub zHli@nvGy|89s7Iu)J4PW>ySz1R^kMi0(?{<)nQp|_I0ad@}wayQAYuRidn$& zR|vYO(Y$UVaZB6%^{3GNS>_=N(TgsbDGjca{DR}g2!ZjBg#~*?(1t0{yV#LY)L4GG z(v(Uz#Km6s1Ss%LRE6#NtS~(GdS*M455Zv8YHrE%oIx|F^>_7;?K@T>a)8+%`CGNp zuf>u)pueEt$Uc(c65n%SoN#Vgn)%y(p*=grTe>egB4T!MO@&5jUF>RqH^cx__0Y6G z3R3*XnH0yGc7eFQE4Od1QW}UuJMZU5!zj$WP|&x0X)FQ3!+auv6!Su+HJfa#ijaP=7O`g5YNcm;2b zM)zas$eVH9VLb?NAz<9502E3_N&hsif^>Bar5 zaQCY?%7gJ{yjNt5U9|(lxuOcR1@?AAlN1)O;_WB`%C{9#@I*P$Bg7P)lg}Y$DBlSb zWr=ai(}0IL{M+*p=lyQhvJ9Iyg?%*ozq!ltvO*~4y^@9e&TTv>dS*Q>a3C3Zb%r$B3_ow=yIEYjKWwpDndWc)JM5-dkb%RM59=F#C9WWK{0 zk^zF8Ei9mmbX8s5vamRSH@=m|#hn#y+v5)1=Glo8xjbU8xAAdL`Sd9TW@wlnCBM*B zRny-FJ|=THACN^O*ceDrm##CWqSl0%26WPXeOx%Nl=cel-s&|ImG^7;$j4i;34Q)Y zGFEjhQ(=*sh$}7+a$>36QrzWCxfZWFJ$$67OKYu-zIZ6;h$jUXKHi=z;z2W6%4a#r zfZn&($JE*8crB%AJx$I;uqJ6naYmC1Aau0!pU$?W(64=Y#yr3UWw%g<_)V!tvp06lK3qX1qWL zsDB^3wUpl7<<{eo%8zzpJx$v1jxg50J}}eskm+)~%)Sc$(k?@BsNUvm=r0j7YfZlw zE*AXda*zPo8xzyL*zyQ5>zVL}TYTELxmIJv(+1^?!@~u@vafy{#Y0)_VxE8 z_XAlp0z%kIBlB;3Rv_@c6LqtlYn;qTE}}3Tm}KH}_}q_BNM%Q>E&0`droF~Z3u$lG zn+mFQ6R`Ov7WzA5zJh7I+``Be^Ur|9YkJB8F-?|aQK`4xDF4l3zvQnXIX%9ORj-{pGv@K# z^0?YbLTG5xp-$J_kXy(o!)W_^|MtJps>zr!u4c7at^bC}9!zIfOCdrm;y;)RzaZn2iG@hP z+6UVMAEb`yS_qfbRSvH@;pG34OHGH98LNLN0aY)au8}$V8ny=8J~{yRo|dvd519SB zmv1P_f)W3BhcfF6DB^{@7?ZtKaR0y@*N~=fVTr_x|K*RXB95Ld@MM|eH>W2> zy6CEi+ci~w*E7g6S#-z01Vt_ZTaCZ1KXCuws zAkP1Cf%k$_4mOCROf~x+e0Vi{w?25b=aC6|rAmRS=h^iQ!!t6>;Ejd5_g=hJSN1!wkPtZb_DHh3fe> zTLj1k9*6M{dp?5;_?ETeT!4e13m(S{()P)4F4Ld4%cOTik`{;^PhUj2Ais zTr>^z?z=I>-{0<}^;;QTv_F552kvka!RD3K@P}N2<;Dm2gv8+5y|Mw%>`|l*L+`aPw?(a*x69y%UAs`^-lQO3*3W5~({f z6l?Oz9Qy@a(k>I(Ars`aE^9={e0REr>RnH78$F0M+=K0V^E4d3l>AD(K@i3A@U1~M-p+QziRS^xNo2$C)ube_bk2Id$QnnDL zO(9HivWbl^Y-#3b97xd|G-xP(-n03%^fT|ed&HSGA`e$^H~kr*J1 zSA6v-bh%{Ah~;2Fdnd+An{P#*J16_P%yS11E;CbF*k=j;}%_4&M4kDu7UEOQqknDG|MOtF1(bk=o+xo#yCC$+7 z!dvK2vFJ;|x2L=zIC02Zh-o>dApR>Jc+bBYj$_q&+iHzq`HpaePBmbAs8ukyFW zkFKw5v0bK{4PQYM!j5}FWXGQ5>tV<6#>vKjM#=iYRIjG ziM=|v7Pf4{-Yn;>B*4L7DjL1fzvK2Gq{qyUt7l6S#lFiiyQmhH>!}3bZ;!>&I+*>etBC5KO^S52V6Yc>i+48FW0(F#YHx36vU0kLc%+3+fT7KTRZCqd z-a1Z%Z<_AYVZb%9+gN;hsoOTLh+))qApz~0?3^Uv+SPK5MrjobHTeyu`A~a&7-8&2)ex=o#%&x>S z)n;w(>F_GxF@Dnerd!h2?KW_IiURBJWW|4~&ZKt5k4qHL9tx88u=U#RpGYAC9rLX{J0p6P%E`zD{Zy9KDN!_ z4F8~xDmr`4?%Cp2janE1a29v0%b0MylZxTPR5m0mPkZUKhe0fCg5f>Rod|(3@fys} z#4L?pqq`Sh5@jc}=b>`1=QZUu9-x*tzif?b=ya&+eDtSVQX-x>!4ar_i*97f{qY`%>k%%F= zMX&BZ@^20}trBqfmffwhyc0HBJ;g~J^ed2~A>AEFDlh*pfMdsA-hyz$uaEjS>j@X@ z)W!~ihHT@DYMjC-0HT2OxusY*jaMWIY&12JC@X~-&7OgBgocyp+L1Hfty2Ef>sqdZ zk?c%r2#5zub{g@PH25#$bEO5t?TB2j)#Gy!P6p@@29R#jQM$Zdp#A8@Yfir~JbQ=s zXARI#QIX0zo90r?Ow2~ipZ~*&YxaQ+atrTkzR-BCN$uQn{Gy$kR@V` z8nqT8PzNztX`{_a9vV7B2|c{jri~*t*fDS2S$c;y8f*1N#t9BeJ@H2z%u)ek*(9WG zmTfgpzE`R%h5FXz%_9{ZERgBI+Ocm#wEHWSd~A0PC!1^;oA({Q+aADu2y7?FMv1>y zpz?+BtWoYg$dTte^}5<7O~dh9-oPhe#Y=?})ZKsDyc~SOgRpd6&@j{JohSPdDH8dZ z&c?omS{CMXt>ipTbF~TMdcyOlmUD$$_pk57Sh7RwMf4ga5lwXPBv;8I!nKCR9pAn| z*4OtetF>*WhviX@B!6y}#nHo-+u>iB#Kfyi5^b zJ1N)G)WL`giJJ=K_)CpHjn7$`%>7Do%u&*hg!ch)jGIFnLJz8eoPX)7Ie&2;$y$(h}ALoInS~?->trRqEli~%H86z_)NT@?8{>PclRtV@{uAh zlMyElAu@dp+r0-}leA4<$WzpP!AAb65!wo8x`AE7b;AoMM$Hb~C2jCTo|KsxK~_KX zZ6O>)dmbC7pp1l>uH|XlZ+$a0?!VJ?a--RBc{J)X;_jOCgB9y}>xZ6wH^~ymUNU&I zjHV}qTiC?IAJl2-Y%15vS_ClOaOGDyfFc#c{KCX0+yPg5&EDJGG4K6KBG-&au<-OA zt5GUDIi&>JRLUgnXRJ zptjXt6z})Kj>{LSNN1mIT0GIUnA~?~TJ8+ z@N(7fT$JzR-s&(-Tk*<55MU#oof!V0OCS2EUxdb9_-fM{0Qf|zE@R45dX}5Cf4*j0 z|4#b$U4b>P)?!?5^W~%R<|^_XG8Yk&3-26H$l$Q3@^#JF$uHx6P+P-ycW_>3T1hYM`DlaCylT83_OtdI2LQLbUNuyt0=CS0JhS;<0fSnWj8k^aS%0e40w@MS^ImYFC64Ep~Ei@w5GJD zl)m(IRPvn2BRei-;;nkc?^>o+V%+<^UJ+s^+gnf|6En520z4ead?FdE3?V)t?666M z@RfZ@bAYr5VxjkH1irGrlw}D`xki)CZbr&xSFJAX@JA22i6=A{K^$1@`rS5>vml3!z8>wmTl_okgp`1x^%!|BwOnct1NdSZ_x=`cVN4R<7w2^D}s|d zkUZNXBJ=Z^Rtsz=ZBeLLM0+1>D5<}qm}>pO{*uvVARlS#vm4*#(cSlt(Vt48aHeyP z<`jotR&JWFq6)8{%RZDV$edbVjX?ED#@UsL&CX>WQx@8PF$wmkf4>lnS4?2=91U~A z-=yOAW&C*V=~#W*dl59zcwSWl635oeS^f_7`JLWCKY}Hf^4=Oo`yvE>>I;DQ zDv)smO?6`?Bkinh!h!p>J0^PknfKcNJVQrZRSer$q};nQ!RSpo3U7q-O*vC~{GfNg zQr|3YMYgamvTj~s;(YtV{+F;2Cfcu{)vXsx0=%3@>b*Kz`(GcOVvXNXRVSkwb{F%a zd2)RnxK*CMdXo^lt9ds~4*T||%BwH+{YZ0Tz*55-ZH!2Ls93UWJY4R$)0}J=?Nhtj z%^|VBN@*FWJE@pLUnPXhGd3pw54_E~J0`CBcbtTwW(n;~_Q1 zu(MLofZUBCs`UNd%`wX$#|gxTwIH>?Aet|Es28`C6h95s9Ficd_^j;ns1Iq?DH{#( zQk>SP11m}MyMbVUk@ve)E!=zHUWKXWYmwl!k3cEo>79&z#nfxZZowKi%gHJ(GRF8z z;Mo9i`+bdWJI^43y0)pk@Zrw`K>gw=hCUUfRGgw2y#!KHX8s!ewKb-_dC)&rw`3Xo z^L$X^`wu2Ts5&ykqZI6b-@Sm?7#eAl~=WBtav5}9L&d5&W_JveT~ z1fI>=m9BQOk*<7RV5rA>hI_WKILd-Lq_?;Ij34nM8>iRMb|3*PdVCp;Ig@cg|q;;W(YbSQm!W_`Roj#OCHCte|!0 z%GnGHPwrgtIQWox4mvOZevxeE|70faWG6(_1T3j5Q12VwVL%IQ1o?Gv0wf_G*ut+U z^{mps=?H|MM7CQ;+I0U|J@*N4(lZIDUY93V`&vo9?CmnO0&pC0dKTz-ZD6p{RVi8w z>Ru|I(;a=XsJNcu??)P)KC1`tz=S_XVheGbvJ z>-q7$d}*6C#5OivTns2)_-{-OK2&01v6a#FLII<{ji&?j{IFf>w53dqQZ@&W>l4TD z2XH?xCu@I%XUZ549S6)9p3r-K6);3xyCCcegUJR704{ezJ(bLh`*}S!T&VPG&Zls1 z-20(NTLG}rv6?ll|3-{aVBA#u)Yvrt8%9&(X8+%aj;^u6kKggc&4Mut#FNZz%fe$WL6u~S1>D{fb9q;Q z5@Ov9?)!F%1x_ge@)rPX+(NQ%0|>#{D>s!P zwb}WrNV?L!OI}uZ@3h_GgMn`U$iI)X|FOz~gbKd_Af;%rV=9`0dX#iVn&!r}jS>K- zo|@q5T{31q=4yS*#g)^)UO~iSiRWYpT+#R0eh`YLb0piXRI~#72$i_V`F!2t*P3)5 zB3)M*k}GF$o*nq24Wu!rhJfEzW*3+Q_;ybQ_eF5$I+Ws%`E0pr2fRZr&Vjinf+uMBFAry+Gvd&^AO=Iyx}N3K2a^whTYa0SZ%fNQ%y z$*WO=(Yr$ov2~K?DvBLvXO>3qSU&E*Jn%qiM*7rov3W&@OBa=lo@Vo>@)iB7JkOfL z@l@G{enfgD#QY7kGQJ?w0qQqwnsfb$@-516M%aiQ8&&Y7mkHAGbqbs_*hsNV;OPH-<+BvqGOTiP={gps<)RJrgXli>7Q%rvOEg-aCPQjR&6`=eWbdDSv^^) z6iVlpsW!*4%l9+60}z;Cs~jH9JX}u@?GnYGdb$ufU?D21eFuIqO~UQb^eH~ee@!Pkuc;~9d>n4Y z%_xRc_^&X4d^lF?^XBmTFWyJSRHFzklM>8gv@?~N-HfM@wn_vbnNgaogX0d@FH$0H z0DrJFOIdmM+H&miC8W%U*a+UgoIg^5qw-vuzP@n|Y?1o#_NPj&6oT2wg?VG8h{~$4|-Qp5m<-7A(v8%aegGVR{ z6z#GsNE}3TYpOhzCNqjcjV}7)ob$B`SYgx*1S&E&xwtDK87o`^-`WI(sdty)YM0V% zKgToQms=+Y#o&EA8&U0l0B{D%o?z_{{S+npIg%!$V+}TIv8jl-U0ybYpH%=8-Ko=}7HHiI=k$%9a*~FqqjWt+?U*Gs3Yn)cB*@!{$ zt{&g8tGKXFN*R#w*FP**mdb*%OQ)V$m2Yj|E|onaHmzVL+ZeR81}=|&a&l;9A1p|b zf_hDh+)3uE&#yczkyT7qkaq7b{tA-(FykMAB*qdNc|iK*wU!PuhLkz0xoX9N+FvEM z=^M5=VyIfK1>FU2x0`pSJAdylhFbY+`Aoc{M883YIEGD$r^hePYV3FS7MAy}kBhgjP4k6<(mHx?uBQsh?ceI(?UrIg^QL)6 z@6*Lc5ypo-f`g^VPh?cgcZR#DiGE{!LxHu{anv!LqQ)&Hr6XE&+4c8cA5s8Ac}prv z-s|t@iA(QnVen8wzn6=gy_}2c-D0g?j-?BZm*YrdMz<>?N&ZdT zE(TDVFLUNpmtE4nn$K?b)mwk~p3->xh$^bjEtP&|#g1Soa<|deCs^odUMdpHG@EEX zyH5xgS*3feImp4x70l_kRk&NXt!leko#NNWr)20en?AbjOgll_Eov|DGEh0|QqW{f zO{j*&hlcUTNX}T>(+!1N3ZV)#oifx!zlW})v7G&YFD){&_Gv`#x9yN9T9GX{;g6tV z%W++e?84yv%Y17oNahA}Dmj%lrbQRi`*s_KHvk<`P$aYR`E~Z<$9~@mR`Y%#*Lpsz zI(ME2eqPcj@+<3(Osg<*dQ#M+PcwZ|&Ls0&CG*HUa7q{37=8q@i%xB#aWH%&zb$HiY@)X4GV%0kY_24_PJuQs; z8V7c>`SUHncPBqXm)^wU78@MWT$6w*2gJ_Lda)00R~z%M;y=OcGqY69H-C!-J^J8VBC}E*CgLb!{kR;oP~^DrYVw-T9irX$05D&HFpOfD zDXF(D01cVCt6tYEI?$-_f@E3+2b4o_2v(OSH`AHq#oG@tPn2Rn*+L^?sfDQSuavhe3&C&J@rL+*L09uadYQ>5gtGMBg?_b_iRH4L+nn z{Xy++#eUu1KCx`$9s-`jj!NKFUY;Gh`B*Ww-rn9;I`zB!;f;s}?XsBWK;sTCY~3{s zzh3QB*qO~+E!Ob@_K{bBRDX$jDUrVWug&6Z`Qh^tf;-9!{$sf&O*4SDFbY{nS z+d-)J#k3lnI4k2}LgN!}yFW6!DD1JIQkQox09Yvy8Y}ddbTuLwQ)UXfePW2cXv8g+ z+dfdZ*a?o3?Zdh~;@)&qJs<@FX3;c*MB&B~$RcIiZ2o{~kj1@s1SWWAwG~F!qR$a5 z!4_(><7=Zxx?8mBx0>#5&%!53xHOr*-p7pRJ{eu~oM19X1XG6sjf;CXOVkvPO##bT zx8)^9 zaSXt)7{+qMsIp1r~l=^gZ)?=w&(zpIJA>27_D6w(L<~pGgwXhvR~@K|rZKKF zld^!H16b8v(Z$pG)&kY6=kd`d#tSU&cJ7ni2$jIG!o#0`)*a)9McFb-p4K;!#75+V z9;wLf2#aCdP=-O=ZfVtSJT9D>L65wrPe2Ox6MH`wir`1ti!$tqc6|8kGRBy;KqG3m z%Z;+Ppqq*&iI&i4yzaZA=)e6np`f=>(zS-9fZaNwt@aBu!BK$8MD@kfBUk4BW5X^7 zQe@7nuMP1rPK$@gYnG2&*BST^gDaI_5$kr2Mbr7Nmb(rSYCyo0*=5_rboXgmLp^7v zqhm9ueq7q5SkvsUOLP>PR5dJF*2jA0i>s-p7xGA^>+vpST0>G^+YnnxayN)`s<>F< z=$=?Gg370Q#6*a~9;I;5L+=WkIBK^5>g5Wu4DzE7%va2ZkRF2zP5wc}bt}?^oSIQXK^}OP8!TfZHF>P*ANBeZ8unDg+o&AYKlU5#73ArS+L-~ zf2B1|xq4ozR!^J z);08dV0mHN=W>oR4Zk&^=RQ|8K$v~{hWrwrDb@KY= zvE(gutMD&JGQ4tYnY$k}qP)D=xpAn!`Zwb!5>5*F;JYbe#e1Y>mVGSwO?5yj`|o1D zlB}x>hSg8shSZM!4fOSMHDS=TPNT3zmEQ8&=7Evl&v8mld?OkXOgKBk^@Im{)qIq4AyHQy$2Bx10Xi z?n1GBsk|p9#}6j8IDQX(=}Qe|{wvz%6t@nU=e=*=x>EMus^{{%iC-0;v;kuN*4*Ms zlO$n;k;e+bTOYj830Nt4a~mtE#)c3HnC>kWKLEKM)w58M(5J!`eZC7GBF1;AI5Lyd zDw2ZBRqgW9imM?**Do>(Fku z;7^W@^?{4Ku#j;woqc@i?DcP&`C9i8TMKS9A^gauu)B=Da6rtUoRR%>w6RYayIV(7z!Fp7<}W7gGUySK4x>C=cIdEWJA{naWG?qTzR z?Y@TBN763^SP)9*lcw;p2+P?c)5_-HIUSWZR&ybuIH5X{K^UV5gSWgyGE&LUkN#tql zHIBKnTAw~haz6^!$r>b$r~j7A3celCp4&Px9Ziw7+K7pmiQb#Qk{I ztXG9s5-8!S&(^_hmlfTuk8gMWmkY365lR^B7dj^+e6OU~xz%Q}&d04d1U^to_iZ(| zkB%}{A~3?rmk;6Dz+zzC+SV_0A59lSI5%6>@kmXM4v3@-P5ol$I0x;iq+)aQ#2MQ) zi$3%;@H)=Z`z2{0x+(dsl@Fmy-`aOs_wI(q-ERN8>(QN9=v@B>q1*2uAo5EiJyj6L zO3j64yDmh+(GIwhXfIOnHqthJg!TN3Z`+N-&lFK{{6voJC$T_Ck0!?MvAgAZ6m{EN zSXY~xkQFz{3=pp0HY)0FKfgEO%ow0}(Sjpb;n!$U<1)xCl}h`VsO(d;>-ub6EZgWw zHbWJd)8HYV|DI0e)G2tP0Gtsf3p20dHHQIic6@w39&X`*DGn-vON|9p_stx(J-%S|w z&L5dl)GJ6;)X_^lh7^15X2nesO?$w&XQ_(8uDpc$c@=vYxqUQaD`K<^;;wxbx5zD} ztn5Rc3wTv)ScB^`zkmcMf9O|9fK$X_iH@3vI0P}R={HzpIgs2wLE~O47o>)jd>2s( znMF;BYYsOC~9Ze!-*n$r+b^ zYB?uAqYzDY&BcrUqYDFPwB87qB8FJuh;{bdB8K1~m`k$4#=PNI-hQeK?;5JZA@3K4 za2(}kO(^G*5p$5!+WF-%T;Dy&^ZF9P3~3%Gl?z1*_#MS-0T=2nxxLj`aB5!3?e*lC~&ld4GY6^QW!=MiJad!rko^hdjh{bWeP zRfCz@g>*u6EaNU^3rGoFGc@kJ}~SJ1ALCUU&D_Cb8i|0woRhM?3wg0dG-^@C8$%Pnxb$fRK+GarZv#f} zartsH(xVD{1lw?jo{yGE-<%a>UZWcVfDC6xxNhTtVw)m8Zn|H{4PqDzo{DJ+y6Q4w zBo19+v~A0Td$a}V{;1Lp zYQ(%*_1w09v4Ww+-X}x4nzvTE-dhl@Am@u}gEn`p2_Di3-@Te6iK%qU5pcMPsH&Nl z|7D=OsTy)cr*>dldb^lewXDq5cqfp+rBu0T3A_082z<<3h3}#^hrQ~OF8(|9uL24mV=O20kkIi|5C(B^-n$s;rzuXeU`KDcvbrr~1JX8{%0dzK~M6JK@* z%gxEyZEfQov+rlBwN_vMPQ$Q+E^1FDPi+L9#}az)VZkp;p4^?VU%h3@sY$k z!w?{E`yiDGF-ph+;$qOHUP+FneLpENx7K*T^s*_gJIIlBG6e+Fh3>;o(}@U zx*JiLI9-{wW5PLlE%KzAjaW4Zn6v*?`lWr6P2Oe6{Bh6a;!Rs3H;v(bfV_Z`O zr9SO_|LHdfYK#40Spj5=&rvr_hQvR6cBQG8huc9o$foevRd&!iD4LoIYxn0N=t(7) zj&Ob3mJS>KqxFPL-SMBF+@X3yNVmZnazT69+w4?Nwb*KI54cjXH{TwOcXf8^T{o46 zR9v!3Pa@n%a4~9i?ie9Tk$*R9g&zJye||BZbqw1T)+q ze*M`WZsAAAHcx_nNHOvnap~$8)bQv5{~EUB24{e*H=0L9-HV#j^xo#s?@4jEnXtCB ziDwu%_q4ezkguGWpjEbM^_S5uaB@yw+h;3_80}~s>lIw3LrPBGFAAKd)4^eSzwa#7 zgfW6+;PMB9r1@J#0DP2}xQYmAKuHSUFKSS;s)^{r_}xO&J_95uen_31S9_*lger%9lZ2a zTSaKJ2lQYVp>_WcN8!`z9y&>>`|X;l{r@KXKnpoA5i_UlT8&W;yN8MCbtmVax zpyEu{+M&7yHK01j7o;@L-B_PL?G9DVqLHK#Iu4Hj(qbXx!OF*86F|NhPGK@aXQgwa zzt*K;%If|9E~f9`?J%j2PRUkFH~`x;I46W>(K5Lx%k+?PSk>a~;nS6%AGEG`l23p5 z-$3`JNG@#qqlTJ{o&i)TCsO6(BASB+dvX`I@QtR<B<}_b>Knc?=_qOUYgmRWdcwGS%j-dy?-Jc8oit6vVg_Je+H?o z7>UaJy_)YYxmRK~>epyZXk2=S|47v}ob;yJ`g+$#6Gyeyw@B^(qaB0v%J2>}s4Y(aAPTpc? z5OhwZJoC%6YO!Nt^KZ-8rpy#qN1P!6{bD6wmQ9xN)y!`XZ!dMa{Nv}l|4=a>hUBju z;2T?r*hRs}R8z=DP~e@PKy>|OMj-F7shr=pT{SJ#I9ye`fce1;& zwM#3RA-nksv~BGUe%GU-(RkX(0KRpunFr_%S94;4`5%l&?FLeT912=1TM z(;O9{{{7vE1u|qMX12A*3ZGVkS6xsu0IV_LH8bJ{CU_Z4NDQsLGTsLXR}?$TbXDHgw#fwS zf>){8uiK9ibkAeVX25yPl6h=vN)F~xadL^o+7b_I+v4&Sed<72ZGCU8$uEQLukII0oQeS4lu|%B!-4$svI_E&-sx2 zzPT?ez$KGN6(~0*2R5*et!zf9S^@WnIdgR_4R+-RS4IpZdF!1 z_V#e2BT=jd-?XaqZ?RD#e68V2wp~)p_;}%_= zdR`g{47iU}0h~+-r?-kOFP-(7_-FKI0F=K{ zG`&13(z>i-ob^1Un(Tq_zPFzJ#DNlfrSq{@?f(KBnI z!IDtHe@Qc5M{zR~)=MCIm2#*ITbSRSgux67JgZp1c^nCSi+#xY+RekA@}tx}y54NvmkCioIW|2Xlfe1!8h12OB?kHr z@c1Xu4ykp?HlO1ohSnNAOb61$(D*cv=w5m-XfWvV&v58ki)PvkqA56BE|NfHxf}M- z0%$%Eeh9c4T`iuJH|$gf;cSLIo3)hk}YCFeZl6L12+poW5$kqAtI)WXA8xW;|^F(mwmSU2Pv@&X4byX>ub; zf6X@6$Cb7);7HSI2G;w+B~!LsB81d4E*+r}9``5iwK;RVe8)m-*JexSc%ho7u1>(e z{)Q|+x8|%OcsY?&(PRG{)<6~)3LAO_^%?%SJE3XO7SOgq)zlMXo5Y?NZC`+`<=Ewf zal#0Y?4UIQoi|~;?5Xpf2~J=p*ull12@Vkk3VO?eXvXWaG*M^ z7JCS3ofcWJ2zl8rvCCu7x<;Q6_t{!tdVzje@yV^x%!)!yp`t>~3eUo|HTvPugNRUT zAAFU|Q?4nuhuMwzD+%H&oR^~srD*!ZUlO3TbQm`_P$bT2--yc)?4B(X#Qo4$@hBsXULQuj0O>U1m|it%%7ilJltxl>EYY?rbY`(8$^_3}^Kr{`0vMe^C*UMDf+ zTe)-;F&zqt+#9_A``Bm9kJ4%c<5<8XR#f8k@6CX=eO#N`tx=zP_~Gu?>5_{Lh~dAW z&l~n0!kKefGUHr1rd-Lvzfs|J%Z+QDt+yRb2;r;T8WGd-4mY zn>Ca6N<)Gau`aIVN2%mXB`NU=Vl?}PsD>-X#^r(4&?E6{dNru&?cXB5)khIdak zbRQi4SmoBX_MzH)0QJlC!K9_E=*U8Mf9-A8!i`t)=K@g=;Iq=5gKy-`wR8)34pH}z zEU8wZ3^I0A+K1ab#L%tv6zbxBiDFfljneo4)lP`Y`;57~9g{8b)%tho%g?{CPm(`+ zyjbDkV(`PN%4tlxi=5ptE{G3Qx(zG&AQwyMM13y=^}wzvu)xA*B3q5siB@-4!9%l9 zSGpBi$_S=Au{2E~O77s>tJlaGE(LV{w0t;h38U6VGq4QE7`+zn{wF+K#WZEyc`%_z zmdEkeW6Y3}%MFj|qR>Wv3f=l7dEEvsmM)}nnzCi16_)X+d(I%#-R@?4kFZ)E8OH)m z2R25g_rZ5Y*Wf$DdUb-HidXgx$Nc^dw`T8ZQy$S=WVo9Ys2G1p6Ma?x(#P}_I!n2I zJz$nwmB{&K7Y~mWclH6bjQ29Ji#%?!-2>px8=QRih-V{u^`fv(MVT9#v-h)kip0M2 zPq$}s5-Vk}EG7>vV1QhaT5@Z~!FSrkD(SdT5X7fpTen+2I^%;(A8|RyH#Hq&Q1aUL zQ<>^&2>9VP34F_Dx?x3BGLw8az3?>sZ^w3%0%zjGeTm6%uZfaVH9UT9g9=>!Z+1E& zXHe9_#m7ov?b~YjLe5Dl-9f4h;pYS+n%K|Bl;q?ZU2US!H!dgwoSJ(;fAn? za1;OPX}V$%izg{GXi3i*<^xn~z24PWqUgERVL{w69kaBz<4_HaN`bo1*0UHiXnErF zg#SXUyT?mHUVe%Wl3x`Fn+r9M0J&uczHlBh%mwoeLd!=GFTx21j-%O%s;3!*xk404 zkErI~8nCGUoiBI1SS8QmPui9kf`&J#xkSnIeJeHlwS@J8HAkiFz;j}Wd7;M!A`m?7 za^3lwyi}~IR59m`=~k;PPQ*IpCO;2PA_ubzn| z-B8pZV}kfUG^16js3)L<9#d`zZ}%CteB)o03;PYI3WohEX>-L_gMXEBP7+b6BwA$j zUS|a{zf>%edZL7OU>T1vH?9T_X7xA zUARq5&IrzZ@vtBPLCWUHlnQBepQ|h6W^51d>Z`Mudi_oAEB|Z0OlULTs(Lk~*QVo0 z`1;KHA)pf2fvZ7$rZV3!Za&Rx|GXYZvfmPb?N0bj>nb=|mv@6No_2#ey@}>1IKKfa zrj5RvTQ-mgwfa#UDv3>keaz1=0K1J5)1_@(M@w|_-goz254|3?_g!2N=RB5t@ovq0 zv6tl~ee!Nf(}v8%p(3#LSh5W>MA_>Y%?2gk_dhB!R$%nvA}%71{H_Tpr|(j0V_8OD zEBpS3RHS*FI{4ho#i?)_7*5yyv1m})g3qX}wYDzLeIo(lC zwy<$-zc(eE)54o}^FSShxSjjLa;$+UT9oN>zflB+>?Ro0+S1(v7c3B*H-9Tv&k^{R zCDM%kcmqsYvWC9Z*qb3-NKw*vf5NogIni4+-LcRng;}11C(U*<@dizB#uJIaGJ%6U z2W>Qvj=(aMKM1vbLEOu`-CMqSh8RKXT3@IdAf7YdU_Xns5vc2Bw8#Fzm8&hae^aQ@ zN6WzL-M#tD5=z1LT*=);xlb0KH$A(YVdFc40B3A1bD^Oyig1Q=3>oUW# z9Qbc}REAK(F=MM|0N9)}P<;#Z>$4(_CzSCB>EjT$2?ed}2VnW{(t2di%lGhqxE_juO71o9h;4G-6!s z^vBBHOX=8L2Dv`@w1y9%+T{QZenU?>k=LoR0kyU^0{mom{^3S5?r#$=cvMxmHu}s( z$K^*&5|ypA)z3>{YPb8o?UmVuAX*BLkXI?I^&}Q|A98{2=KdWqGcOF}?fB;gpW%uK z{xD+h<&!MhHQWwSg${K#YVmAJqRgbzoIg^lyxPALWDY%8({f95KgHU}o{ zx_-<#ox&W2#MT|eha#TZN1gmUnHl?_w~}>^tWHGD+0ycXZV1HZs(SyrMMzpVK{RbW z1#Zqp$1*5!VpfNz5Su~(&kJEY@AD6yKKYR%)@dKMe zVzUL}^UgE-9e&YGGR3g=55+g`TfU=ikkf=wU1FmZ;E8Kb0vpgsaX3viH{2z*w|L|81mnHH2=K^Bg(MP zZK`erqqS9v)SN-`U$a&JTr65rO&PpSW43MoTViQb2`H{3_eLb*lGoRX(x&1FgP}A-Wkc-d$+>?8=L$2k05Y=TS=HG)9a!(%!oMq7@BTD{kNesNj16ahPO8D! zST)$zr<3EZ?G4!*F%fR9p7gR1RXAe-psP$8*z|VQ{%uMF5}nWu-+TBez$%er_Nt4^ zu5JvDO%38mBE@l@TT%Zx$M4WA%uwMk!7TqzPsDxNewf}Mw6z`59go@;8Kj5`$qWoP<@W#49I9WMhhWk<$j~Is>bHalm3>Qma{>ZIhya@l4Gff zACUf#Q35mX^gZu;X`4!{z!R_XZedQ0D7(F#kcU2YIK|4E5W@!-H2J%R5bF?9T=)&& zY-gjX(GxC8J{f-TKVCSOY?jW4;82(8z3{PJ`ZDIvCC&R%3{rvU*Rml6p~!2yI$|77 zfW*6+q?%~r4FZ_U%3~$jZBPit3>Us{QboG5LG|rv$!gugo}I}cZ(+fIbXX9bgI+GPw#bF%DH9I zT=IQ+-RkyR4hvn9B%NiZt5lkDx%ToF9+xid{fZvo1(lvlqWMC8A?)Yu_HlWIycaoZ zD6L&HCJP!uF~r(R2k~N>+%D^kfaaD|b+>jb3uV?j2l(b1`|1v2o}_WR11%TMN8F{< z2@N~+bv*w)tX64~blmb6X&s9kIX0=5RBC)B%g6Gq{F)Mpu~Iv`-B+(ae+)m;iS-%1 z%0j={HKk2Pd$=G{eS*xdQdhI4CVJAWgc1*y;Z!l*x=wU%;L&&V{Ui!K_MVZIw3p3T zSp6}xR7qyJ;x0sQS)I(dAWz}d&BLO9?rNyk>QuHKN!#E6>!)jPCgCCLQn7w^Xh-8V zZxT404B4MQcQcHdl&CZ#lp@+x#E?lJIwgL{`HI)fNA(9!GfW|-iuT7wM(!Hc+~uGy zXq;Nx{6^OPcKwh$&RDw3=Em^0>qi!KkLJjXp}xVPi%SnmVHcW0XMN5&Q6cL zjNVc<3nh}GrNsNo}qK9was$=(g_^3r~YZ_d$x zF?E=6UiwkM`l>~TEaFYMDRQH6WWRPpe)2LUe){VDrg9m|P2pFNw0G{?<)uDeWZx^A zi_6@)pV+)j%H%8mBXF0zl8di7;L)XM^qs|+_rKO$zN8D}_76mxAI&;_ZencNl~?!P zlX?)+eZG|K#@nAj`%^()i2@qfg)0_ULSFdeCnem`25*80ie6$b`S(@Qp!HR=m5CvW zvdve;%xL8H>=7(LJp`9E(8h!)Nj^jF{>+PSnyU|)bl3E4W?)0_SJ8E^YbPN|cQ5Zp z1Ap~UFp!1XXrP5L=l6_pO(^6tQ|*gKYs-?(gP)$gi(5~MRNeseRZ(8vdlT*FOE>jg z^xvV2g-bjN_(JZ|jf&8Lq<)}K@^zJQ84+@d3!5xloaBC!7FWNkow=ObvB49LZdZqX$-Q1 zbDsV7b7>ET#Kc4?9oaeP9tBVOPmk9&Qa>T?CC}HOHX39PYw(q5j9|ddfDH#db*DjA*Y+jvwt}a*G4k?8$UOXko9rH)x-=A5CAc zaQY}!3@DqJlTL8J|2iig<|BnmNkbs1K}*@q2O$t&iI)k6dPjj{ERU7LAM-Kl%FFoa z-|Nv!h5Nzo?x;6xe`57X?u4cqx<9@%|AnYH=uXwBXskL>Z_xHCSY(;T90*wC%=ySx zCFISz6VwEY1`EZaB9+UCW<54IIFg4JKYWw5Ufi$Wb)dA+_==YT@hs2uDGOEptwhZs z!%CQv!W(VU*2^^&_EC^>0Hfnx4dmkD{ynz6N{@|&2ejRz1Yg;7jA0e)G=`SpiT>7q zo|5%suHVW8<2wOQd(vMYw!N&TSVj5Ejfk2%ZaBZXajxtdH5-(~m%+&|ziD4zU+7Pi z46!0GM&neU#ylj7q;!4n+s~tG1~zR)wuM}s%tjbazF%IPKX;D)ASQK)DnrcL(pbk- z?ZmFJ!DyctCpIxp$8yngfj{TG!X?iku8fqGFgwM{Bu4hU)bR^GRJOV=)!dP;_jXGQ zxU2IGg&-oVGI{DQ^<0Zf3Q&4ed1P0)7b+Q0{^ug&XJA`q*)pbK?&QM1s~)cpMB}gY z=bY5*O&d4N zA1M<;v{`7r0N#tKE0(a*H6cos2sP~tV2FEFD=uiZWQp^%Fn(2)$+Mh46e|-?JXfDlfL@-l`$tGgqj-!O&H`+4Z2l1{9~R5`P`MSFF1 z3!tyM^y~s3(3dbWu*@wZ8Pz=H2|=i|U+bI_kTHeJUbl#RCx12t@8`m&;GP&QQZK$) zpj9H-2j=BF2|s4ZaWcYC$m*NCh{q=#3{~h)je5~%y5jU)FdHM0c$@<$1 zOI`M3Q!|rS?n!_BP2G69e(v~FUxuY^X59!Cr@r0(m57x;nLD>NgAP&RkFHzeBf=+B zJg0I_rSR`+_-H63j~_ErRcEK7zl^ z(LX_zFAh}_NMbc^tpN;2jD5$bpIzh!M1+ZI*1601zFAT<@gT064gNb?SP z=hV!YFjJ~g3j8oS(db3GHX0}&kNa~v?d2*GS%n=pJrYkJulZlj%RmqI4r?r)nHr-f z;Qjc-udj)Ud}68FZ|EIM(Lhf+D&KuG$+_xAvsecAc|l5PIBw+amE!EdyTD$CPo05V zR$CXYaIVhICU-=VUb{M{8)}d&LcW)2m!jLu%)p{%C6Ym_6dSe=)PyC^ox!jYICz;}dD~zxU;QN#KtD zN@ArMx_mLW01-x0)ZY>vOuKuj60Ot+7c20>Nkpj=NmPACBC_wUEBi&Pvfcs1E4;H< zALXlnGzt%$`-i~?HnI&KA1;&ud>L}az8xuJ-k>82`O#&PMHqC4D9dtUfD%lllClQx zgpL}_DuGrE(>Bn_nbC0>SCo6G+H3Qnf=1(oe^?=EjHDn=G0#UQSati17Se!AA_@HJdkAY@z z%R5V;A=M+-S@Ms@uQZm;FrlVzwga=^ml!V0K;mk4=Uwo*4vmMdt2pC3wmzQh8rSsS zhHCNd><(a#-v-B}Dw23RXF-Ix$WXsRyCs}woBh}wUj!&TFjiK%aeB{ox!uh=;`A3c ze9&?z-ETI9aL*mJlt`NBUvq|GoaZdtBD4 z%$sT;bBa00v{8BB&D957Ba+9+=Ky!)(!Xz*V7IjlEb34sHKu8zrv}g8)OvZzm9|FW zmO&Zi!(DUGX8pUZIGrcd?JRb4dvDTO#8feR+TAR}T?bWvZ}shJiOTVxfiE;=y1&GhVRi8dKVK2EpIW!>oJi+nc`u`jY{MPTz{nu3Hl6;TC$ zN={#NW7?$qHa63Wmi}s^|9h40c^tzXjuUJxE{+&ytS-M0oEM=_?c`STG2xvtHn8aS zZ)vu5t*7@|vda8^s&x$OgY^cUYEjdkuU>;2zs`)78G0fy`%&+)*C8WGjfLq^FA+!N zrrHtSqKdN8KO*NC zFjc)+4Pr_~Zp5iWSiTOFHe1d7b2zvTY$nd>Pe^4+(9%9DL8T~@M3RW1)&;$3q2G3l z+}6!*R4{#p9+^8t7%0=JLkL8`1LOHCZ7ITA19h|Vaj3hs_S2OM>l(EL@iao{lvI1v z9$-xZ=z-(*Jf}^{+U#qU%L|_KczCbPA+9iowjj!;L-BGKXSX!@cC_d~t$i&-pw+cL zYVVuK1Lbfe|K4wBXV>zUvM~q;)tI&ZghL)Z&!D4}=&BRfh|;MC|E>AX@iOq&BW^$E z0Hsa=cG42yXWyp))CIq@hL@l_y%yQg`;Mqyn`5X-x`ULizGUFcrXGr(Dd7u8rh^e~ zU`}8rI#PK(@2jqTe#1S+q;$$J40}N2%~XaTFsn{Mxbu3T5qru(a!PPdLwZ$F-tNgw zQ?|9<{v50RlL6#T_&>tlG9b$43m+x~LRIyI~Q>L|)25#~EI_XKAFg#C*G(h-rB*lQsFLwY#^#%N+iV|H2!~6z$DG^7% zPeyASz;FXKKXrBP=>Qwy@#+As9kR$x`blcK%?jgiyzq7Oo=5t|6dg%(admlL7XzBfhfnj3VqdtP>BU@;&}wb%$k*{ z*pr6q-kbVy^7>px&!$H%K{|IajxRP02q1vv>D1@?Rm2L*{5qpcmLCX!5?b8LX%`Mp z9OTeaZxQM3lkB0_t7PmR4+P@AK08ZkuH$Pvf`#XN-zR2{Reo>fJRaJjLG>RF=P|jo z19-B{w{`-Q*d0RSF?1Lg<_mz9o|3BF!3Str?{+0bxVyVjFH%_zu z^#AEq&$E+!vC!y|Lm$SFX}bGzHs%w|@N!x=yn-m1Qt^ljXey!V9i4oN@0j%&dG|Fu z2*LQAP}wBBqbOj481O0TpR5aoNG+W=*UUM58P_2MTX`fXEnuZ2l#x@Cs9DlMsn7XrvT@3 zZHwrcNbYcLX$$NZ0jgE(jyR0rJ8jI|OzY<&(eE)6iLKN05aWZ@QLE&<)t=%@)pYd%l!gP^fgKZ&R5&@M?FFa|4Pd!(jF>x zi}55^CE`ONIGWG7H_XNN$-U8bV)aqziblB1o}0f{HO}Pnz;J-%!V&tNR&l{9-m!{C7O(0(lUQVjLgY6K*jjBaBGblKvbH7XUi1FtmF~ zg+{(6c0cFeC_!JcL33B?X1VTK0OFsst=0{HAyo~aMnbMvj?JL!eP<(@>Af&lyXFM# zAblmH=g~}RN=xyqHH%RB@AG`q=?jwV-m$IhM4mL}H1MtzFXWp(ZE(B=N0%+79bSdv zI3q^-N=?p6EEC$5XWTHqf{xxD#Mw7@FLFQhB<8H4CD8@wsa6JGhIN$#{4!j11aEx` z9%UXibKFbE+!+`IRgMaiC>ws;j_-?jMMJ=@EdE2=A=bA`n0z$yG{|8HCt`@ZRiKgC zgsFW1^=gyO5~7j?H!(|i=T(0)_|h@kaD_P*A`62 zaQiiqaY+9LU-KOyaxmXc1G-eEMnQr?-*Nyb&98b3o)&$QKkvSU+OIEe_J8(ly1>Oz zJA6D`$oe2_WsWttVqw9~h*0Yo{YgQ3b^clq+Obl?vs`H4#J_pjPhf`;0eSbz^lbLq zVd#@>4ithE$l$Q zkaEbr+qD+*GX`q~n23_KL$KktTk!URa@S>W?(NIX%T=bT-r#wOclJusSG^Nfe^Pvc zi8E5enMYHUZ(+o4y;k<%?+GV9z|(Ex!{Lh1laU5|jACzih*wIL7||JVpEs8?wws%#6ZSt%tJzI599&XpiOLg_YQzJ}XLMD!kp)Dfr^=>333cs>296kFEX zz4O~E8O%7hPA7_p1rp1gfG>8Y5ZVVEL$cj@5Z-s|4309keie4UG&vpa-@fT_wz(if zlGAbuNVfU{(ZYfzk?;ahMyKU^l9~dd5j#J!MvXcQsapehm4mH?UlMjXNdk%{vz#&B zEnYffwZzyQuF}+4Db(-8jGo^|Nh17nk=)8a+0u_MGcwOsri$2dM*<;^hmQO7YvfUTPsHm6!ja9`~`#Ia@#sN zrsy}gb|+r3pY-9nSb^XDQ0iXh(FNsO1!A{;Ty#PR7XR%K{L$@j^=x$Q7vxpq=3$C>q4g*32|_n5dLf2#wk(bV&%zTMyb1bV;Pv@(}E)rWiMpb@>igE z(sy7ds)OSvMt%q`<65UXREUA+g08k52&BW{}x<`V6S3Xu3;}rcn1?z3S$A2yy2G-gg((F{0 zeL>r`S%Q9)w&PZEXS>eX2iuaFTCey$QO2;`MQq*jw@i}n&6J|DQw5)fpY??Ior0O; z#coyLRDPUu2IQQ};ZOGQZ%3Hlg3(pdBxEg+AzHy3l|mPPK) z9FkTDk=j&F4X^HpZ4*Rt-p86lji@>jjq1fv8VM+DZqMf3O0E3c1hWFNkRxlx0DmLw zl$AT$uE}kW<&yQ5MQely`CGRUy?|}uM0KxOMHJ`HTOoTZE6CLesn@;iDZZeE-Jp-w z{ve&~(Cuf|&5I2V*-ZFN5G85zFjwqsiDQVr3l1HCjlxMGhfq1TBhv%9@&Ropw7!i&Ch80^Y3Vwd)q49CnDA+vAUn}|7b zwz*U9g8RF;aMV#74E2V@z}S^ZV{@WHEz4d-v{7KNGY>p&259yUxk2!ou9)+YZDbWo zxQ#hN!tMT!FhV6i$#Y{~1x~6$s}!Y_k66eZ+aC5^(B@?Ykt-Lh-NVYfJMe$QWrYmE zEG=L*mAFa8ChSxpr{S$V;V)02Tr8x2(Ri+G!l3-mVhduoAc&i!>Ytzd&rDbB7DWUl zV|#)1w3xt`H&@B>6#`R?7A~EYV#;RLq03n3&MH$-yR(XzBgx3tGO>$9xDto;I$wkm za{zid6eJXjohlx@s<8de^U5_wx2u3S`#}Nmp-;uxx$<)gCHeK4+`zl{Mgi0U?31qhd+A}fGitOu zgs+e=^8Of4kPz|>`UV{7{3M0=e2eriGb#55X^b^h9|Rxs@(_%2Tah4V`-YDR=?z!( zcA>hKO7f4)R=@ItfYN|IPkIw}1*mjtBafY3_R@mq50%(U?JB5q^WsS*5JzVtZYlkH z&xlecf-1uzb?H^B1^fCDNzn?PkFgOF#!U@&hy#yWCFZ6qZEf^uWa=O z3&|?nkXdCwxU&a<3MdbCo97$;rv(@)mrIZ+aW2s39j7JcbWB+qRvl3zz-hJSv2Xgj9$fC(iEM31Gbo^XXd;1!~fopl}Niy;IHtAm$(oY+tEU$rih2#7* zi;t+!!Z3S$#Bu&*CH*^61|*zUc>d$T6P3_sKNAfJxmLJL^!+2JQxiA#-L-BGx@X+_j@DW6Edq=Z#95q zTfltHyI;wo07vSM+;|^+gMaD(YjR*dnblv~=*+GfUO?`;F59$BOLQNqW35^I+0H5T zR(vR@Nz1w7QOo*WSmnqovT&v&f7jvHn*LU8_$H*=O8)B1Fq%m3+Yd{>9@Pf8nMv(3 z19YcdMTF=EpmQ;_9N$TvuP6f|)Nr0V5#!*~`ii4?s;(zV#DV+7fr-RV4lEbsqn19|ck8f?kzC1G%eF-+qzd0VAot~H@(bGUUfWk9j~rsfs!d|Q&NfU7)n{Z_ zJ{Fm!lz^oW_%W_cvP|g@I}pv8lai4cr4rP6_SL;B5{~@of2dsF0sGGPcUWl~;bWTN znXAx}xBO8S&g|FS&h`!~y0c@Q+^eUgB67d9qN&|q`er`8tTr$xDq5nB7IB&q3dF1L z$z6agFq9!9fMKEk413N&`e}_c#+9nC2A|@LrOQp9mtPw6cEazo7(3assbNTz_8){ z$zaw!mm$}ql3o%kB1Pgbsg(liL_YD`W+R@jZ7Y3&MJV=%2*+HH^p5kDFx{7nv|`xH zJ(%U)W7ew{tuxm@>LC$4+vVu>8!R?#P*iw}K+;FP zk046f_DhBXbxGPv#-VvBf?eC;$OO;FRx0Cdbu!Zy+dT?HcO@D`9_(BL-9>enKiX3^*^+1N_?s=C}HwBw}s@t$zZw)Wr{4 z!rBjJJ|u~`4J!FoJj`>F{PusZ=itfY33$eW>HPsLXene_pl;T_M^K8g0a`FZRTp&o zy|g<}yVagBeb@}-MBi9x8)POe7=6=2j3z$2i>)RCDbja9#4mKc-{(pr@3^_Y3E|S+ zuu?+*%Pmp@CA$&DojIO_o^V{2q{w-5K(g_zZ~c-Y&dKsAeF24ceF$WOx;+98h~mF| zlK{vKNlEh9KCR;a{&Vr~|HGgD`|bbt*Z;~jmkC5oEfjrcA3t0xi0iYiPtz)PClbb- zXDejXHcW&HVcd+qa(XPI4p*8XEII%WKHHEgOa2WHFy}1)3BA^1fO6mO+Z^J6c4I7DHnwcJ5+PS3KjfgfBVjG$QOhE*EKTv|H{I6(0 z%VDC8ji1xRw%|^CqTu217|~zQOgX9_>A;qz=)^cN)pH1;NcfL|(;_yys7NfHhYB{U zjGG^&V1uk2hX20LML2otQQRTbmk%=x-dpOq@AN|tJX4us8`jFI|Dw-e2i5@s#I$}g zZ~r|oc1!=-KN_}*H)HdZ8its+q7lJhlz??nN6o7oomuf%rw(DyGs;dJU#gCkM5bb# z{dvaFvB*$E=E>v=BF0m~W6HW7(D4Kh`cpl@6U2ZNp9C%hp?|Fl3yiZ|W!sNj?h78x z6bGj(l3grV2rr%=1UMmOPRUH!ibXAd$Rr94ta0)`c;Vv@taxHM_5lQ^_?83o?ajU6 z7Z(wh1Z4(f*x*?yyq7JS+NxRik1j_pw_cDl`7=azVugLrx z6{%sP7mNdU;0{!8aFsZk!chWBNfIobs)MQL-LG__UL7R{on5j%?`f&v*Q)A?Gv@oj z>d0toW;Y{-T0SyiaVL-Y#E#K>CgYkQpG=i?Z)f@(e27+3E^mh6k2k#?f7K2<-1#E( zL|Et+6IoLbv4)0v;i!(8B+A#~>E=Cw$2qmo4SC$NkB?rMnETw{ZEk*$D*dQ1{+ki; zeFmW;msf>^0XynfVUj)HY_KonDmq=aDnoIds%&ObUDmsng=Xr}^wbxf_rPm} zn9N^g{G-2F4)0aP=JzRS61MV3g}6K8$S>+}#DRsCZ`J>@ zGJ5}cGq(lLcN+vDP7fzcGbVlJLp*y2F&P+)MVg=hkGSP76@SW^UamK=eH8|ZV1g9n zi({Atm9LvXc*;`EIJJdmR!l#M-^%_q!KvAKR98J$92QPDs=!u&uS|XAHMBI!@&)8{ zgs(iegIZ(op61}MR*^b}#IDEf&$CuYj&}&1WD9@|BC_eaN{z!?|2m0eqxEDdIs1E3 zflmXg%!_UwU^~N-;Vnk1UFAvT){*GODVy!z*Sir4-8l?z~!$vzEOY8A1? z{;kt)31iaF3c=;WM#hqvp0Avp?R+o!2e^K<+l`@~+T;_VOZD*c!_&f5$X^m>wzyC0 zl4kjZG;w6@Wmaf{f+h(hAyGdZpV{Zw2>)TvtsHFD$r|4Gi8gY9{LoWk#l|myD~t1J zXPf{1!XkC@wRN%JNYwBo+(QA0e4)Shs1Z5aFW(uxMX=B@(V?T%jlRm z))}>t*W_Maw>Y}(Ha zl!YOa6`#wG`;hH)mqAuxU*+L9%UQeZfq-*q4xiBX7dcxZRZ`+dv^@K8`?n~f- zeZ{xT{Jz-zx;vYiXOt$P^p|!~hXmLRFXv>$i|5Yy8?se~`uTRpjWZefs0vgo%Qq4Z zpR*sZsoAT3U)0p?8)pD_u?9p@4SlBz*}T@H$GR?x_O6Z~qqSt)Pt*1Spn{H*Ooa?$ z>%$wluSCLb47{g>G`iX7-p1lylQ$OPh9Ryy65a|F zTiVe=z1wi9HUUT1Hn-u4V%ioabT>;3))=b%=a@A5+)93Y|BuD`HI~P<6>#Y`VY4lO zK=%Ps!=e>^OzokOFf*LDAx*co+d(!|O!ylNE%VtEvL{@5uypQ2rIWvgky9r6nHBhQ(Rh5KI{MP{r1 z*{oadKb!S?HXIR=KTGRwmGaRS8+EDPlYil~P<4QcZf3n6Dt)ib0r{K9|1_dzmibH! zHRR*AC+TQzc(|lXXec>o;nUDp5~HXY$o}7$IKfj6QEp*%cc8+R%Jl%->^8nxy+^Q! z?f@~+yiS)4>GK5dYrdpKOK2x8mA z@h?$iis+~;)QuHOBhg~opZHG$g9l5UEa!eDlk&xHRRKa_^4L)89y?fA%P(Lh>r1N! zw;&6@q+|f!4oO}N+SGUBKml06gI`tHL%WLbi3V9cvr(@@kx7`$-kNtitG;F4&4yV$ z?yQIIEq`Y$5}Z_MTHEf7`}k0O&DrCS5l&EQSIm3W%sV|N%1Ly@_Qc~zf;y<-z@(of z1^t*XA48G#>%Oyr@<6IrHGUDG0C7WEw$At%jdzF2?5sF8Si#FGL5Q3Jj{Ef)*vI8a ztddR5(8z6XMx6mq)v)5}B#rag7OE+r_ja%H?A^ta1?b)i!;JuR;7Fob)C*&YAFa&TYdEH~~M4OyX znJH0GKOe-b#x;38aP!+A!5O-eJFY-KvH zI@F(+Y1sD(s2^SL<_Pq$0Ct~sVI0GFWOwIo_SmgdgiKRw3!CP^B#~&?>UdU9D%uaS z$ahCE%K{Rxve7=D-|u+%-lzB^?4`5sp61Rs_M>Iw8Mi-Z-P~vQN;oV>1pO^*?)VX~ zB~g))B5=G>^V|?e@AA1|_1!A(TgR-Z$@s&}zE*yDzZunrO?7EHmdf|N;oIAI;^-wY z>t_|3=Z`vDuepO=faOF%%LJF(8LuXz%8f3e0o>H$AI6avjw=PdS@u06iYu(C!N2lk zBJtCQXin38n;ke*8PuaVL?gnxw@f5&<|J~h^5ehEw+w##3rRdfTG(;Si|x>V$rQdpAr}T-s>xHCwH3pS}oc##o6+`P(@@ zGcxgKO`~@eI=R6#P9(lEye1xcTs|qWTgnvnyuFX(^S>sk)nDF12J2=) ztk!^($-s6-lgjMpyQ@KJ4O@vuR~_2qRuXl)>;P<4e;gHj7WnaNY3oun z0l_6*gm3Y!QkT4O>LKsdpBs0RVxrN&I>sQtrbC&uh}l{B&H7QfmQQVUiy6>1^x?rp zGd{{o;C*SBh=yI5av^}>`#g&NeTjD3pSXvWPDPiJ2(OE`(8UalkhuFSiG&BPyG3CHZ{8E`#NJmzhgR&nq#8PSABt*Bq%39}+AzeL1)`nN>b3I^cVAL3t z_DooYeeRT@!h@VPb5&x{EL3@A2aun~V#jI0x&_$NQ6+Bf?t4;oCx*;*%rz$2NVaB8 zd)Z$X9xA$w5K0lpjE>VFQ@E+RdcA?v`L8xxZ9$TBzCi)UC^UjRm%ScZiC+8#TldJS^Oun`gU8g9$>vWVtQf0f>M1Er~(WD@Y>l=oG?)Z$ON5}le#?k2= zazBIV{flAEOQ=y@ZtlWK9k(C1>s7239|bO&r#tQy=BP(YT%ce3nL(juQJJ~mdCIea z!Y_`_%Nn`wbfeM03kr+t1FO6Jn4-IR^|qW#Abh~{CnleR^|!*f-`1+*$~V3be~keF zSK9VSIY`wdWi-}|R^BdZTm6ts#^f6fyUv3C7kP*Ypnr&c!EhD!2N)tgzTMRm6UE~X z{NrM`?h@QK01$*gVF?Q0YW_e2$KN@Xq`Lw=~l ziHQ|j=6M}%ytVxJnIR9(vUisiuO-bI1ET6cOf{Yq6)LFP?`)s97sn6($#v*)3O9^c zFp2P%2j!m=4bkNBahsW}5!OGZ+NQxI3#h}mJgPe#BZCo*3%LjA{Ws|Mw)Pa+C#xQi zpH)l`rZ0KavT*in3nU*!C#Ske*j++Pfq)Yrb0VrxgUA!V$-du{C5%G|hGwza^Ual% z4Ybf%ONSG1XmTAIVb{Z>3d?JU#u+3gj(g0LnBSDYeR-J_8j$NArI%{#2rQmjrcxMK z)FtPMP_Z^ob$MmN+^nyT%le1X;i=hE0kKVaf6#n4{bWTMzg^cQBBRRvnJE@ZyG zFuL{R=byqN_1|f){w*-ReHQ>7>@~gKLI(wxkp_al>XI-&U0<9~D2UjmKv9=L;609ZuA@GLk zPhHZpvc?)pSNk;@Ag?h0Ki5i$mcC`ukoH)d;ZK%=(xq|Cl8yYuJ?m~}w<%v9;NxlN|H)%>{+PD1r<+Mlx9mT)8s&ccXj(`JGi}l;M zX#YDMClvm@?{=O`#EhGD<81)$b;IaXr<-%yk3-hdZEekx?1K7Zy&zp| znCfr!kvE{b(x6h$22WtPtNU@PknCwbFlNZHXQ&{rv~LBk0Sk^<6#x6C{;CJFIHG=a z8zZ1Gb}J>+3b%Y>V*AjG3e)E)m_joi>kg;`&sg+PbKF-|(m4#%Vk#1@tpL&t)ZjcpF;=}&@h!xqBn zq}p!$fA8ZjP$Y&vat_B|QA|b?@^qF5Jvy)CDP_5&v3|gZr3+S=kavN?TrYZz?SG>% zjysv6x%q`ih`5h-RXjI@entfdFK;hCk}LTv1l3=@EPZy*M_bH{5W1l68`VxB-%Sh(_417vPCU}p@xutM6|F?1DzEeetZR$np{r3Ky6`gJyFFvxAq8|eeVGm&I{6OVzkMWE?<0@p+~bB%fXKHsvR0 z7x&TszGf-ADYo;61HOLYPNx6oojUXSp=E-7hf$!re(XXL7zz%rJ#WG^+LCQ}(>+bh z^Nta3ww7(M>`}r(us0R)|Ex}MTGTthY3GGzkNZfh<-gi%Mgy*7mTrBhHOsngC2YQV zN^T??eOGE_OoW|Ae^Q#AfnasNf4~elwo34^4%sK(@&k-j(Hr3*v(Pkb=nPGRLe0*3pqSxf9bjmrPJaq= z9yjATV)YE09jjYZd2saMN8MG}0uf@Zhp$5jz|yf>ivPKmwMqNzWa(92@0L40+TX>N zZ=xo|409H)0ZsiqIiZt>@98sry|-%aKO#ry4w*E`BTOWFE^H`Ra}C&VcQ)`pdyFMe zz;1zs8{8*{0fXpjTAhqU)QPQezvVWl?a|3Ql0N$B9`WaCdkEmba)8G)0|y9UBKSm= z=`M_t|KCuYxHI9XnX_kuYB9%V;YO8iJN1*bK_?r*)m}m4$D?xG+#(r3O2`--!n*Ua zV>|KiHj_^j=;qC#;bUU7?*$s028J2zKX(y7_wb%0QQArrCK`0t$TM&$N0lpe^)+E? zhf8Nc5SrLlk&`IGHPzleTo-5U-F>mVQC_}Y zb45@c)~xG}Kb*>%cIxAwC+LNn^SlZtw8NF?DzutXBQ2=n&&&ZRvFi8jY6<$I4(l&=Fi9$fr7Vt>FgkZMT|^$UA-!b4E)9R4gcbKnTyyqU=E@TobXN&f2ByA*1~}f_HtM*FHNa`~VsxG1Y_KO!~$i zlD8<*2(L-4iGF*Z`|kg=0P3|r+A3U!)2}MUYo%&sg2`g9{)rn@YoNOEXelu8(@4)T zo)j2kK1H)fw{;^WvK@AUhJTP0IS^W5DJqx1(3t9VdI*aFF0sPJ@(U`yw%117$7Bd=F=m zITIty`&MbfeC_iy(uMd`e0!@X^?IC5k0e-$v|wq26fM&z;|Cs!&9CGSso4H92f%H#W!>OG06zbLq}*3gpAgKjUBRv}znd)HW;dmLAu|A|YFDC=W~EJ{|-24IigO$+8E85||IcYfrauGGLJjylww+GZ_~K&w4lg`7 zz7!p;-hA}k|7M&7n@UoI#1;>Vs~4Q20p?1^Y$IfR@lU|Z1@fEKH?FXjiwFSAdbCrun0OHy~jvL6%cGfV&5Rn4R+|5;ibnBP5RsXS0YJ zcDEW4LYB)7|4IM{RDF&`z^U@>!#pt0Bl*bC@JVQeXN%CQD636wJA7#|`tJw#dpU2q z-?OPmiYp8)dhB#|9L_tBJBfmakQ@?QW3k6$4+^)cU}M;_iPH*hkw^M&*~xp}{>sSN zDG!Ua{KC>Z;}dHh#Gd<8h$Yj(2S>45l!L%Kg9Yv=3^`hV&!wnp6q|0^{d)RAfA^6? zwy;zti{`FE3*+#K;To{bN}%sWkn^^K<+p&jI74BmA}BFb0wvSMVX;6+AJ^Iq()0Z^ zTK+c3Yt_@N9Up_QR|8H;UF;%nN_YB3Mfb7@-eRb>HBn-N|8=of{i4wZ5_h^ zWw;uRX8C)^$|zrWvyGL1(Dxu0`fF=t^bxng3+D}C{qDAYZ)!Zr4mrwl2rnRG`%8ac zxj1&Xu~_gtWZDf{_-ILHotGUTb&3IN_K8Y4J|2GrIwX@($%&{#f?bbCB2!S3sJgJ8Br#2u~uK9BPiA0i$*tiHQ`711jJC02?9Ql<=J=uIlxd z!}%Bm{+%#rOzwFiT73AI=W6<~_Zy4Ju@@?Qz|8qT(vUqD=^LO3%KJzAjmUA$9J}m91HHU1roshU!yIt5)!cIKNsf#1@#$E$a3QLHDsq!hs zKV~II_0MnZ-rMgS+$GPnB$BKxvKB4ZHeVU-k0OA!ycLJ6*bGE=BpyWYI^ zkXF@Kxg+irA2{;ZA^RqkFyE3=GxT$8>pL;05#$1P+X}H|6`|IVk+YkJYd`a4O*gNW z_9@tE6sz0QOBAx)=<&@HqFyEN8mVpsBxawf<*?G;AG<>Gl>{QQ*TmlIc1AYq%GRt~ z?@nN&P*FTrXIR~=KY|%{f~@z`b@L?}X_wu*>W8?~>-<3hI-9V^RoaBukZVQ7#*)?6 z*7gNgII7#JC(Zf=Q+7-}qb@MQ39mwZf)P5I?FW$d#-08(-1$axMimqCrmlNs+OK{B z|Ds?{Fcc`=>1Xe<`wMTox*Qu2hU1CTg$X^^C35(Jeu>D)GHr3~9e+x$pG{En@4p4i zriq1zoazyF!&}TdcDLkjhFm`jWyz-qcW4yp{N$h>hxJMj9*Ux7Vi83T^@N_jKfG$K z!k3Sjm0ukx%!w}9uN}NY$A{@|Qu2Q=B6;p*Y=8Wi@G*tp>>S`Uz? zs_k|nI@&u^$$2R1D{r8{oEyOmBAezDmF7-k{rMk&U90;N%_36c)X&-vuC6MwHNR@q zcF10EIlhkCyorjX5+T5jA@})bn=7j%PDBo}5Sg0IJy_BOT9Fw*r{&LuaH02r6FGqs zH!gr(D}l*M<$Ad_YrK^+vI-&1B{OF_$QW_8^15%lCQP~Z!PQG=vaeI=F%{LpaEkAd z9rSR%T0_R8fOXKrY~|^qds>t(ts*=f$t-RbUF_qw+#-rq`Iyt%CgQJsJ}{~){?XAH zI3QzBs*?8ii%oZeA8gSNn!E8T1-szlSSLFk@fgMBANI^~H{~BlDB3cWGGrC;Y8VU` z@cu);>AREykjtZl(HHy3X~kjZ7|Wb9d^XN5RO3#Gf2%|v0Y=TIht!5Zm zRK3p6tVk?KiX%rj^#_<@b-kaHC8hsL8-BPREFeNWV%sjckb!?I9Ae^%a2DSy_diSQ z+fui9uWJADdv3^jqq^y=B+8YwQP|-6F^>m*Ahs#2@E)+T8SH&_FLEK^!`a7lO9nbh z{R_+SroGB_n{j}CYE;}^FDH|LtI0f~%nmUM{LzZxPgoXm+VrOKpn5xB-5oT9kA{m^ zVq=#anyQm%t8T%WDqqB#|Jg6z?SG9I0f5-FSMO1X?q(^|*Q%^0qjL_VTQpys{zT9; z_ZK%;NA#OkIJN`U6VRLPj)x(_@+*IG=e-gUF*byUROc*Q`h6Xgb>Mc8@&=&kSYQ|9 z?8-(p&#%z5X%x;Awb?XvTJciJo5htdJ>J#Y-Qr;0Q0OJ0kk|?P#2LVBm(?4_nR;F} zIt|frGQK+N`r*{&PvA;emKw(@Po5h!gfj1TMfp96qn%4*NE&}XzXN-d>|uZtOr0)o z2H`TliKvjJY(Ph>Q6`~GGq0;dLeA&R991}*pT?W{I^s5mu9i9KKpRD;f9;$D{;vC; z(ssP0tDYJ+b$T*#V0d|>uT;>lL^5TNot1r&lDj1AWvYyfrBXlbokqspl=- z)SbgMpaHv+@Dg+I`!v5C5!7bPI!E}{yPkGHpkU89sF!QK?WIsrREEPpkm=tMla^?> zSO1$8dHxcSk~Ewmw#83AHAWTyvoW<7P(-~ z*rj&7A1>TLtuEH4-3W;eJK<5wFwb zjrp_`=>x-}ls%1_FFxLe-npg-D4vRh7jdV+T-|egjo0qbRnLAq9Vw!%QuhH?usQ^A zC--DsMpe$D?&o4fgGU$JtL>K}d}S<-&DVTb*nl&HIWNa)BaRt%!WUQlYhmp?J{en$ zKE=C=m7C$FO;gb)0JQ(jc|m{9dnv$oX|s1N$)gn@zBQZ6-`s7EeritvPVW~m9%Z|S z%gF|Yjvr`hpX1lZ+YRpB6E{XH{ao7h%w3p{J?sf#JH9m3(;@-0CK07!rscKtJ4#YT zcZ(be;W<)CM;7bT>XnxvtN{~wy+d0ze1L=pvkA&O&&265 z$q=&H$CJNH2F4w7;84B9qT||f4R0KS{G;EKpTAn97LS6dQrRQDB|Rj8&9!X@?`(FU zNx`F4So*33{lc@3i#)OAq$4p$7g z{iXDBw;;Z!`xT9lf{5j{?G?XJ@GtiXzC3&XZ`on%mQT4(bUT%QA6rmr4>`MM`8ur$ zv%nBN`JsQ5&rjOVhl9!;*`vHfs5Tkc}_AbE}z}F&6ueFVFt6X1w8B-MA z8U7pku%09Unn%IiNV`X&a5~880Oa0_KOJ1`oLBRh%@!9rlrOi2n|hzzfgDDeu?9V8 z=(qL&!WG#0ZCueY@-(VSi9zM#{{h1jnL790u5$U|AjCy4))iaJR zjUO_ORATRKmv`rRepDN_8>NI7jLrwd8O!ejgIQNFo~ufZtsJotkO(0iS($S85-_jN z>O~DtEAG*xbe>($L3C(+Q5_Bpqz7A%2%Jf2#a7<~Hi&Y(>`!DekqU`yY{pcn`Bg>N zW1eIsXAo`gF;5=fekpHGQs-nw`^Brs3SPq{6D7i16|Tk|hpl=lLB=)}YA@1C`@$`n z7<=Uoc&$!MALzsILn&~X>|kC(cmuJ1;~tW^MbGK}8Lmc0qkVBj+@ z1shI#2NkgN%nTtY1NB>bh4Niz7(QM(3lnS>qw|i8J_(K{FiW>TJD;|8UXMB+L6tR4 z&2j=cnSmq_UMwZJ!6sWLJc(-_d|{C`F{Cjy4oT-iOe3KTCN%8`#TOt(5lnBv2AZ?( zfm4#Z8h~o?(x)#4JnlHM#K@4+AYRSrD?L6FY*_ngzNRzlZBVC6k#)kk`|{6A=~nD) z{Z_XJqKJ{%3WkL{-c%+EfmU^Tnd)Nw$Rsnu9 z|4JTTOD_)482Gx^hCW)M?b$~4omnGj{LZY&g{fLF&vTAmSCE^5j=g&Gy|+wq>zS>^m8~w*3Zo-aDTbbV>sNFP$Soh`)BvIsFrYh)04G^ zatjY!G$DMC2!*b+k}A=0f>PNRVRUrBp)8`LoWr4>$f3-L1Mv*ILk9F9wHc^NM869+ zae)N3?rz<`;KkszCuQk74K3J5C++)R7ry1!4 zeVg`&+jo|v$A<2JOb7nnN{pjwP0@3!J9%gwr63^s{B;G?WPK+7Z;(T1x$H8Q}$0)Sf8{QTRY=IOB2 zLo_C?tD`HdvW1d(RW>>D?OmzK1{T1 zF!|ehnJl+7xJ^zIV09uAC^&yY4b(R~a4$LVV~fd%z9g{y{dy7CT88(4q{fscfvtUf zc5bH$h$df33B_77MW!wOA+t}iID=rg1}#v9`+;T1H_=u2?rjiU{+5>PB95 zQetfO4De3e!Szi&|66wd!~vw^p=HMyITfvHV!1lAOeY& zBh+2N<_prBPlYCL!QT%^hR&g0_EBtKIAz`Jm{Qyr_F>e^1zB6pgym2rTTY&A6gn6V zrPZ_xzlqxwEOuKbJ`HDU#4jJYZPqMkMx?yqCjsvk4W_Bi3Mhj3X72f0v9OdNU2nh! z`nGmwKE*sIh?tfT%?awsQ^}kyK%C=bXCXRj%WmHWlbH>frIf8=4-*vU;5(Gbn8;am z#F78Z0(ta)QGK&9=S&T1BwWRf6hmfgQ@a0Pzcy*ZYK)M-Khb{_ogZ;O?2i)#`N`+- zo`Jtz4lj7Xs{|g-;Pphx5txr*Jiu+8(t}Ghs^}tsQCof5-6p(^BsiOPx=@1mC{$ z$6aRWz1_0Cck-Vvi+j#7Se~tR=6v3IzN&ygC;t%%LM6kGR=5Eu)FrXpw+-QPUlyu9 zS9EM;p-0S6I9XMOb{vuSv~R%zt&>;8#i&9sZ-UoAJYUBQb?BON1xXwkZ-?jgl_oK`ALF$Gf!9} zil8)?-kH^JP~?+87q2v5yjkhwIn}t^YO~pT&G+X@GHfiyXFD#T>?^Ffv_~@|$F0_f z+mv~oW7=A}2iaqt*s&~jZBlkO^D^r)X~Ag7Ziyo+?JQ|2xus|TEN^uSGWUam#Y1d0iGrfkEvZtKTwmgT8ydfEg7j+wuki^)i03ypv^GtJ(Y zD8E}0*6#Ev5Qpmf6xjj9IaQej|G~PrLkH{c$-rEZ?~rkC5>6#;gCac75 z^>OIq07Y2D6Y6{-ZDP8&j@X(yYE@vNaCAd2N1fe#@;HcYn-MqD|qsGaqv98cb z(~=|A;K}<}ryo1P#OIE@MYw)dlzxQRw}EwMO0ql#Z5h0LV@wGv$L~^%BYYh z(+;rmaAUmjP~sfW+&m!ATuDa@vGD%JQ2FWS_+>(9p8|xxQbs(zy>Y)~|EAaX8D@U< zDCyB+QJ$+BTk&jCNHzUH;t?YG1a~E+*`blsAGM$50GvJS0U%pyM$H@jtR5oPkPiI8~SG6`i+sTL! zkvqFLW)%h^6y_f|-MA1G#Xq|@UfX580H`iJUrYmAEKgBY`FA#ne{@MA?Mx(kLkiD3StF5&{xSH%NDvf`D`{xs-H^fOL0B=dwt5h=AnMAl)73 zS>E$~=lo`wXJ+ocuWM$i%XYOzXK8)G`-#^HfcOBynx>choy6LnSzude$3ZW^Iy2iU z`1EM11e|=+KU1gK_&pWuTqaAGe^+-9fO6<=S;iE5 zo5=sdBuL*y}s$N)8MXCtB zi#o@n!SzXo-8QxN#6iy^5bz{TOAf%a1l}*@sgWnTF_t4sOMEABb%e7SE! zuEi9B^MLZPn*ff!|8Hx%iOwY&F~4oZ^7)PfEC67 zXgLO=S|f_Xh2Als&<>pdW{Mb0r&-TJcv%_>$}9&uo}Pdr{!#-_M#00+Lv0C~`Bg)G#SPsgw=!ZMmq)*U(B718!}H$|e?NW8bhZ(X z(e!3=iRh~+0ff}^)0gAKILV>$izS|J;jSt%+ zsH8O20xoqU-W^M7f!8HN^PNB@u^TRFzs%*go_h-hh?4+hBxPuOc7X(UX?S?)bxwQ} zy#G`R(Wj(QH3VH#XMN*4O8QR~)4yJ21Bb~QdL&iLY%iUo;_|2A?4i1_JZ@(W^Du)hhQch*k3z`+X}|; zkiBFLwUsnIFy($@)3W~<;*vlKLzydoII{1%4l;>`uZUA1vT=u@62CIX(1Gjs&9=CD zcL(w8{}c18IEvK`2XiwPJVS)wSO6NX-;DMjcoYbqA}%f*}U$F;Kw zU82SFeRG}~Y7^LEzqLy>Gw?{z1>W}B#e0Nr{t|{N57&)c*#eqpOPZRjpK#FypI_l_ zS0^yvCtp9T`+5=?=k9qIT=C+uhe`~PWCj4LTV}6Ko5yl&bbUky-a<_w;*d$4%8)jS zMW9dDyvX`HtW7iC7Q#(0>F<+=Mu3c}&!=W!Nc)|;2#P|bnc+^F-4HLCTMj|`>g9}G z>#xY2)ldO!)sLDbR;Pc)5E>-0V98^I>GG-n1RxZDKo}3&@A0Oi7(Te|87qIMIwGEZRbKJU}yTvW~@0le|glAy1 z+Y!^D*w$UtK+4yJj5#}M31s$X4Bt6=UUJ>n(&zfBuOCnuV0T-UZkdr95l>1rtTA;*yR~2d}X%wJD49?@tdGe!)P5$5B$Kq z^>~Sw`IrFa9Eq{h&fD`cvkureR3miJrsYXM;;=A4oIl2sjG7!+4@*Kvj({aed{8{N zBpWDTeQ0&*+@6^8#x+C8+!XJadJ9?I+_*LZt{cv}1x7NBWMXHwGyCTV=K5tcYQ^MdSXt(0lBTGe{;6I3cYo1PLizDgXV zaJRr(tn2sNf+_tjcb`_bofi)UlK#C3@9{0}Fj!)NP5j^lo3U(7DS%Kef3*J!Z$tf2 zy{xs&Zt#{hbh1kU5=N=&31AqevYcrIIXHXHT(PL?149iRcF|$=IMq4_@d&xmslMK_@MG2;bnBr1MY5QHS#ih zduhP_A)9PavS@xgRRp=Kw$wJ7Azm%X6;v+WN2`FGyo{zs@~jtv$uUWS(yCR&@CGV7#))eyxajAAIXs4?!1F$VOdgP2|ScxHiG zDy4}cOZ<+JTd6Vsm(`8k@!~{FXM#>n*YoOEl{sAH1CIX^wpW<>=-_NbLrd6a$%xe3& zTp`8&f@}Ye#}U2ZP*W1pS=|uhgaDZ75$+2NerY-q^I*&B3LS~mz*!SCJ>Z+9Ni(XpFj3aW@-^)~U z8ywk%iNgT0Fj!g1XWzHAq(a=wUD?qtizUntZ#p>tRowoXJDKKPtE-$cTMd*7wT$E_ zK!eWwKEz+b{L@Pk!e81(>%eBTA<0WF()2g*L;?So2hn>R=Brz=wm*L*Y@#^KUs`Ri zP| zckrx0@ar~VCRp+S(`XbUEl@8tSJmsX46y6~J<1(9hTNm@$hH^D;3wc9nxi6kRSznG z&#NER&Uya~5u1c?3_k>$mb<)Vu2}mfbQ3yx-?QHa!jlnZKX$C6ZS8y-Li0*&y=Wxt zwt_$H@63VDYFv>phIjh`dBJH@T$i{i6!o+Mxbc%=PMgJh_CNvfU<1=+7w;?Nxq1Ht z8qxi1j^ZWFpRHaN4#{!{HL)vB+HxP+<@>9s{)%dX2 zTI~27zrj5Qrnn2;P)8%S+aNMb^j@Lm96f6*eLAsOi|D^Ia9u_jNb5$t0MGdtQi3~< zD#IM~83}!<(<6;`zy(e`b$0!}9wlD>jh1dD)TtUM53ABE1d;|C)@rt?N@j{SHE`YG zzgqgfANqE_XJQ|R{AnpsJ`;!mWPS8DJ+fy(_7=+Qsd&aAPgz|&kCls%7J<3nCYAcbmRtirZ(3~2iq^A+VL|0bnE3raDKB`4=AL2hyWOJ!;_J5-J%VepG zHTM|Mo|o`EX5PD)Io-1=Q64XJhgx8N{8wEq90NWOBtOZL(`r|DhlTUy@EKWbD8QUa zzA0t{K}$eI9p+L(y@{lqr~hjDI=%OjfygdJ_qlF!3>&m)c56ncI&iNFeJCgL>j;O2%qxX2f0@$$QpG(KZUyZe@SkwQPJjTd zqfz3jTfAZJQHK2n>-gWStak<6aX{>Pzec}1ZjOi(DFDL{>pPaJtH$YgROzTk=jIRe z&$Netkz%He?0yk_H?iOo2iJ}C6+cNNf3vc{U}lWsHfnE8%==9ic{=~wcu;p=QmG0b zq=t6LN>Tb^)FYKV8p`9{zt8Od;<}B#qp5s&oN6P%z5$TTCLe@WgRdrhM<<77)5mp_R@ZgmJt%bQ5v?xG@9NBIZouW#JFCi+jkv9 z&v94vK!UmYCkd+_23BWlva=>bf8slDuVm{aQx{|>Dv6q&*{F!WcHfF2zubNnQ5`TL zIO`EUER6^(=Ut!h9Ksnz-ZsFNQf|>uTG6kYnR(Y+Y+dudh-68Bm5F%9${zhretFru zzJL8MDouHYLn8xu^dTH6LvGO-FFJp;TBPRsg)zVv5Ds=B|{o)MQ)N$#C5P#q5eB zQ`N1r(<*AJ?s3xMgec#CHCxg!PGYsT>9#n7wx67BTI)&9~cVvxB$8Rs!wtrWK|okG`${yey8A}uX-ul$|rwfzHB zQxh)Rz;cQGeR_DF2L(uV>m~Xjgq92MmSMXn*1?M75`O3BW~&pBjXoFK6l6X;{~QR@ z^T|#>i{O*?(W+xz;t9(-2@dDTyeJu~#wMXwW#k3h^!R)JvO%B3Zy8ur=Gd~>Pu z?ljtC7O`=nqDMB@Ge-;idxzae} z_8aL>7Ms>n-89JJ4h7H1!du8kR@EvQ?(71W*C>xhWmq=C`q+u?{`{=x4OA#fbHTQh zIe=+ykysYk$3T*QK8jh0h6?Eldk_|TCJ;^~zI+)J-I)!T@95VKMVW{`nx$tZw6f~V zGh_DAGbz{k>gmJD-A_KoW_5Hjcag7~Nih`0M=>BFg53P`18_8-msbiDWle(dg{IkA z{eIT%lgoD{4@p*ET1|FFc1)kok1$1LWuoJ0t$!wHdUH0i-EH7(1&)h;U?NzYzNiBN z`A(GAt7+W2Hh~8*^W_)z=FwYW9A!fWU>*k&>Ncpl_=a8$Zgn91!Hy2X@Kc+aG#%7ceHJ?Z&B~4s3J*}NIGUGT!P1D4 zDMDdxFq_i@hyHA=19@s4z`(-izrn5+&7&_a-}O<33!}3rGLR+V*F6|)Cw`plSevrD zGP((NEg#AqJ}$l@pPbe}W7n_3KD?b8bwQZR^_%Zo*NIYlSKgEo8EBs!C|}fM4As#O z^2&&Syp3OCH(~3_ql9kfV)Z(ku~>p%M9-5AD$i|66Y?^9mWZl+$8s33yC(ln%?zRBG)A{Nb0XR2N!)0FL6}1|qjE$w z#wE&6SF`wVCCujP6ez0WRMs)MxG6p3TY@GkcjP!OG4ysI`|aFIyM2-ae|BmvW+4!5 z^mKNqZLuv+r?;|CyMAOMv<$^)-tCwBKd(Q7x2hk{r&dFN90Z1nCswzl-m&ZH(mD`D zr$+=SIi#V1Yw4gBE!|e|q+QCwa3w96t}RQ<)cV z&$g3F;Be|Ro^im0_3(GB_3#ahHkmMMw_sc70E{D23oX577cDeDi}fEYAb;)~!WMAdzbD_Q|qGugee1p9M$+r`|tv~ib7l6pR-U1P}O|&20{8fsC z+`GezVqAS}xKyW6a9rXo65~IxF36@Ol&r0oXc2AHRQKy|pJAV#Q^cmk*f0k zAYUiiqXoY^PwBOyXA1L3D_`ijXwS}V6Y-mu=`kB!8m{vrjEvfr%EhU?^?^6H^nps= zJB4~KP3}Y5Zn@u!t{anwRb|1LZ8gWdC1)GE3|WIQT8fw$TOH!N)j9do=2_X5#woDo z#c1gep&3>8jlAcl-c#EK3rO#x_B363m|VMa{hesZM{g}I@;Dx;eT}1gV}9Vc!kE{{ zjY0p>E!$6;`oP#LrqI<;XZK8{(H6j(KaWwJK7fXVWQ?0?k97JMW}T)Y+9MvD6Y8oQ znsLT;+`KsUa<$x5IC1X4tLwpa(0<(#>yY0W2l+AS7Yo<$xsorSF!}K5abU6z*WmVEYNB@|(kZR#=cg07G231=J zNOi7?&Kl@(?F022=rQ;PmCQ~v717f^*d6w23kk;CCk|`Gc67q3QtWxcZiWZ0MKKi9 ztR9mcvC1v$UVDS~cek@F@RZ7&oRvBS`oNN!I#l1m`3r{P%%I&F=$2x+1 zMiMuL;*bm?rS`$LM3S7z66Im^ksjluSf2)5a~)DN z{|)nXdvIcq@3yYkg{nJYdlU-@<1%m4oytc3Jd;+{fl~ebSXI-Gkc}?v@A9A>e#&jv6D{=C?rfb6fsdtO-rjVbd^|!g}HcnRZ)Oq9s;?rx2 zItya~{&Rng^iI*1!UEnq*CM0u%idc0(gVuRPxCZR>iG{&1hkJqFzn(Vdq39`?>ePi z_S*>tX|C-j8+`FXQc>OJdrUbRxIT6$X#Dz^ClaFTS>VcK&3*EMYQu7=X8WI))Zxdk zIt%cZ97rW>f=kJM~Q#F^PaWg+$x8y#xE zDgD`Xuh=?Z6MLx;&Tpf++Mu-F?h=NR;(qCGw3&KNWov92tV|j1+kW4e4m_)cd4BDc z-Ip!-NJ?EueU{^ZD`horR;E|$kMBsYvyyuljftQ$C@8g6-PT4)`M_8on~y`WD8H6) zkmA~vG1#7e_N5l+xAv+hR=E^B)M1`EZ1LYY7tE3~T42x#1-S}AmiU0G;bsYIlw2R9>9BRfDT#%e3If#Tei*Xn7?EgP=)PkLiw1Yk*PV z(^Q`6m5N&`Y;+$~CcX0Dm}h|oVI+rRFVkr%?FtxQx{Jg_Xew{0$4Al02-mIY^xM{% z*#QZrQS_GXEYsQ2B=fcX9}l^#BK)^n@1v?KX-4_QRR#WNw#uDd5_Nn5%8FF$&M*@E z%Hj+B>h4bzKuuyAOs|018s5+f!!s)461_jNk}tamJHSNo{o50bhfcY3H?0(-G{gRu zpR~||ND?UO>)MShM=gU{X>-AyJLzCGe3Yy7+?ni6QrCdy;;YE%5XY~e6TyRbUw7#$ zSI4uN@z3}~zlMb9{5c82p`;~w>wx)v1Sw;00%OitADX~#U?UD2PZ zH_- zexzaXGYXUHK+Sjk`~FBYRP!t)J2B!A@_qOFuD+aFN=KHwWIL2>&i@FKys6-+7+s~AiU&=b(e}qFBV4vVYm?7JM^!%s=ao$mhSU_Q zHkyW=N6j1lH43HfCFzuVf%M3Y6Ttw2WY67|h3bW4sjrn!j~#6D{x$FK4>dtYQ<>gB zw(V60F;Y(c zkT7$~YdYJGHZ;urs=Cnh8Ilr+n-s$IUfz9Bx`JpyDfSHK7zb|3Q>L4~jb0hPld=l| zmoNlItmzDWWaV;jnUQr~`wjSHUgn!X-nH`6RXrb@fVt#G>4ZwZGPTKx3Dzhf;46pG z*~`b!6H1e_f9Xs*$Yl<+^5%XGIC1dixXTOm{@??skVqZJyJ)db<6T@+LzTue{hdl z+zc{%rOFtbD}YBWXyFJST(9Re=a$lNxtNt>dSgDliGkAuIi6^}O`-PS z77{29U1)b=bByBsw-UMlV&W6=xNg^fYdHZT46t?>Wb>eZX#a{PEN!pLwo<=-2g2 zhgbpFm+N*GJG!GP0*X)3o^5{bPWL8-jAj;(BKXkLA1IO5U$+DK9R;t0sx2Q!>h<5R z_d7d7g0s%066E|WoE(559H15l#H3qZz@k(9Kz@DM#CIlo`GA1 z3e5r0wbXC-wi7ulLf{`a`l_OO4eTTfvqsvQ%qZEdwv&`x1xoi(+p-26i_IkX76@5y zB~k}vnVSx)G^&?PsW(wKTU^639R?8(Ya_rq5(84Bu)$9pq+Qw(? z^5%hp`feMaH|@tkXaxrKyTk09`$=~rsc9F{vFsS|U@gBds_^u|HCm%JgD*ax>?FwN zFc|a);bz+%W?A9Z#!5SzljfbJF`CBez}x94s!Q2ZiNTW+WoNgI!Z8ur${R-UxLh)A z|9RN8F+eOKW@)>$qKwZD&Fgq%Mvrq(7PYH>Bw5+D7N>>>68We)Of@cd-HOAaq7axF zS6?FL&2C}e66}+JP;Njh1mCThQfDQ2l{Yk3==Z4_Q|7`dyR%8~Hg#U%yBQ6LxQhv8ZG8bBsy8g^yn5u>74(3}>U zubO8Q+zau-GZ?-Ye^2`Ub`g;yak{fjWzTVCn!=g=E(|a%;yX}yzio*YtF;;eCKx7M zx_+ap7&6a4;Vh&b>+;R%6L#6w_QUZjIZ2B9zk)w=SCLc( zIt!^PSLOW8>~cQO>_@KGa(>ZX4j2NOGLdYE+&cYScswyucnzHQ`*n(((?>>fU%e&z zi#itzETwaS1PLck|CX5UaJ8InS2-s>u#0ob(1 zqfHx3jn)hYYg0wE(#ui2Gn5pmiBfi)&*<$gXK*e>n6P1N=O|&S>>xnNCm>3zr(3~jLX$P$&HB1vD*HIL_iY9iw7beatAzsuJVQ+ zBFFl_9>B~O(HAF3NWj|RPEDR({Qa&%;GjY8`P}?${XfaE8&yM9uF7H)ux;eva{%#o z0xAb(6qxLr+|Nhr-iR1uBxRwOu=<;IT;O(uYqlVm9W)l-{ye&ufJ{s3L1*jkfLvEl zk-4xoYOP+3m(Z8Aku4#z2P?^Dm#NEwTZ`zX^%WB@q486V!f)fbwLpT=Z-CN)ua)w0}|3Y$pGZOxoL68DqCy^F*bQR1_PR&ir z`~afW@&7PW2yhj+2G(xPtGR*Vjkol`caXXez$MhK;nz5U^-wJ zdgFY(_yntcHTo4g(T9|ZdDFv_t|6`R{vJ#qVHVIhC5(<-7)jAFr@N^^&|CoUjPONS znzt#~LZ&?DEZT`O3+qAB>?1$)s3(pjvRe)KZo(5|=_YP&tWi8t<=?DP7eKC(TjY)Y z^YQ=R6#low606Rn-HNR^6o#L-*t&q6Twfis{fh6k5?7D53N(&3GkzqO^t@DGsh>}| z4`h8vhy->f+|v%cfkX`IJYZS>V0Si#ZN0<(w2n>W?H>3QVmVa*E$7=C|L6YEPgB1f z3CJc{d+Q4&G$EjH%k6tE*QcqV4}x$ zeo|St{A%0^v3H4w>&k^Rpq$pwE<_;0+z-N9`MU&l&UA`RuV)zNM1PTPUzZ-2pC<;N zK{mO7C|J+vgmh>o+#oPL#qFb^DO_Bl4B&GIXaMNNMI@ib!8t;@k%U zQTwaW}??U;sHb#UQ9fp{o?3##UlrWL$t&GPz(W?3VSp+1t;lsVuwe-TDm7ML#PpxW%UY@27i~DDAJ4%T;K9q$ zSQ@Cx%*`z6HU^7eYu?|rcxKf(nn(eQJJKuqN!ZtM2pUDMGYev{^xW1mzue>FxytQ9 z(X0c5YoXl8vpBzj8>DJ+@>5gMmMk#l+ftIprpKKd1V}iur)S$(I>=2h-VwX2N&5Z; z@C)SwD$1M*Yj zX72_kX@)+lV73j3UG8OF**Xha0J{5fg7$#_?WlD*BAN9h0S-+4_Uk57@3(Y?zmRJZ zcDZ#|2h=2{YaA6>aBXsN+G=Nop~!qXXE$IQof@uk`c%x$mG(Q z6Y{|A;zM=9FIddR5ujvD&f8}$;R4Nd&IaR2eo4W4LV*w_+G~9&UKBX0k4Bt&Gp~KK z%ThCdnL^k*@Nnjxi01tpYBDc$I;kJ}#by@rF< zit;OpTqgWSzRRtui5+}e!kOJCro-#VZ0rD z)oUAqQsDo)H`^01;kibP3Cz?>+31rfUL1t@0bf|j=M+Y){C z^Tn}c_1Qasew;CiL|g_jWjEoUxx*t@d)iZYjp zs-&4P&K-e!{!54&MG2h{dVyBlM<4Q+CdPRR5st2=b(@T>iJ?f1>a*^7jx_Uepjv*>nOsPWRxz-Ai4;ox{o>w=7}V80k)9uqnNwO=1c+ zO%B08=kY{t>W%8+f^5zN%VqtF>QX)t2<=x4OVci(cHs<#zwTjCuJbx9{f#xY?xIZE zXjW)S0%K2gjYg$$X>$<8C^RI(%ytv7&qck@zf0|cS(h@g+V-PAIMIeNNV($uv_a$; zvjR0T;SS67^`sjh0h$an$bt0AxnS_#p=3bL?0(pZpJ9k%F=1R-cnLt$S_YG-3tHX% z6!<<-=cdRaL3hv6adTt$mhwP`n(q`;YjWP5LDZJ+9$6~KuJPY$ zabnL+Lq%Q+N_7dpOqO%e9a_z84dYd;W>(A##ERQ1D7|I=+z|9dKsHIk!`V8V?^$wq zgw(n{b2syGQuSi|?UFzZCqD{(qtrz!RTsI?5A8~dJ#Ubx1+z9A&kiw zaXkjy_0Cby*J`uz=_wk_Ul!%`XSw=TR!kro9kpR`!uU-CED6Oz%`n+6Y>^}707RmI zYwSb~a1`C0CW1A`MXu8=7y08fTVcz72~DmtF0X!CoWb43vydMt*XYfNiY@b(QhF zZX8XvTQ5WYET;lQKeY`6(Jy8e>~~y$c9B3z2e-D>I_2m!4SeD(bT)$S@jQWy!Dagy z_;&)thwoOrQ>$<#Pe#KO>J_Ik6kI!0WL`Vhm%E?c6D>IHhEQ7-~66 zYYIlRx7g#58!bMaLm%{_%isn z4XChseW~#lurdi_RyEu`>9mDf>Qin%aBYS8&7C}iyc=?MLy%=1Xpb;8uIkCYq!cm7 zV0*fY23GNEv$}uYDo13sj*M{Ya#_&?`$pHWr&*(`%TH5?6L?YM=|%2)jwg7|EA;Tr zvLBN_)x+DAzvf?0{1+y&`z=n}7J-l%!;Fb?AKjg5STr#B%MZM()8>cpjGb|M zb%&KS*S6n5sXJ{0|9_{oT4DLK~Z8<@<{z`!ce{A>sT!l zhC0)=JW>+@7p*yb+Z_VRXr*!#-}Yelhmlk#T!pL1^00SM$o8n$i~WHYAo40p%uw zQ}-MpwgRRhNfstsWw=584q9_tWgoc-!&eK{eTO-o?o8w7 zbGs5|rV9SVB^kAP)q;8Sc6xhmT6N6Vp8;k)P@Ph6=2O3Mt^FInE}bQ_)D_8*<^ zZ><0SN+d}3i*#zJQ33Ns%kmpzxw=Dj*va<@7Jp~4w=`Jk@5)Z;w27>e46nHiy19EM~@D0ELyR zUA%8y0_Rm(0fDm#^$xkiFz7OE_jNr&#hDm^nv_K6YiHpE>$o#~zZLTtZV{NJqk#yr zT3nGv60La7YLkp*?qZJ=1iMP_j-)6bj)G}7QjE(Nj2LOHf-{_c0zfb<$0BsQ+rC2Y zFdXg?$%F>J&#zdTf9*hVSovCeKu&KgvA_9?G(a{sDrq_@m`dE@6(jMNM9{d&7yk78 z5U=t4jg#+GV}R27K_%%r{WHs)i_ z4H`4w<)nQDfs__1Q6PAgsBy>Z(7hGHDq@%Sg`&?)suT$gIvv&3!1m&l7SYo{=3Czm zAG5YKCi#f>4)q6?AXAQr4FWc$1Dy+U@^BamefH<~6dcG9m0NZsfU&W7`Wou!2ddns!b-V#=jeWd4@Q@#`Pz=sk8 z9KSwv#uKlHK`o4j#TKi*=6ZO91Jf8A^HU(e^_Mt-Z4D>V;<{ufH4JycG8i0Ei@cH7 z48-oE1~lu^M>9Dc?q-`WpO(zjF}7rRBKPqU`H08r1I+5yI5Unr1k3ZArxLWiaDL$s%G}2sC0c3c|CJN0iE{xLC_J69U9WUc#l4!%}*P5e612gT1(u!yEL8|Ny z`Uuv)PvUJJe_CkW=xnY3+8lc`aH^wdI|gkZAT`JA)Z7~aE((tiyj>OH+ln~8f&Vs! zln!dOTqzNE`2|HA=mII0v>X%d_>CC4@>Mx!sUK+Y5lzn6vSJ5keCql^$i>~d=AaDi zX7(0XXg#d@xF?Cyn>9)wHs`LD9^}#nUHFtALn((IRry$XXTbN7r z@+%nEm916YznTn&hNptuA9#J<1X*UE+aeN%o=7ZB5Z1G-=8oL8O)1ufRi>A5#Q|;O z7x@bSZl(70_D}ynKcd&P)iOjdh5GB25nhy)l7w~j(XTe+`XU(Z*qiuy){`_!zFmsm zPn25|?c@MHHOi!IPXV1-{Qa}d!h?!f4^zWJNC>=CLGEaWMDMPt9K9!&VpwM4DGC|#c({cFf~^j--1fL zQyx#|%$MjB8K6d5Tai6fS|=%cq|XtNNSMI3e0~+}dnp=p7UXF2euNG2TOLg#*Ew8fI5vRChi zb=gX2lJ>U8UAcghVRmr$FWJvuJ?IQuyLCMv^a0NDAkKphe>&NnNjNE4pL61DXLy^Y zp}o$!y0Sl_GgRzTX2q7?MZB0huyR_axcNN7Q(%!#841R6 z+*!ZWUbgInW`MUd-Ho084;G=d46Lo2cGg>gq zX`t>s=$oMV+-yoNzN6W0`|Nf0ZN;~_& zWhs!+*l&n{dPlS^(ay;IIeP1x*_W_M=@l79PGx_9t#Iy7VS;i4)L}1(?^O8#s+a#9 zg0Z&k;&z86$t8wcL!U@mzFTlvw!uBeMZf)_^o>uFU9=I@+00QM!-8Yr%uZ^Pkmji6 z**>28@x?Hq4X!zS`&eL8)QG0O$&*c<`EjHdFM4pmOH_5WWZzRtAmevVIM z)~n7@4;)C$-6Yv|M|g<#BRh#VRfA~-`inpUX*jBP%_l30@;gC~v`%E>@}r`S^IHQA zfR~^xVAQ=S1wvla6C@vfPWO(|VnHJF4XL`$%)k;H zHGRfIf>phdy9Gyp`nPaufVW;2Q>O)^X!aK9* zWEz|@JE*yu9R(q$t@=M#Y0Dta53qak9rD5j37tR#d0A7#UVB{scfV8&(G67oVFJ

zpP;bSQUFWN#??f6&ZuMlCEzumY50MXbuaZ{uQv;N*$Zk$t2Kl)8*UDtrCU_%1x= zf~0mrO}To6$E6JAiRAjT@e%h=yZm%C*QyN}EX|D%4U~VZIoA?APhcA((%roJBZSKy zx#6Xc=#JSK7h*jJx2A9uXm@XfyBbMt!u_FjT!&;0Y>=R&Nz7Io`gMgO_n2Jo*AW>d zV`_$^mx&9}dx2#&B)#e~={o>Cj^CB7zpTFEn1-cnZ|@ZQcqk{Ti%MmE4oHe}N(9-q zmXUFL9*@XWj40Mft(o(Fb0=Tmt4xez#NT&LaQ}F^NZ;+3R8RUx-hp^(&`UqdX zI9mhG^E_li1!7`KD=tLtuhWv>=gV{c6Gv86=_(DyMkjD6t(MJBuC-KoMhwMb46jxE zjUobF#dsf2QszX^h5J39V3zKCwP6v;izXDTCv2f8C@6-AcUZqXzQd|-Bd=fWThp2u zgBc^kc_IY&$eeh+Ib_kcdE-+|VT6@#%J(wtqBA_yCH-o)FPVrxxGy%YU(#{B$9n!Q zAg7he>~A{X3z@h(Ze5L2r?q+f*4|+y`!8j}pHEzfILDlLM}4}6VBs||(rK?DFzrR2 zzyb2ZqO~oRt_$;Jx=ALt>1*=`RoM*&)Y|t`f8rZZe;xEBo7=&8TNfjh`Mtevx4L@4 zyOmAZ8zkUGyjy-wVN3{aT>7%z=$q$99|D(AwRP9K^V^I58e+3W;3!#B&)?LWc>kYhyg`17rQy*yWRK` z;H>|G!!G+vDx7o)vp$^)OF+SNS#SS~d>#r+>Qnzsa5h zlkHsGKm1eyX*1u5w?n+)Y{omZ_s~8{ZfwL`TwZMDx+O>xxHzByv1s^Ep|5wxd04%l zOJ!ix3~i=x-{vO6Qd$l)v27BX1+LkQ2yIPCe#jKaL~i&$wY_&d)&2iJZiHkdl}#aJ z?>#D;kgQ~9B^-3j?CgCZWE`T*%HCVDIYhR@LD{oY_V_+ur|bQ>Znxk6zvu1x4vjfjX@X_&ANOOU*2Aj%0OZDLM zl`s{%FeLbm}64XXcnQ8nX6x`bVHCn+As*cfjt=Ad%eImh72$Dr1=?W+bVtUQ*hL_KJ z=Qzm&4p7A;Nbqj6MN@|9GZ*6$SC|!;_7`1=OeGaYvX0U8sm@8{uq!C%p*~7Nj*tAp zQ@Ng!P7SR{4O$Ujk0|o2nU)s4k@vHjnFIj}G94TJOjOKTm9lso^wsb|H)TSJJ*=?4 z6X7tp)eU~6tFQvznc(pFRlA&W)xaJRT(|4umKzgo19VVpxyxb2B&hnifs(mjybYkt zhHg=u-FjergU-~n#ENqcCPH;0mKkyQx}ACOlj~xPQYrSO%2|GW!dAZ&WA9n&Wf_@x z+uE%@6mly9B|AoJmU*4FE{IRtBVhczL;h@TER>XMR>>OIilQ1i+v>yos`GM=k;4g! zAAGVtW>?-$J4mBB0tJWud^IAOyD60v5fQ_%WNUNa7~eHV+|>EbM0Z8i89Os0w=jKl zRJZT-O*vs0_s2q6X*V`7H+>Q}KYi--;lShBR_kSRd)TLA%hiW0Bf=kHMc#rss!bHn z?_SR(>KmD&Qm_Lp`W8wzzizhozLvE>o|n&j8;l)`4@0skWJ6blswzI;bgy3&9}uz1 zX*|1?T;jkbyO$^)iUgf&WDN}xcy~6RTyS=sppKry6$Wi`gduFWSR zt~ca)YQ5{3*duQS?jKdYrcn?Q2dLcC(7^2AT(4o9&y!9tAcRxCI)( zSNVmS1>B`CNl7U{J#V8$3{aKtsw1FL(ud|ICMn3Qni5w3FEP#yM~i}U{(A7yQ#%Ef zqy<)5slbSyN8=+;bX&E(ZI52j8wVTfQzG<>-1fOTFEwDyWD`I~AAIgc2_8)UxKYK%_(w4IGp3Fz_kkg47NLj@1ENw`1%epj*SuD9=A z*VoaUO3+{AX4*rM^8_86UQHz*swwH&)90(I(ht8Z5{k#!8yR5qG*&Z(HbL7qP~t@i z66TIkTJYMWKnffsa2pXle9bYHv$DrF{e$prJj`&AqP4|Cb^gf$v`0h6;3aY;&-~~J z)gc*6W!<@@>>5$ziN>mckS~_Nn7M@CKSo%QS4^5wFIhe4rsd(On4p9cA5Jiaof5=( z>-}6-LQ{tV>12AHV_bRs=mX5*Cgo(4m%GYJLvXG{eX1@eIYPx+;N?nBkE7Ufw!57I zVDH;$Msv@MZ*(K#YkA~LB?0fzLcEuNt1AR=U(jKYwMg-@gLhZ)hNxwrcPX{IrO9HV2 z4QsE**r(%W{M)$Jg)<(gtdYbu<2=lKY2x9CmEX`bLZ{dX@w*moWp*p9ta6uQE?sqE zuyV#qT%B_Vzb|o>b3cS4gYq!rXvy|GZusv1XCB4dad8AviLR%Q zR(bq-(Q;@7zzhsPef=Gz{E({YX>-Y5aG6-|@^;F3|NE}SENKik@AU8XVV(cY=7n9= zG3rNOe^-#7fRH(pl!mzj>@9|7a#Y#(*?))7gk9wsY_w}X9Iks;yDEs}61JCNswy3U+Jq%jBteH}ZrXC@v$E|D7<52s-=^^KY`e zbZ8Il1RG7ypd_G&cJ7g@Mi@9Z8O^p0uh^uk) z$roC~>!CnF{~dod@rYetAHP4ophjTwV5hT$yj#{IgL#n?^8^(RT$K-Ij;7b8K#8`$ z*QSYGefC=cKi5d(XS80%CYgtqfLMEAcg5ambar0KgM2r6#0^CY@gsKh48O+%O<;aM zeX)G!F8_KR77Tw=RZ6^M+ZgcX#2VJlaHt_0a$)>)QZeza7CdOD(V}HJsBTPmBL=|aQ_pI-$ zZl=>3w&vAANDBWw4|E=M9&S?~e!up6ytXM6qd|J1R^om}?4&VdP9-(*DAA$N?5V2a zPWvFsmH0O)b}I?JtGS)(Adz_@W>{Np z_Mk`(?0SG7q(!3ARu0wBA5ldP_Qn_Z2DV~hNU@POh_|wnRK7%w0(otVjXgqcPPsd^tZ8dE!yt7K}qWc(Rqw@a+*$FM%O^aB=XB!LYcKE zrWzX!ANA`foEJY4ICEdWrjIVb-Kx9bx$pKfJ-=8!90}|b78X{l;IM?Wrx0H9K%uMv zGU3R6+?sn-L!`cQrV8qkm(pr6~+dXdO_xJta z8Yfo_Y=7mjf?{qFJ-g72|DA}`YAN4bLNb9zU4@dP5iarwr**7sBi(o=)&BSXF!nlo zB*-dt1M!0bg7|}0^G){Xx8I-tc(57+?M&!5w5e+;5TkWGe6g%_qN|Gh`j(){8S{Rh zzPl8&_%-(PANV*S8K+-~_44bE_YzVBg_q2TK7DC!A4n0ct+|MADa7tB3sR?lDIl!D zKFxaS&{gH?5vgY!ZoZTo%`AxrTt}|Oa*S7$KP>JpzSV=HPUm=Do!;eV=l9zP(Q=;K zY?huiJRe_iIzE8rqg96_2C)VZmiPX%#SidF-hb@yh{2EI{OfaUwf_!SROAX z))m6(4qc6-%876o-_CCc)+(2yrkk3Mms1=Ju z*Iqo{owgSp+i?rXkd+-AxaBqFBnq?q2m4Z;o<<_J8#W{DCiSJK_jjI-BXYdZxRnTEoN@gsezW6Aiv^h zXSW09pOJ=2*x+o*zOmwVNsFrj5h;ALx#VhRx5Sh|PF7l2fQ`z&PED#*60g z6=X*7KD)WA7L!+$8)rK*l@W2M1Tz{dZI^MefG!<~o%@=sz(i^&ax`rLh5vulTYks2O>qPGo-x&lZV93^)~8g6@{-Co_K)gSF3pTnCHE z@L%|0Ep&8a07=-@h~e%i9T3&OP#z74_^q`TNu6wzyDDtlN`voj^=M}HiF@?<#Wk=8 zT896K3^8d^$A26^V~+D|HsHEaq{g_|z?mIz2|Kjdsq&bZ6qwlBmn;tIXaKXd#iKtmEO+@-+Anpbj>M+6uRQ?ytSw0zMj!9=;m7e+u-P zsA+iyGBS!wxamZfEzovvP19d_Z8~=qP@k%-T0?va2L62@OlcF?^QdFA^EO~^PaF@c z)#B&BO+RusUNr#~qd%?>nWZ{>I)7pPjfjDrJ;Fb`xbW;D_|xBU23fDPuIzf>8Q*Zc zy88C!0*8qBcxHWrs~Plsg^vUM^7+o;;#WPi@~po>3Ud5$JsYJ|DkORVLkB?KhVq+= zU(>YZC(c2!Oh-QBV&k)P01Rkq% z^eR+q`1e}Qhm z%iC`3lxxQIHlhIn5_A$}@X_P!AY-ujzrzoEF*WL3)S51J#9l8c#{JAXeG(Qv)|Vga zM99Sof8;S0m;awJozU_Qs0UJQ05<8UoWFV((lqFsa~HsZY?N!}^wV}u@Xcu!dp&p!lv@NgYY*RUY}%>s6CEn#{cKXs%k^R zxr5$3w0%`~<7f3Ene2M`g9ks=Ake9D2-PW;F?eAPf4{!C3_-Zi_#h30U4msBS>;+y zy($Mw1){t&vN7-v@ipNG5cUdw39(}1P+DeYl zR3yyS^RA&}y^)5{Z#xk5Wrk*VOG19G zu3S^DlFQC79nHi2$fB$cy2azo1fH!>1?fT>msCAPwdB%V{gxFh(vGM6XvMetnCFo& zx*f>WTM(YE@Ys45|A)DE+bk)2P%BC0FNt>Zjy$Rav_anc&q{I0*r3PtzV20*jc#yy9s*=~dxuCSDWc~xmO<1A>QWID#A?sZ5kbHU4IY{f zE9MY@xy~=c7;!4K(Axr7FO#r!Isa%#YCllHa>L44&i4lCiFY9olB@~9j22?67S;dk zv01dmT#dK;2xj!B?`j$_5+h@sGqzl6<9tR2x5=(U;=)=rg6BLm$mz_lpQ2YH&iQh( znxZuuR_=Vd+&YtknUD7Zz-E{R8%waJgTf#Dwq{59EAxqhkU9kppYlrnY2sG?H^FD% zBi#&#EYehP8b2T*8^$BvItdNI zfp|}FYm*zFywQZnwZ`(0zg?Un#D#}arS%E}*uz)s8a}1`ti0Gp77w;g!3RHYh)}O)W;J)&2!rHpkYVoUU z_z7v9CRaAMwy7DlB^u^iI5tus6)Se?qh+ zDO}=-F<(v@JH74p)*{~~(dLeoJe_nwx(;p(8KasaTOfyPYso(kPb-|(W?RF^fz<5i zfY5?pY+AVTH{Q}oX^XqRM%f~(tyEO{vEc%@TYyPz{~2#oWGj5gt}ZLH?1p|&*4w5o z-pEI}vcv!N{6YV(qxyEC05`7Y-KzyIjv7{)POCh|TgLUz<6{Xl+?Pp+GEPXvwZta- z%U%uXBCrylmgq58GrGfK9^Z#(w@`XQ$|IFS{H*!2w=M|;HqhFHNC1ESyM~kDGF`D8 zwNz9Ls1Mj5x5Yr6>fJuKCRizOLi6aw?h+D-BpLrK5uguwkwPMxl6=xNR#iiupZeMc zKMU3zD$g=K{1H9z$<$TLA-ie#cV}mjb@$Ku{Pm#&wScsTddH26kGa)1?Lt>M#pMaS zn0GVpYr?I@$bx@j@*{dIApPcO_eG6l|Bvr)?(hyle@aTzvKq^6d4-kfyuLe;YK#{& z!iCO_pG*=tXMPWG%o^c56v3=7ho$#F=w5d|0Uz*MnW`+Pqeak699g3y51HV^9sFe?S`P#G(yW7Ml(OQFdB^~Wkopzf zu0?R73}ln%AJ4~%iVeA+SbSb=o=#@YKQ0>$A7yL-S}y+W9*Kq3kI-M$niyutnqdXT_ktAe>%6>!5s)6w_A6h(t29voEe`U zc*e@#WUd6X*&1Y+LvUlT@nUV8ZvFVal~LLhjnt@1DpzSn~J1vD4 z!A0xCvCEG6LOH03$%c5tx{RB<^rRxge7Vbrmu$=si~a@cuEm6^7+)`K+YGM{D~5xJ zfp*deN!LmwfJyo|PMhg0*jZSOu7eQIC4+jlg_I{(T5`~08l5LW2@b_im$xx(e5%QI z+~7HGo272ED-4TY?QiS=CkV3WTiaXhkOvxE6+2{5WRWl%@<)-mb#n;mm+$95>-enm z8i@Db#PqkT+`-J^l;9S!2t5N*QRqvulkfgRwCT294G{9=2s<`=`{JUGdfG~jgU=>a zDyuTh`srpaZ|TiNn$)AmKz@LBiPuGa8*2i_Pl(p^JpkoBuIMa03y*v5wx9>1Vj)Q3 ztK)MbJCYIF9d8W`bUf7|n=NOxBdF)H0TixOi^$JCp?dd4wDAOR;_m-3yE#_t}xQMBzyiEfUo;Rk&QDJ zkDS^2Hr)4A z$PI^*m=(!U@W8S6!LPXzm@e=JaXn8W8ZXN8ik6jKG(h|j*P=62qZScgF70%pyEf3E zz`IX`I)c_0Z~oEG6Mit}FEtzO`doWounJ-{(x&#o?TvM@W=R+nrsl24h&YQg(H*2d zv7mmfkyn|DvjRJLVmM#P3L*J>69JMy5TCF5OSBYNqyJ2Km&#T4j!a#^qr-pn8(aOs z!J&;+Q@pPyc^`ztpA6*C42g*k2Go4aAWa#1=9k7AgrZtf6I0r`w7ph43=#iCLMIqY@aEv0|z&h8vJPxPsp$3;{xsR$|&7|U);W2d| z-7hI>$VqLSu|f5slJ*<-NClet4{YACQFWf&i|Ljb9dml+KFi`mf%Jcy*FH4av^8u? z;J-=9Eq?LLnXr^3qwwMl@`+cKQmA*Ebo&5;NHC>Z#K5%rV?h7+=+E5E)&uW(ck}#- zZ|ureJO9j;ofiK|5p5;i7c+$Ke0Q#=4sz0VG-vdre8^m;x6Niy@-0UlA~BlgQa0B! zT7FIvw<*RHxTJh|I#a1#I^ejeIbYgmV0NNEa8uf3e&oqnvGGDbR|R#Wursx60LR1= z%%7~P&~xdz;er)>m#nV$3JtSRrwU$S(C^6~LDnqI(LC>qKDa2>UL>e9kkq3WcR0;**XH?hAvRC3!r2Fk>R5bm4Tou_b zV1+F;#?q4ts6kZ6*=(AJg~`rHI3=ym*L#V+sq@-^7M4`*l$cEBgE9pPd?wr@KrXZ2K}5ZdNl@*T&0kOX~=``l7IUKE^U|eR-Teyob7z(*XKGF%J}~1W4A9Q%|CelfMcxX!x`g0H+GKAG1RWC zuQ=lPkG%hMEk)O1_a1!De`m&{IV3S^(}!xiv4vf*?^xKN=RmKd=mo6+)4NYB87TYP z4IECX`lTU;)KeP$mCU%(mlv&!H$G0_Ju17Y;Wf(C;=fO#nfBTIt0_W#LYDY@vYz$) z%=DwPd-2C}%1{t%@$?#+3*FqVRNNuyK1X-iOkPeu54lN=LZn(qAW08 z3C+q(YH-WiUKUko^)-n4#2oGpWS17O$5m66kZ^KE*wERDP5`;;W}TP4=k#_T6yq)6 zdU0{7WjezN&#mm0P~C$^LWenRx)$<7q6U7Y;`Jxb zoGisL_uO5sT|SWNK^fQ z{RXF6wY&}RZ8HS}>1KJy$I^aLgZLfT54AOdCCudYaAVI?@wxrrId0lMhk%f&?jXT7 z&az4n>N1+C5j;H`j1@h=eaP8*xK)1V{5c5nW8Rbp@cEV7h`|an_6X7Z%lp;K`Ncjt zK08HO4DzelR+Gk9Z62FV^IcSYz{e)!G)4Lza@MsFp|U3w{TW&2iBozkzOFt$<2iYx zTUO`AiV?qwX*BiQTlsod=IL0n-@4Y!c|HaN&PZ>{N^!U^Ky*onpjT2nR)(ws(%VcI zRDnGgM|}#1@<9Rn#an!D^!U;PXBq`&U^%FfsOg;tv*<464y5>Df>P5E?_PIe8VUKl zy_>|)EOmY7LB`S1J>kQ=ytU2H<~|hBi6o2rQr(R!jPZ*Dv-|QZ^Y6Vp^!A@^$l99V zRizm-c8LcHZr3z@%H0ofnn^!O4r3I|$=!a(c%l|Z=T2u4srZfCGt{kM=lpvTgZF7w z(-A7J6u*V|{2wbjH<)9qbz{q@dp2GxrPr;BRT^<5-gF?5t8Mzp$K?lGQDd=~=|GjB zz7aR4JDcZx@7}JeXO6mR5en2fbsyZ%kvJ+abT)jO2!d#Q9WNs4=nq2Q%F}m8QrFb1 zZTnnl@IQTb5ohWAvz#t7@kgQH{F=H?^cfk>TxhFZ>6mA6E=&wIdb`+nZGYWEILZUl zXS3YYbWt_18u`=R8R0VW+RyXMe@$z~|KHO_$#A&u$eg`sY_c7$*HQeS{rpl(gqsn3 zri=RV9V@GY)nv-+lr`R$l=!D%HeKyDYYp0xu7Z;rYdry73(ldd^CZ``IL*BjH}`yJ zOJCR*A1#h|1KqRR2cWcr1Qq$^81DR+KhvZKifCHVp|2aT)7aaL6%avTJ$&b>GWbVBO34QUjzCZPMb;F z9!$GCl2M*=L~XO# zlO&TKvJ<+Twob}iJNsxnedb)uVjKgvA6c9S0o8*qaeki3?FEefHj{~ZN+{;rjhtg6 z{-`*TEZizF{WI9cP?1{-&4r4F?w|wTb0_3aiJ__0FSzx`N7AM=pHFs*N|@zW=dC7n zv2JC(>scz}xsRPE>E@d|whJwC_4eP-n%8{#=eG!8zSVh_+dGZBeKzGs;Vd)1gVsGRK#yGpuj$CfEY+U zBVP@G#2j9s_HW2_SQ3`bmfA}On^%-$lJOoo?iJwlb8NkKs&zDeb)K%LR71YvbLZlP zk0(tYM4cIo*R>AKT*BPn2K5nj5HZk{8)}OnD zTLRg3@Uznta4y#l4o4sMXLk2JWD?WPc(A!6AW>T2cTkY^Ltt~`GnxL3cJN}$wMq&t z-!khR(OsEQAvHA4E`5#D%?^CZCv#i(-gS4bEO)J|6b&$#ObafXz@@1V)k>@fl!|^e ztefbLiOOzyN-OImN;Eh4`vZslK)u#f!v9_Xb4S+tY}Gl2CNK0-bmNGBfA|rlIz8?AII6I88spY}`u!DU$^{gKmNGosQV#}`3Qk#&oS71YM^3zOmkty0M)XI>@c z`MDEkR`WALrrO!-BYVL?l3}t7dN}dho6ak7UHSZoYn5xMUcVn{%rqEkQhxu_g365x zl^36Uq$jf@A2}M25)swrKJY(hGyx%p7#xuCG_Wk$7x1f?z`b$G6O`p= z*k4~$A~g<~85nV^dG(<4%S5XF_pHD@MtEb6c7cEWuK^?=B=a9p2R`%wvl=P-{hH#S z#+Y^PJM~_}{xj+9ESt7COG8a_lTDZFF7pneyhSD=IT4zkf!=a!KaF?P*VN`gN|286 z_NU``WZWO8BDe9{bE2jxbQ1VKjnV4V)@3kSJJfC>}(v0tRP5{zXE}Y9MX!IMr z1yU>XDFHxOxK-Y3wn`J*C`{ZI+dbete`rLlh2=S?nTDy`=(;0T)6Mg%ff5ylydifL zbfH*6I05=%YUB|niO-qlB>62bgZ##q$jH-t3X3=Om{GYkdQOCq&pn+@eoq_gy3U)y z!CR*q`*S~5qh25FynkD5n@QtydeJtH-mlbQJWnUd_OO#T*8yRPlEOyzmlxM>ZnZ`m zd=879>4XPsImcNBaMD-rxp>k$?Y3=CL+D4E`*j$8#`UB_bVDB83|@7s)Y?7X-$H8-LbcH#s5tBm}N6=>Bxeg3F*CxY~{ zIC}q2`jJQBjIEWxTVwY7=E-Gx;{B|VEB-S` zQa@RqNdLk%KecC4Q5ex9@lN}oJeuXefp?J}B*~;Yog6x#@_#)&F?wnyJysT~V{KRJ zRQT%I%*wTG73q7oUwKiZE*K?VKV+ZwD4%REF6b_6m1v^8wQoAqEdLkGnI(VISsqaM zXIU~}!)YGMnZWD!tTE+6IC*->O+0D#J?W;-Z%+cTs*D;xU*A{l0XWIzt#eXeN~NK5 zvi6ac)K(b6vdjvE|KIP33GN?p&wj~)mmRT(`wp+JpuWzJ_#Rw$tQlxko^8=0x2=}# z@ylk)vqyKzdWqxXa^x_@cSUB?lGN?y$-W)j_7`?N=ds{NU%KHHmhj-WfX*gRuzVm} zd4@%bF|PcrcHQ0tLfZ?k-lh^>$#=&4-R&?X%--`!txz;^WGR zKi2YOT~QZ486R_N_ZYctP{v3qR)JOeOuSn z9~*1pKd9Sr-=o=v;7oUVl5Qu|00JLZEH+<8KMRF3Y_HVn7H@uygdpJ4rt>(GU<#E}7Zxf5#S@;o)J|>F@_-YP+Yi{rv;ii=8i9OdaPp zrHSazZ6Fv;*k|M>;kFmR)kzJT>urG=&4MC_gp}C}0{HkAWV<4^W1aM;>U0;TZ=7MD z(gC>D$h>4Cynd@sa_{Ws&mr^1&87m+X*dI;%5_89J)w7<#d3HsWBo@bveuDupP(_`&JFtG;+@NlvwI!w7dq1w z=W)w?o1mR?Q8~3g&i>S(lB-YEKRw!nSv!Mr9+5K)yY#LB;y8qH3xuxS$7H}-op?(3 zG2lDuY(RAE^c6^kZ%5c-_6qS%@olT{172dZYee*TgNhN#KxiycDoo6v zr*GX1g`>mJ%xcW0nVAts&4!<+Azb0l>Bz6mtuO?B=GtLMkJog$!!P2C@rFE%`L>#I zV35=tz;`4IT#}JU+;i@UC#G~#{r9+l(6x+DdC(0xPvOXl&pD?%f|knr zJraunto#g3P(~j9UOL!pE6Zlqa#wMZ=l&@a(ie~0YkrzCCNnlsipus2N9uSy+qckm z5r3i&QqNXOWduQT3a$8Ztj=}bP_M@&<+i0QuLc*CyB{4t;6j*QX2^E<+EyhO9MPu%|qG0R)cr`OPEiUJGf==5%G8|As z&C5m2t7~v$8{6xQ7J`g9?*Xw_aY7gH)hV`p74r5JR483)?{5F@W%&nxaMc7KO|ZWi zf6j`zbklDjCR@rwZrA!p=mGXifU~_B(=&q!r-bd26|JpK<< zK3*$%rZW1fFroGNVM~gSXLn^$c#@M2$SK%w*=_~i>@)OY%eJ8htRJshJ{_d3s)GApKc3vG;3AN#htIKIg$nSvh1n2ByTU z5-=*PMUMQME^5P#Thk(=q`ZCKP2=I|e8xVI(jYn4A|@VRTLj>8Vs6*LhcNkf-4vC1 z;K6sie1%4Zj)nN&A+3KChimcFi@2p^y~Jz;c61FrJxqa6$gm2|3f(sI7kB+TO#dR5 zP)-F^-4IHP7;)zf^f^l475Ie>M2NRR;8W;z+Da*5hKYfY?en-(CPFdum}7?BJV>I0 zBxpUD?jA2WKl|e01r)6o(u)Q7Sto*@&O;PR@r6E_x&P(x?jdoW@CG><dDEa9P*JTW%?4My^`nv@1)Q0J%O1 zf`h1a@P$ z@x|T4!z_^U&Hz8NJ#}yn1kr3fa{9-~zW5vh)sv+^oUOD4yE+=a!KQb_gLunh@R>^Y z;U|^M`I_ISqELEr&#lJ+FL4h_)8L9+%`RH268N@H*=aeh$m33bf`9jf(y)m^Tt9&l z25GdknWDB&BflD)!;!*|p>HQRUcyAY1SX7RWcZYY(&^>K>&M{@=Vla zoFX@r$*z>}xj9^6EYyMtL`U?2@e;^c^OL;y^$ewxrv66-yLFACv?=?qv#`mE^3HE| z+0bW0AZ;X}y|}8$qc}|zmp^82pQ3sAjS(}Hrb21}vsnujm+*g0TL3<;5f9b+&M%Z! zxOrg3?`D@JZT=jbo$IP9IAD9=qT~GI@agI0eRt0mL~-GXs#`E32_$Oi(f6PE%i5buvIv7n ztgP4P;RRuh(}oOeUWA8la(#0Dw9grgnbO-ocRNOgD7_BYR;nz2>YmD`|5@&Q z`j1Tk_=1;8adbvK@dHVc4!!K@6_=}rO{7_}%>v#{>&4NcB4W+lmT}e+ue)nE$?(-0 z?vd}_IU8(4D%9Ro8o;Btb7YjzdHU3++IxA4I-sawt77E&^=c}=gF8RBZ+YN5+;3`{ zg)bS4FmLH0Jnxb|9QYnBFI9@CjXO^%Xm0EJJ#Ib#mBV}7Kp=@Fre^sXhl%5kkbubCEeXenKGb4~NOe~bl%i|5rrN8zdC z=NAY1wQ3`_3pnN@-eW2hvX5+KG|TW;@mg^Gah@)eEi@(0l7D|PuR3w7HPlO7dTS~< zk2-}L`SI)U^t1l#tzaz4L_+2d(EF6T_>f`r=L=Y2NG$*3_Nl^VA272W~$lS!125&*vCvXQ^Bi zuFC3jsEu^>8(iZEKD9nt{gJ9ap_*8e-)=f8POT*O)@b&oi{_Z@#fJ}$)RgkL<}NmN p_Vq`n>SLOTY-;~M{dbWqkGmuA^eGA3Ix`L!YD!v)B@ZlK|38&f(1!p3 literal 0 HcmV?d00001 diff --git a/assets/images/reftree-eb6c2fd2b4464cf68d9c3fe04e8ad231.png b/assets/images/reftree-eb6c2fd2b4464cf68d9c3fe04e8ad231.png deleted file mode 100644 index 1a84040ade541d3c01ac631ecfc3e5816a671077..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90254 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 Oh@7;NRH=mF$NvXq3YnV# diff --git a/assets/js/22c5de9a.0c9c5e17.js b/assets/js/22c5de9a.1b68f151.js similarity index 87% rename from assets/js/22c5de9a.0c9c5e17.js rename to assets/js/22c5de9a.1b68f151.js index 05097c3..72acfc3 100644 --- a/assets/js/22c5de9a.0c9c5e17.js +++ b/assets/js/22c5de9a.1b68f151.js @@ -1 +1 @@ -"use strict";(self.webpackChunkreftree=self.webpackChunkreftree||[]).push([[672],{8598:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>d,contentTitle:()=>l,default:()=>g,frontMatter:()=>o,metadata:()=>c,toc:()=>p});var t=i(4848),s=i(8453),a=i(3554),r=i.n(a);const o={sidebar_position:1,description:"A journey deep inside reftree\u2019s animations feature, showing how some of functional programming techniques and concepts can be applied to produce visualizations of themselves."},l="Visualize your data structures!",c={id:"talks/Visualize",title:"Visualize your data structures!",description:"A journey deep inside reftree\u2019s animations feature, showing how some of functional programming techniques and concepts can be applied to produce visualizations of themselves.",source:"@site/../site-gen/target/mdoc/talks/Visualize.md",sourceDirName:"talks",slug:"/talks/Visualize",permalink:"/reftree/docs/talks/Visualize",draft:!1,unlisted:!1,editUrl:"https://github.com/stanch/reftree/tree/main/docs/../site-gen/target/mdoc/talks/Visualize.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1,description:"A journey deep inside reftree\u2019s animations feature, showing how some of functional programming techniques and concepts can be applied to produce visualizations of themselves."},sidebar:"mainSidebar",previous:{title:"Unzipping Immutability",permalink:"/reftree/docs/talks/Immutability"}},d={},p=[{value:"Introducing reftree",id:"introducing-reftree",level:2},{value:"Inside reftree",id:"inside-reftree",level:2},{value:"Functional animation",id:"functional-animation",level:2},{value:"Zipping it up",id:"zipping-it-up",level:2}];function h(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",img:"img",li:"li",p:"p",pre:"pre",ul:"ul",...(0,s.R)(),...e.components},{Details:a}=n;return a||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"visualize-your-data-structures",children:"Visualize your data structures!"}),"\n",(0,t.jsx)(n.p,{children:"This page contains the materials for my talk \u201cVisualize your data structures!\u201d."}),"\n","\n",(0,t.jsx)(r(),{controls:!0,style:{marginBottom:"1em"},url:"https://www.youtube.com/watch?v=yoayLNPTESk"}),"\n",(0,t.jsxs)(a,{children:[(0,t.jsx)("summary",{children:"Older videos"}),(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["ScalaDays Chicago, April 2017: ",(0,t.jsx)(n.a,{href:"https://www.youtube.com/watch?v=6mWaqGHeg3g",children:"https://www.youtube.com/watch?v=6mWaqGHeg3g"})]}),"\n"]})]}),"\n",(0,t.jsx)(n.p,{children:"You can use this page in two ways:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"as a reference/refresher on the material covered in the talk;"}),"\n",(0,t.jsx)(n.li,{children:"as an interactive playground where you can try the same commands I presented."}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Throughout this page we will assume the following\ndeclarations (each section might add its own):"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"import reftree.core._\nimport reftree.diagram._\nimport reftree.render._\nimport reftree.geometry._\nimport reftree.svg.animation.Frame\nimport reftree.svg.XmlSvgApi\nimport reftree.svg.XmlSvgApi.svgUnzip\nimport reftree.contrib.XmlInstances._\nimport reftree.contrib.OpticInstances._\nimport reftree.contrib.ZipperInstances._\nimport reftree.contrib.ShapelessInstances._\nimport reftree.util.Optics\nimport reftree.demo.Data\nimport reftree.demo.Shortcuts\nimport scala.collection.immutable._\nimport java.nio.file.Paths\nimport Diagram.{sourceCodeCaption => diagram}\n"})}),"\n",(0,t.jsx)(n.p,{children:"To start an interactive session, just run"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"$ sbt demo\n@ render(List(1, 2, 3))\n"})}),"\n",(0,t.jsxs)(n.p,{children:["and open ",(0,t.jsx)(n.code,{children:"diagram.png"})," in your favorite image viewer (hopefully one that\nreloads images automatically on file change). You will also need to have\n",(0,t.jsx)(n.a,{href:"http://www.graphviz.org/",children:"GraphViz"})," installed. ",(0,t.jsx)(n.em,{children:"The interactive session\nalready has all the necessary imports in scope."})]}),"\n",(0,t.jsxs)(n.h2,{id:"introducing-reftree",children:["Introducing ",(0,t.jsx)(n.code,{children:"reftree"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'// extra declarations for this section\nval renderer = Renderer(\n renderingOptions = RenderingOptions(density = 100),\n directory = Paths.get(ImagePath, "visualize")\n)\nimport renderer._\n'})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.a,{href:"https://stanch.github.io/reftree",children:"reftree"})," is a library for visualizing Scala data structures."]}),"\n",(0,t.jsx)(n.p,{children:"Let\u2019s look at a quick usage example:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'case class Person(firstName: String, age: Int)\n\nval bob = Person("Bob", 42)\n// bob: Person = Person(firstName = "Bob", age = 42)\n\ndiagram(bob).render("bob")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"bob",src:i(108).A+"",width:"313",height:"363"})}),"\n",(0,t.jsx)(n.p,{children:"That\u2019s it! You can configure the visualization as you like:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"// render strings as a single box\nimport reftree.contrib.SimplifiedInstances.string\n"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'// rename the firstName field (pun not intended)\nimplicit val personConfig: ToRefTree.DerivationConfig[Person] =\n ToRefTree.DerivationConfig[Person]\n .tweakField("firstName", _.withName("name"))\n\ndiagram(bob).render("bob-simplified")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"bob-simplified",src:i(9341).A+"",width:"238",height:"364"})}),"\n",(0,t.jsxs)(n.p,{children:["There are various ways you can use ",(0,t.jsx)(n.code,{children:"reftree"}),":"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"improving the documentation of your projects;"}),"\n",(0,t.jsx)(n.li,{children:"live-coding demos;"}),"\n",(0,t.jsx)(n.li,{children:"exploring how things work;"}),"\n",(0,t.jsx)(n.li,{children:"anywhere you need diagrams of your Scala data structures."}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["(",(0,t.jsx)(n.em,{children:"Incidentally, this talk is an example of all of the above."}),")"]}),"\n",(0,t.jsxs)(n.p,{children:["My previous ",(0,t.jsx)(n.code,{children:"reftree"}),"-powered ",(0,t.jsx)(n.a,{href:"/reftree/docs/talks/Immutability",children:"talk"})," focused on\nimmutable data and various ways it can be manipulated (I do recommend it)."]}),"\n",(0,t.jsxs)(n.p,{children:["Today I would like to take you on a journey deep inside ",(0,t.jsx)(n.code,{children:"reftree"})," itself,\nso that we can see how some of these techniques and concepts can be applied...\nto produce visualizations of themselves \u2014 using one of my favorite ",(0,t.jsx)(n.code,{children:"reftree"}),"\nfeatures: animations."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Animation\n .startWith(Queue(1, 2, 3))\n .repeat(3)(_.iterate(2)(q => q :+ (q.max + 1)).iterate(2)(_.tail))\n .build(Diagram.toStringCaption(_).withAnchor("queue"))\n .render("queue")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"queue",src:i(9696).A+"",width:"540",height:"922"})}),"\n",(0,t.jsxs)(n.h2,{id:"inside-reftree",children:["Inside ",(0,t.jsx)(n.code,{children:"reftree"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"// extra declarations for this section\nimport reftree.contrib.SimplifiedInstances.{option, seq, list}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["First, we need to grasp the basics of ",(0,t.jsx)(n.code,{children:"reftree"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["To visualize a value of some type ",(0,t.jsx)(n.code,{children:"A"}),", ",(0,t.jsx)(n.code,{children:"reftree"})," converts it into a data structure\ncalled ",(0,t.jsx)(n.code,{children:"RefTree"})," (surprise!), using a typeclass ",(0,t.jsx)(n.code,{children:"ToRefTree[A]"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["For case classes this is done automagically, using\n",(0,t.jsx)(n.a,{href:"https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#generic-representation-of-sealed-families-of-case-classes",children:(0,t.jsx)(n.em,{children:"shapeless"})}),".\n(",(0,t.jsxs)(n.em,{children:["If you are curious about the magic, take a look at ",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/core/GenericInstances.scala",children:"this file"}),"."]}),")\nGiven our friend ",(0,t.jsx)(n.code,{children:"bob"}),", ",(0,t.jsx)(n.em,{children:"shapeless"})," would provide a generic representation,\nwhich includes the field names (at the type level!) and the values (as a heterogeneous list):"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Shortcuts.generic(bob)\n// res2: shapeless.::[String with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged["firstName"], String], shapeless.::[Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged["age"], Int], shapeless.HNil]] = "Bob" :: 42 :: HNil\n\ndiagram(Shortcuts.generic(bob)).render("generic")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"generic",src:i(2634).A+"",width:"417",height:"460"})}),"\n",(0,t.jsxs)(n.p,{children:["This information is enough to auto-generate a ",(0,t.jsx)(n.code,{children:"RefTree"}),".\nNow, what does it look like? The best way to find out is to visualize a ",(0,t.jsx)(n.code,{children:"RefTree"}),"\nof a ",(0,t.jsx)(n.code,{children:"RefTree"}),"!"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'diagram(Shortcuts.refTree(bob)).render("reftree")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"reftree",src:i(3342).A+"",width:"1581",height:"836"})}),"\n",(0,t.jsxs)(n.p,{children:["As you can see, it contains values (",(0,t.jsx)(n.code,{children:"Val"}),") and references (",(0,t.jsx)(n.code,{children:"Ref"}),")."]}),"\n",(0,t.jsxs)(n.p,{children:["How do we get from ",(0,t.jsx)(n.code,{children:"RefTree"})," to an image though?\nThis is where ",(0,t.jsx)(n.a,{href:"http://www.graphviz.org/",children:"GraphViz"})," comes in.\nFrom a ",(0,t.jsx)(n.code,{children:"RefTree"})," we can obtain a graph definition that can be rendered by GraphViz:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Shortcuts.graph(bob).encode\n// res5: String = """digraph "Diagram" {\n// graph [ ranksep=0.8 bgcolor="#ffffff00" ]\n// node [ shape="plaintext" fontname="Source Code Pro" fontcolor="#000000ff" ]\n// edge [ arrowsize=0.7 color="#000000ff" ]\n// "-repl.MdocSession$MdocApp$Person1720614161" [ id="-repl.MdocSession$MdocApp$Person1720614161" label=<
Personnameage
·42
> ] [ color="#104e8bff" fontcolor="#104e8bff" ]\n// "-java.lang.String337059875" [ id="-java.lang.String337059875" label=<
"Bob"
> ] [ color="#104e8bff" fontcolor="#104e8bff" ]\n// "-repl.MdocSession$MdocApp$Person1720614161":"0":"s" -> "-java.lang.String337059875":"n":"n" [ id="-repl.MdocSession$MdocApp$Person1720614161-0-java.lang.String337059875" ] [ color="#104e8bff" ]\n// }"""\n'})}),"\n",(0,t.jsxs)(n.p,{children:["Going even further, we can ask GraphViz for an ",(0,t.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/Scalable_Vector_Graphics",children:"SVG"})," output:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Shortcuts.svg(bob)\n// res6: xml.Node = Diagram\x3c!-- -repl.MdocSession$MdocApp$Person1720614161 --\x3e-repl.MdocSession$MdocApp$Person1720614161Personnameage\xb742\x3c!-- -java.lang.String337059875 --\x3e-java.lang.String337059875"Bob"\x3c!-- -repl.MdocSession$MdocApp$Person1720614161->-java.lang.String337059875 --\x3e-repl.MdocSession$MdocApp$Person1720614161:s->-java.lang.String337059875:n\n'})}),"\n",(0,t.jsx)(n.p,{children:"At this point you might be guessing how we can use this as a basis for our animation approach.\nEvery state of a data structure will be a separate frame in the SVG format.\nHowever, an animation consisting of these frames alone would be too jumpy.\nWe need to add intermediate frames to smoothly \u201cmorph\u201d one frame into another.\nWith SVG being a vector format, this sounds simple.\nWe just have to individually morph different aspects of the image:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"graph node positions;"}),"\n",(0,t.jsx)(n.li,{children:"graph edges and their shapes;"}),"\n",(0,t.jsx)(n.li,{children:"colors;"}),"\n",(0,t.jsx)(n.li,{children:"stroke thickness;"}),"\n",(0,t.jsx)(n.li,{children:"transparency."}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Ouch! A sane functional approach would definitely help here :)"}),"\n",(0,t.jsx)(n.h2,{id:"functional-animation",children:"Functional animation"}),"\n",(0,t.jsxs)(n.p,{children:["Let\u2019s start by introducing an abstraction for morphing, or, in other words,\ninterpolating things of type ",(0,t.jsx)(n.code,{children:"A"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"trait Interpolation[A] {\n def apply(left: A, right: A, time: Double): A\n def sample(left: A, right: A, n: Int, inclusive: Boolean = true): Seq[A]\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["(",(0,t.jsxs)(n.em,{children:["If you are curious, ",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/geometry/Interpolation.scala",children:"here is the actual implementation"}),"."]}),")"]}),"\n",(0,t.jsxs)(n.p,{children:["Once we have an instance of ",(0,t.jsx)(n.code,{children:"Interpolation[xml.Node]"}),", we can generate\nas many intermediate frames as we want! But how do we construct this instance?"]}),"\n",(0,t.jsxs)(n.p,{children:["Consider a lowly floating point number (it can represent an ",(0,t.jsx)(n.em,{children:"x"})," coordinate of some element in our SVG, for example).\nThere is an obvious way to implement ",(0,t.jsx)(n.code,{children:"Interpolation[Double]"}),", which ",(0,t.jsx)(n.code,{children:"reftree"})," already defines as ",(0,t.jsx)(n.code,{children:"Interpolation.double"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val numbers = Interpolation.double.sample(0, 10, 5).toList\n// numbers: List[Double] = List(0.0, 2.5, 5.0, 7.5, 10.0)\n\ndiagram(numbers).render("numbers")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"numbers",src:i(4447).A+"",width:"376",height:"193"})}),"\n",(0,t.jsx)(n.p,{children:"Now if you think about a point in 2D space, it\u2019s just two numbers joined together:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val point = Point(0, 10)\n// point: Point = Point(x = 0.0, y = 10.0)\n\ndiagram(point).render("point")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"point",src:i(4967).A+"",width:"226",height:"231"})}),"\n",(0,t.jsx)(n.p,{children:"Can we use the number interpolation to interpolate these two numbers?\nTo answer this question, let\u2019s introduce more abstraction\n(in a great tradition of functional programming)."}),"\n",(0,t.jsxs)(n.p,{children:["A lens ",(0,t.jsx)(n.code,{children:"Lens[A, B]"})," is something that can \u201cfocus\u201d on a piece of data of type ",(0,t.jsx)(n.code,{children:"B"}),"\ninside a data structure of type ",(0,t.jsx)(n.code,{children:"A"})," and provide read-write access to it.\nWe will use the excellent ",(0,t.jsxs)(n.a,{href:"https://github.com/julien-truffaut/Monocle",children:[(0,t.jsx)(n.em,{children:"Monocle"})," library"]}),"\nto create lenses and other optics along the way:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'import monocle.macros.GenLens\n\nval x = GenLens[Point](_.x)\n// x: monocle.package.Lens[Point, Double] = repl.MdocSession$MdocApp$$anon$1@3ecdc46e\nval y = GenLens[Point](_.y)\n// y: monocle.package.Lens[Point, Double] = repl.MdocSession$MdocApp$$anon$2@2426d136\n\n(diagram(OpticFocus(x, point)).toNamespace("x") +\n diagram(OpticFocus(y, point)).toNamespace("y")).render("x+y")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"x+y",src:i(9221).A+"",width:"580",height:"231"})}),"\n",(0,t.jsx)(n.p,{children:"Lenses provide several methods to manipulate data:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"x.get(point)\n// res10: Double = 0.0\ny.set(20)(point)\n// res11: Point = Point(x = 0.0, y = 20.0)\ny.modify(_ + 20)(point)\n// res12: Point = Point(x = 0.0, y = 30.0)\n"})}),"\n",(0,t.jsxs)(n.p,{children:["If we can read and write each coordinate field, we can interpolate them separately\nand update the point field by field.\nWe do this by piping ",(0,t.jsx)(n.code,{children:"Interpolation.double"})," through ",(0,t.jsx)(n.code,{children:"x"})," and ",(0,t.jsx)(n.code,{children:"y"})," lenses\nand combining the resulting interpolations:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val pointInterpolation = (\n x.interpolateWith(Interpolation.double) +\n y.interpolateWith(Interpolation.double))\n// pointInterpolation: Interpolation[Point] = reftree.geometry.Interpolation$$anonfun$apply$4@22a4cc89\n\nval points = pointInterpolation.sample(Point(0, 0), Point(10, 20), 5).toList\n// points: List[Point] = List(\n// Point(x = 0.0, y = 0.0),\n// Point(x = 2.5, y = 5.0),\n// Point(x = 5.0, y = 10.0),\n// Point(x = 7.5, y = 15.0),\n// Point(x = 10.0, y = 20.0)\n// )\n\ndiagram(points).render("points")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"points",src:i(7692).A+"",width:"1176",height:"363"})}),"\n",(0,t.jsxs)(n.p,{children:["Of course, ",(0,t.jsx)(n.code,{children:"reftree"})," already defines this as ",(0,t.jsx)(n.code,{children:"Point.interpolation"}),"."]}),"\n",(0,t.jsx)(n.p,{children:"Using the same approach, we can build a polyline interpolator\n(assuming the polylines being interpolated consist of equal number of points):"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Data.polyline1\n// res14: Polyline = Polyline(\n// points = List(Point(x = 0.0, y = 10.0), Point(x = 10.0, y = 20.0))\n// )\nData.polyline2\n// res15: Polyline = Polyline(\n// points = List(Point(x = 20.0, y = 30.0), Point(x = 40.0, y = 50.0))\n// )\n\nval polylineInterpolation = (GenLens[Polyline](_.points)\n .interpolateEachWith(Point.interpolation))\n// polylineInterpolation: Interpolation[Polyline] = reftree.geometry.Interpolation$$anonfun$apply$4@30694434\n\nval polylines = polylineInterpolation.sample(Data.polyline1, Data.polyline2, 3).toList\n// polylines: List[Polyline] = List(\n// Polyline(points = List(Point(x = 0.0, y = 10.0), Point(x = 10.0, y = 20.0))),\n// Polyline(points = List(Point(x = 10.0, y = 20.0), Point(x = 25.0, y = 35.0))),\n// Polyline(points = List(Point(x = 20.0, y = 30.0), Point(x = 40.0, y = 50.0)))\n// )\n\ndiagram(polylines).render("polylines")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"polylines",src:i(5846).A+"",width:"1491",height:"664"})}),"\n",(0,t.jsxs)(n.p,{children:["We are finally ready to implement our first substantial interpolator: one that morphs graph edges.\n",(0,t.jsxs)(n.em,{children:["The following approach is inspired by Mike Bostock\u2019s ",(0,t.jsx)(n.a,{href:"https://bl.ocks.org/mbostock/3916621",children:"path tween"}),",\nhowever ",(0,t.jsx)(n.code,{children:"reftree"})," puts more emphasis on types and even includes its own\n",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/geometry/Path.scala",children:"SVG path parser and simplification algorithm"}),"."]})]}),"\n",(0,t.jsx)(n.p,{children:"The resulting animation should look like this:"}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edges-100",src:i(8914).A+"",width:"361",height:"194"})}),"\n",(0,t.jsxs)(n.p,{children:["An edge is drawn with an ",(0,t.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths",children:"SVG path"}),",\nwhich consists of several commands, e.g. \u201cmove to\u201d, \u201cline to\u201d, \u201cbezier curve to\u201d.\nHere is a minimized SVG snippet for an actual edge:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Data.edge1\n// res17: xml.Node = \n\ndiagram(Data.edge1).render("edge")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edge",src:i(1842).A+"",width:"1365",height:"703"})}),"\n",(0,t.jsxs)(n.p,{children:["As you can see, the commands themselves are given in the ",(0,t.jsx)(n.code,{children:"d"})," attribute inside the ",(0,t.jsx)(n.code,{children:"path"})," element\nin a rather obscure format. Luckily, we have lenses and other optics at our disposal\nto plumb through this mess."]}),"\n",(0,t.jsxs)(n.p,{children:["First, let\u2019s get to the ",(0,t.jsx)(n.code,{children:"path"})," element. ",(0,t.jsx)(n.code,{children:"reftree"})," implements a few things that will help us:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"XmlSvgApi"}),", an implementation of several useful SVG operations for ",(0,t.jsx)(n.em,{children:"scala-xml"}),".\nIn particular, if offers a CSS selector-like method for matching elements of certain type and/or class."]}),"\n",(0,t.jsxs)(n.li,{children:["An optic that focuses on an element deep inside XML or any other recursive data structure: ",(0,t.jsx)(n.code,{children:"Optics.collectFirst"}),".\nIt is actually an ",(0,t.jsx)(n.code,{children:"Optional"}),", not a ",(0,t.jsx)(n.code,{children:"Lens"}),", since the element might be missing."]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val edgePathElement = Optics.collectFirst(XmlSvgApi.select("path"))\n// edgePathElement: monocle.package.Optional[xml.Node, xml.Node] = monocle.Optional$$anon$6@60d718e3\n\ndiagram(OpticFocus(edgePathElement, Data.edge1)).render("edgePathElement")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edgePathElement",src:i(3135).A+"",width:"1408",height:"703"})}),"\n",(0,t.jsxs)(n.p,{children:["Next, we need to \u201cdescend\u201d to the ",(0,t.jsx)(n.code,{children:"d"})," attribute. Here is where optics really shine:\nwe can compose ",(0,t.jsx)(n.code,{children:"Optional[A, B]"})," with ",(0,t.jsx)(n.code,{children:"Optional[B, C]"})," to get an ",(0,t.jsx)(n.code,{children:"Optional[A, C]"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val d = XmlSvgApi.attr("d")\n// d: monocle.package.Optional[xml.Node, String] = monocle.POptional$$anon$1@72eadaae\nval edgePathString = edgePathElement composeOptional d\n// edgePathString: monocle.POptional[xml.Node, xml.Node, String, String] = monocle.POptional$$anon$1@762e80fe\n\ndiagram(OpticFocus(edgePathString, Data.edge1)).render("edgePathString")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edgePathString",src:i(8908).A+"",width:"1403",height:"703"})}),"\n",(0,t.jsx)(n.p,{children:"Next, we will use an isomorphism, another kind of optic, to view\nthe string as a nice case class:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Path.stringIso\n// res21: monocle.package.Iso[String, Path] = monocle.PIso$$anon$9@c1c32eb\n\nval edgePath = edgePathString composeIso Path.stringIso\n// edgePath: monocle.POptional[xml.Node, xml.Node, Path, Path] = monocle.POptional$$anon$1@54d4338b\n\ndiagram(edgePath.getOption(Data.edge1)).render("edgePath")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edgePath",src:i(3959).A+"",width:"1435",height:"701"})}),"\n",(0,t.jsxs)(n.p,{children:["And finally, another isomorphism takes us from a ",(0,t.jsx)(n.code,{children:"Path"})," to its sampled representation\nas a ",(0,t.jsx)(n.code,{children:"Polyline"}),". (",(0,t.jsx)(n.em,{children:"Purists will say that this is not really an isomorphism because\nit\u2019s not reversible, but with a lot of points you can get pretty close ;)"}),")"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Path.polylineIso(points = 4)\n// res23: monocle.package.Iso[Path, Polyline] = monocle.PIso$$anon$9@704fac15\n\ndef edgePolyline(points: Int) = edgePath composeIso Path.polylineIso(points)\n\ndiagram(edgePolyline(4).getOption(Data.edge1)).render("edgePolyline")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edgePolyline",src:i(7274).A+"",width:"1727",height:"532"})}),"\n",(0,t.jsx)(n.p,{children:"Let\u2019s interpolate!"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'def edgeInterpolation(points: Int) = edgePolyline(points).interpolateWith(Polyline.interpolation)\n\ndef edges(points: Int, frames: Int) = (Data.edge1 +:\n edgeInterpolation(points).sample(Data.edge1, Data.edge2, frames, inclusive = false) :+\n Data.edge2)\n\nAnimatedGifRenderer.renderFrames(\n edges(4, 4).map(Frame(_)),\n Paths.get(ImagePath, "visualize", "edges-4.gif"),\n RenderingOptions(density = 200),\n AnimationOptions(framesPerSecond = 1)\n)\n\nAnimatedGifRenderer.renderFrames(\n edges(100, 32).map(Frame(_)),\n Paths.get(ImagePath, "visualize", "edges-100.gif"),\n RenderingOptions(density = 200),\n AnimationOptions(framesPerSecond = 8)\n)\n'})}),"\n",(0,t.jsx)(n.p,{children:"With 4 points and 4 frames:"}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edges-4",src:i(5047).A+"",width:"361",height:"194"})}),"\n",(0,t.jsx)(n.p,{children:"With 100 points and 32 frames:"}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edges-100",src:i(8914).A+"",width:"361",height:"194"})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsxs)(n.em,{children:["Interpolating the entire image is left as an exercise for the reader,\nalthough the impatient will find the complete implementation\n",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/svg/animation/GraphInterpolation.scala",children:"here"}),"."]})}),"\n",(0,t.jsxs)(n.p,{children:["Notice that we never touched XML directly.\nIn fact, equipped with the same set of optics for another format or representation,\nwe would be able to operate on it without changing the code too much.\nCase in point: ",(0,t.jsx)(n.code,{children:"reftree"})," supports both\n",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/jvm/src/main/scala/reftree/svg/XmlSvgApi.scala",children:(0,t.jsx)(n.em,{children:"scala-xml"})})," and\n",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/js/src/main/scala/reftree/svg/DomSvgApi.scala",children:(0,t.jsx)(n.em,{children:"scala-js-dom"})})," (for Scala.js),\nwith only 50 lines of implementation-specific code for each backend.\nThis goes to show the flexibility and usefulness of optics."]}),"\n",(0,t.jsx)(n.h2,{id:"zipping-it-up",children:"Zipping it up"}),"\n",(0,t.jsxs)(n.p,{children:["In the previous section we saw ",(0,t.jsx)(n.code,{children:"Optics.collectFirst"})," \u2014 an optic that is able to perform\nmodifications deep inside SVG. How do we go about implementing something like this,\nor, more generally, how do we edit recursive data structures such as XML?"]}),"\n",(0,t.jsxs)(n.p,{children:["This solution is called a \u201cZipper\u201d, and was introduced by G\xe9rard Huet in 1997.\nIt consists of a \u201ccursor\u201d pointing to a location anywhere in a tree \u2014 \u201ccurrent focus\u201d.\nThe cursor can be moved freely with operations like ",(0,t.jsx)(n.code,{children:"moveDownLeft"}),", ",(0,t.jsx)(n.code,{children:"moveRight"}),", ",(0,t.jsx)(n.code,{children:"moveUp"}),", etc.\nCurrent focus can be updated, deleted, or new nodes can be inserted to its left or right.\nZippers are immutable, and every operation returns a new Zipper.\nAll the changes made to the tree can be committed, yielding a new modified version of the original tree."]}),"\n",(0,t.jsxs)(n.p,{children:["My ",(0,t.jsx)(n.a,{href:"https://github.com/stanch/zipper#zipper--an-implementation-of-huets-zipper",children:"zipper library"}),"\nprovides a few useful movements and operations. Just like optics, it\u2019s rather generic and flexible.\nThe zipper can operate on any type, as long as an instance of the ",(0,t.jsx)(n.code,{children:"Unzip"})," typeclass is available,\nwhich can be automatically derived in many cases.\n(",(0,t.jsxs)(n.em,{children:["Note that the derivation of ",(0,t.jsx)(n.code,{children:"Unzip"})," for SVG can be found\n",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/svg/api/BaseSvgApi.scala",children:"here"}),"."]}),")"]}),"\n",(0,t.jsx)(n.p,{children:"Consider a simple XML tree:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Data.simpleXml\n// res27: xml.Node = \n\ndiagram(Data.simpleXml).render("simpleXml")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"simpleXml",src:i(4792).A+"",width:"1277",height:"703"})}),"\n",(0,t.jsx)(n.p,{children:"When we wrap a Zipper around this tree, it does not look very interesting yet:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'import zipper.Zipper\n\nval zipper1 = Zipper(Data.simpleXml)\n// zipper1: Zipper[xml.Node] = Zipper(List(),,List(),None)\n\n(diagram(Data.simpleXml) + diagram(zipper1)).render("zipper1")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper1",src:i(3618).A+"",width:"1321",height:"872"})}),"\n",(0,t.jsx)(n.p,{children:"We can see that it just points to the original tree.\nIn this case the focus is the root of the tree, which has no siblings,\nand the parent zipper does not exist, since we are at the top level."}),"\n",(0,t.jsx)(n.p,{children:"To move down the tree, we \u201cunzip\u201d it, separating the child nodes into\nthe focused node and its left and right siblings:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val zipper2 = zipper1.moveDownLeft\n// zipper2: Zipper[xml.Node] = Zipper(List(),,List(, , ),Some(Zipper(List(),,List(),None)))\n\n(diagram(zipper1) + diagram(zipper2)).render("zipper1+2")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper1+2",src:i(2385).A+"",width:"1366",height:"1042"})}),"\n",(0,t.jsx)(n.p,{children:"The new Zipper links to the old one,\nwhich will allow us to return to the root of the tree when we are done applying changes.\nThis link however prevents us from seeing the picture clearly.\nLet\u2019s look at the second zipper alone:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'diagram(zipper2).render("zipper2b")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper2b",src:i(5823).A+"",width:"1178",height:"872"})}),"\n",(0,t.jsxs)(n.p,{children:["Great! We have ",(0,t.jsx)(n.code,{children:"2"})," in focus and ",(0,t.jsx)(n.code,{children:"3, 4, 5"})," as right siblings. What happens if we move right a bit?"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val zipper3 = zipper2.moveRightBy(2)\n// zipper3: Zipper[xml.Node] = Zipper(List(, ),,List(),Some(Zipper(List(),,List(),None)))\n\ndiagram(zipper3).render("zipper3")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper3",src:i(1248).A+"",width:"1113",height:"872"})}),"\n",(0,t.jsx)(n.p,{children:"This is interesting! Notice that the left siblings are \u201cinverted\u201d.\nThis allows to move left and right in constant time, because the sibling\nadjacent to the focus is always at the head of the list."}),"\n",(0,t.jsx)(n.p,{children:"This also allows us to insert new siblings easily:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val zipper4 = zipper3.insertLeft()\n// zipper4: Zipper[xml.Node] = Zipper(List(, , ),,List(),Some(Zipper(List(),,List(),None)))\n\ndiagram(zipper4).render("zipper4")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper4",src:i(4095).A+"",width:"1235",height:"872"})}),"\n",(0,t.jsx)(n.p,{children:"And, as you might know, we can delete nodes and update the focus:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val zipper5 = zipper4.deleteAndMoveRight.set()\n// zipper5: Zipper[xml.Node] = Zipper(List(, , ),,List(),Some(Zipper(List(),,List(),None)))\n\ndiagram(zipper5).render("zipper5")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper5",src:i(5078).A+"",width:"556",height:"667"})}),"\n",(0,t.jsx)(n.p,{children:"Finally, when we move up, the siblings at the current level are \u201czipped\u201d\ntogether and their parent node is updated:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val zipper6 = zipper5.moveUp\n// zipper6: Zipper[xml.Node] = Zipper(List(),,List(),None)\n\ndiagram(zipper6).render("zipper6")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper6",src:i(6317).A+"",width:"794",height:"703"})}),"\n",(0,t.jsxs)(n.p,{children:["When we are done editing, the ",(0,t.jsx)(n.code,{children:".commit"})," shorthand can be used for going\nall the way up (applying all the changes) and returning the focus.\nNotice how all the unchanged nodes are shared between the old and the new XML."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val notSoSimpleXml = zipper6.commit\n// notSoSimpleXml: xml.Node = \n\n(diagram(Data.simpleXml) + diagram(notSoSimpleXml)).render("notSoSimpleXml")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"notSoSimpleXml",src:i(4259).A+"",width:"1560",height:"703"})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsxs)(n.em,{children:["Using an XML zipper, a determined reader can easily implement advanced lenses,\nsuch as ",(0,t.jsx)(n.code,{children:"Optics.collectFirst"}),", ",(0,t.jsx)(n.code,{children:"Optics.collectLeftByKey"}),", etc, all found\n",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/util/Optics.scala",children:"here"}),"."]})}),"\n",(0,t.jsx)(n.p,{children:"To conclude, here is an animation of a zipper and the tree it operates on\n(from my previous talk), produced (as we know now) not without zippers\u2019 help:"}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"tree+zipper",src:i(1864).A+"",width:"1241",height:"690"})}),"\n",(0,t.jsxs)(n.p,{children:["That\u2019s all! Thank you for reading this far.\nI hope you are leaving this page with some great ",(0,t.jsx)(n.code,{children:"reftree"})," use-cases in mind :)"]})]})}function g(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(h,{...e})}):h(e)}},9696:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/queue-668a708310e8e78475fbc9b757c69eb3.gif"},1864:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/tree+zipper-b1cf73da287eda26f64b667ce8dcf311.gif"},9341:(e,n,i)=>{i.d(n,{A:()=>t});const t="data:image/png;base64,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"},108:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/bob-6e8dad95c440d488af22193a37ae0d7f.png"},1842:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edge-0a8b76cae995e0a8c485063cb500de8a.png"},3959:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edgePath-e11472fa0a55e29fd9de9876ff597ceb.png"},3135:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edgePathElement-8070f24cd6faf257dbf2357bbc26ab75.png"},8908:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edgePathString-e95d3068168658ba325a9e873dbf2612.png"},7274:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edgePolyline-7c4044c84e38ce242eefda017e1e188c.png"},8914:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edges-100-9cbc68a0debf543244c33b7d26b22709.gif"},5047:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edges-4-788922eab8ebbf8f1d0ffaa9a071132b.gif"},2634:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/generic-2c235d68ea9ba3134644dfbbfcfc8d0e.png"},4259:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/notSoSimpleXml-d176756e1aae1946e6d6fe906c9d9bbc.png"},4447:(e,n,i)=>{i.d(n,{A:()=>t});const t="data:image/png;base64,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"},4967:(e,n,i)=>{i.d(n,{A:()=>t});const t="data:image/png;base64,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"},7692:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/points-5c9c6c00db5175c0d948a09163a40637.png"},5846:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/polylines-00e6358bcbdecbf9807c0bc2ade2d3f9.png"},3342:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/reftree-eb6c2fd2b4464cf68d9c3fe04e8ad231.png"},4792:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/simpleXml-33e8a5f26cea7dd70c3808d86690488b.png"},9221:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/x+y-c7fbf58ef886ad0cf3d32f18259c15be.png"},2385:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper1+2-bc931dc2a9a17d909a27f5ae5d1e095b.png"},3618:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper1-5f0e8ccac0548193671b75d4f96125e4.png"},5823:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper2b-b4d938aec16be4bc5d0601737ed9bd53.png"},1248:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper3-25b272311b7ec6ce2e1b0ce993d16055.png"},4095:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper4-cbb2cf5c5de815041892e7f3b09703f8.png"},5078:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper5-56ccb910b4b659418162c70c7d32c364.png"},6317:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper6-5e74a23116f43ca88bf512b027cbdd9e.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunkreftree=self.webpackChunkreftree||[]).push([[672],{8598:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>d,contentTitle:()=>l,default:()=>g,frontMatter:()=>o,metadata:()=>c,toc:()=>p});var t=i(4848),s=i(8453),a=i(3554),r=i.n(a);const o={sidebar_position:1,description:"A journey deep inside reftree\u2019s animations feature, showing how some of functional programming techniques and concepts can be applied to produce visualizations of themselves."},l="Visualize your data structures!",c={id:"talks/Visualize",title:"Visualize your data structures!",description:"A journey deep inside reftree\u2019s animations feature, showing how some of functional programming techniques and concepts can be applied to produce visualizations of themselves.",source:"@site/../site-gen/target/mdoc/talks/Visualize.md",sourceDirName:"talks",slug:"/talks/Visualize",permalink:"/reftree/docs/talks/Visualize",draft:!1,unlisted:!1,editUrl:"https://github.com/stanch/reftree/tree/main/docs/../site-gen/target/mdoc/talks/Visualize.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1,description:"A journey deep inside reftree\u2019s animations feature, showing how some of functional programming techniques and concepts can be applied to produce visualizations of themselves."},sidebar:"mainSidebar",previous:{title:"Unzipping Immutability",permalink:"/reftree/docs/talks/Immutability"}},d={},p=[{value:"Introducing reftree",id:"introducing-reftree",level:2},{value:"Inside reftree",id:"inside-reftree",level:2},{value:"Functional animation",id:"functional-animation",level:2},{value:"Zipping it up",id:"zipping-it-up",level:2}];function h(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",img:"img",li:"li",p:"p",pre:"pre",ul:"ul",...(0,s.R)(),...e.components},{Details:a}=n;return a||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"visualize-your-data-structures",children:"Visualize your data structures!"}),"\n",(0,t.jsx)(n.p,{children:"This page contains the materials for my talk \u201cVisualize your data structures!\u201d."}),"\n","\n",(0,t.jsx)(r(),{controls:!0,style:{marginBottom:"1em"},url:"https://www.youtube.com/watch?v=yoayLNPTESk"}),"\n",(0,t.jsxs)(a,{children:[(0,t.jsx)("summary",{children:"Older videos"}),(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["ScalaDays Chicago, April 2017: ",(0,t.jsx)(n.a,{href:"https://www.youtube.com/watch?v=6mWaqGHeg3g",children:"https://www.youtube.com/watch?v=6mWaqGHeg3g"})]}),"\n"]})]}),"\n",(0,t.jsx)(n.p,{children:"You can use this page in two ways:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"as a reference/refresher on the material covered in the talk;"}),"\n",(0,t.jsx)(n.li,{children:"as an interactive playground where you can try the same commands I presented."}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Throughout this page we will assume the following\ndeclarations (each section might add its own):"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"import reftree.core._\nimport reftree.diagram._\nimport reftree.render._\nimport reftree.geometry._\nimport reftree.svg.animation.Frame\nimport reftree.svg.XmlSvgApi\nimport reftree.svg.XmlSvgApi.svgUnzip\nimport reftree.contrib.XmlInstances._\nimport reftree.contrib.OpticInstances._\nimport reftree.contrib.ZipperInstances._\nimport reftree.contrib.ShapelessInstances._\nimport reftree.util.Optics\nimport reftree.demo.Data\nimport reftree.demo.Shortcuts\nimport scala.collection.immutable._\nimport java.nio.file.Paths\nimport Diagram.{sourceCodeCaption => diagram}\n"})}),"\n",(0,t.jsx)(n.p,{children:"To start an interactive session, just run"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"$ sbt demo\n@ render(List(1, 2, 3))\n"})}),"\n",(0,t.jsxs)(n.p,{children:["and open ",(0,t.jsx)(n.code,{children:"diagram.png"})," in your favorite image viewer (hopefully one that\nreloads images automatically on file change). You will also need to have\n",(0,t.jsx)(n.a,{href:"http://www.graphviz.org/",children:"GraphViz"})," installed. ",(0,t.jsx)(n.em,{children:"The interactive session\nalready has all the necessary imports in scope."})]}),"\n",(0,t.jsxs)(n.h2,{id:"introducing-reftree",children:["Introducing ",(0,t.jsx)(n.code,{children:"reftree"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'// extra declarations for this section\nval renderer = Renderer(\n renderingOptions = RenderingOptions(density = 100),\n directory = Paths.get(ImagePath, "visualize")\n)\nimport renderer._\n'})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.a,{href:"https://stanch.github.io/reftree",children:"reftree"})," is a library for visualizing Scala data structures."]}),"\n",(0,t.jsx)(n.p,{children:"Let\u2019s look at a quick usage example:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'case class Person(firstName: String, age: Int)\n\nval bob = Person("Bob", 42)\n// bob: Person = Person(firstName = "Bob", age = 42)\n\ndiagram(bob).render("bob")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"bob",src:i(108).A+"",width:"313",height:"363"})}),"\n",(0,t.jsx)(n.p,{children:"That\u2019s it! You can configure the visualization as you like:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"// render strings as a single box\nimport reftree.contrib.SimplifiedInstances.string\n"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'// rename the firstName field (pun not intended)\nimplicit val personConfig: ToRefTree.DerivationConfig[Person] =\n ToRefTree.DerivationConfig[Person]\n .tweakField("firstName", _.withName("name"))\n\ndiagram(bob).render("bob-simplified")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"bob-simplified",src:i(9341).A+"",width:"238",height:"364"})}),"\n",(0,t.jsxs)(n.p,{children:["There are various ways you can use ",(0,t.jsx)(n.code,{children:"reftree"}),":"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"improving the documentation of your projects;"}),"\n",(0,t.jsx)(n.li,{children:"live-coding demos;"}),"\n",(0,t.jsx)(n.li,{children:"exploring how things work;"}),"\n",(0,t.jsx)(n.li,{children:"anywhere you need diagrams of your Scala data structures."}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["(",(0,t.jsx)(n.em,{children:"Incidentally, this talk is an example of all of the above."}),")"]}),"\n",(0,t.jsxs)(n.p,{children:["My previous ",(0,t.jsx)(n.code,{children:"reftree"}),"-powered ",(0,t.jsx)(n.a,{href:"/reftree/docs/talks/Immutability",children:"talk"})," focused on\nimmutable data and various ways it can be manipulated (I do recommend it)."]}),"\n",(0,t.jsxs)(n.p,{children:["Today I would like to take you on a journey deep inside ",(0,t.jsx)(n.code,{children:"reftree"})," itself,\nso that we can see how some of these techniques and concepts can be applied...\nto produce visualizations of themselves \u2014 using one of my favorite ",(0,t.jsx)(n.code,{children:"reftree"}),"\nfeatures: animations."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Animation\n .startWith(Queue(1, 2, 3))\n .repeat(3)(_.iterate(2)(q => q :+ (q.max + 1)).iterate(2)(_.tail))\n .build(Diagram.toStringCaption(_).withAnchor("queue"))\n .render("queue")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"queue",src:i(9696).A+"",width:"540",height:"922"})}),"\n",(0,t.jsxs)(n.h2,{id:"inside-reftree",children:["Inside ",(0,t.jsx)(n.code,{children:"reftree"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"// extra declarations for this section\nimport reftree.contrib.SimplifiedInstances.{option, seq, list}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["First, we need to grasp the basics of ",(0,t.jsx)(n.code,{children:"reftree"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["To visualize a value of some type ",(0,t.jsx)(n.code,{children:"A"}),", ",(0,t.jsx)(n.code,{children:"reftree"})," converts it into a data structure\ncalled ",(0,t.jsx)(n.code,{children:"RefTree"})," (surprise!), using a typeclass ",(0,t.jsx)(n.code,{children:"ToRefTree[A]"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["For case classes this is done automagically, using\n",(0,t.jsx)(n.a,{href:"https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#generic-representation-of-sealed-families-of-case-classes",children:(0,t.jsx)(n.em,{children:"shapeless"})}),".\n(",(0,t.jsxs)(n.em,{children:["If you are curious about the magic, take a look at ",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/core/GenericInstances.scala",children:"this file"}),"."]}),")\nGiven our friend ",(0,t.jsx)(n.code,{children:"bob"}),", ",(0,t.jsx)(n.em,{children:"shapeless"})," would provide a generic representation,\nwhich includes the field names (at the type level!) and the values (as a heterogeneous list):"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Shortcuts.generic(bob)\n// res2: shapeless.::[String with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged["firstName"], String], shapeless.::[Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged["age"], Int], shapeless.HNil]] = "Bob" :: 42 :: HNil\n\ndiagram(Shortcuts.generic(bob)).render("generic")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"generic",src:i(2634).A+"",width:"417",height:"460"})}),"\n",(0,t.jsxs)(n.p,{children:["This information is enough to auto-generate a ",(0,t.jsx)(n.code,{children:"RefTree"}),".\nNow, what does it look like? The best way to find out is to visualize a ",(0,t.jsx)(n.code,{children:"RefTree"}),"\nof a ",(0,t.jsx)(n.code,{children:"RefTree"}),"!"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'diagram(Shortcuts.refTree(bob)).render("reftree")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"reftree",src:i(3342).A+"",width:"1520",height:"836"})}),"\n",(0,t.jsxs)(n.p,{children:["As you can see, it contains values (",(0,t.jsx)(n.code,{children:"Val"}),") and references (",(0,t.jsx)(n.code,{children:"Ref"}),")."]}),"\n",(0,t.jsxs)(n.p,{children:["How do we get from ",(0,t.jsx)(n.code,{children:"RefTree"})," to an image though?\nThis is where ",(0,t.jsx)(n.a,{href:"http://www.graphviz.org/",children:"GraphViz"})," comes in.\nFrom a ",(0,t.jsx)(n.code,{children:"RefTree"})," we can obtain a graph definition that can be rendered by GraphViz:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Shortcuts.graph(bob).encode\n// res5: String = """digraph "Diagram" {\n// graph [ ranksep=0.8 bgcolor="#ffffff00" ]\n// node [ shape="plaintext" fontname="Source Code Pro" fontcolor="#000000ff" ]\n// edge [ arrowsize=0.7 color="#000000ff" ]\n// "-repl.MdocSession$MdocApp$Person342877447" [ id="-repl.MdocSession$MdocApp$Person342877447" label=<
Personnameage
·42
> ] [ color="#104e8bff" fontcolor="#104e8bff" ]\n// "-java.lang.String1172745511" [ id="-java.lang.String1172745511" label=<
"Bob"
> ] [ color="#104e8bff" fontcolor="#104e8bff" ]\n// "-repl.MdocSession$MdocApp$Person342877447":"0":"s" -> "-java.lang.String1172745511":"n":"n" [ id="-repl.MdocSession$MdocApp$Person342877447-0-java.lang.String1172745511" ] [ color="#104e8bff" ]\n// }"""\n'})}),"\n",(0,t.jsxs)(n.p,{children:["Going even further, we can ask GraphViz for an ",(0,t.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/Scalable_Vector_Graphics",children:"SVG"})," output:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Shortcuts.svg(bob)\n// res6: xml.Node = Diagram\x3c!-- -repl.MdocSession$MdocApp$Person342877447 --\x3e-repl.MdocSession$MdocApp$Person342877447Personnameage\xb742\x3c!-- -java.lang.String1172745511 --\x3e-java.lang.String1172745511"Bob"\x3c!-- -repl.MdocSession$MdocApp$Person342877447->-java.lang.String1172745511 --\x3e-repl.MdocSession$MdocApp$Person342877447:s->-java.lang.String1172745511:n\n'})}),"\n",(0,t.jsx)(n.p,{children:"At this point you might be guessing how we can use this as a basis for our animation approach.\nEvery state of a data structure will be a separate frame in the SVG format.\nHowever, an animation consisting of these frames alone would be too jumpy.\nWe need to add intermediate frames to smoothly \u201cmorph\u201d one frame into another.\nWith SVG being a vector format, this sounds simple.\nWe just have to individually morph different aspects of the image:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"graph node positions;"}),"\n",(0,t.jsx)(n.li,{children:"graph edges and their shapes;"}),"\n",(0,t.jsx)(n.li,{children:"colors;"}),"\n",(0,t.jsx)(n.li,{children:"stroke thickness;"}),"\n",(0,t.jsx)(n.li,{children:"transparency."}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Ouch! A sane functional approach would definitely help here :)"}),"\n",(0,t.jsx)(n.h2,{id:"functional-animation",children:"Functional animation"}),"\n",(0,t.jsxs)(n.p,{children:["Let\u2019s start by introducing an abstraction for morphing, or, in other words,\ninterpolating things of type ",(0,t.jsx)(n.code,{children:"A"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"trait Interpolation[A] {\n def apply(left: A, right: A, time: Double): A\n def sample(left: A, right: A, n: Int, inclusive: Boolean = true): Seq[A]\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["(",(0,t.jsxs)(n.em,{children:["If you are curious, ",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/geometry/Interpolation.scala",children:"here is the actual implementation"}),"."]}),")"]}),"\n",(0,t.jsxs)(n.p,{children:["Once we have an instance of ",(0,t.jsx)(n.code,{children:"Interpolation[xml.Node]"}),", we can generate\nas many intermediate frames as we want! But how do we construct this instance?"]}),"\n",(0,t.jsxs)(n.p,{children:["Consider a lowly floating point number (it can represent an ",(0,t.jsx)(n.em,{children:"x"})," coordinate of some element in our SVG, for example).\nThere is an obvious way to implement ",(0,t.jsx)(n.code,{children:"Interpolation[Double]"}),", which ",(0,t.jsx)(n.code,{children:"reftree"})," already defines as ",(0,t.jsx)(n.code,{children:"Interpolation.double"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val numbers = Interpolation.double.sample(0, 10, 5).toList\n// numbers: List[Double] = List(0.0, 2.5, 5.0, 7.5, 10.0)\n\ndiagram(numbers).render("numbers")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"numbers",src:i(4447).A+"",width:"376",height:"193"})}),"\n",(0,t.jsx)(n.p,{children:"Now if you think about a point in 2D space, it\u2019s just two numbers joined together:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val point = Point(0, 10)\n// point: Point = Point(x = 0.0, y = 10.0)\n\ndiagram(point).render("point")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"point",src:i(4967).A+"",width:"226",height:"231"})}),"\n",(0,t.jsx)(n.p,{children:"Can we use the number interpolation to interpolate these two numbers?\nTo answer this question, let\u2019s introduce more abstraction\n(in a great tradition of functional programming)."}),"\n",(0,t.jsxs)(n.p,{children:["A lens ",(0,t.jsx)(n.code,{children:"Lens[A, B]"})," is something that can \u201cfocus\u201d on a piece of data of type ",(0,t.jsx)(n.code,{children:"B"}),"\ninside a data structure of type ",(0,t.jsx)(n.code,{children:"A"})," and provide read-write access to it.\nWe will use the excellent ",(0,t.jsxs)(n.a,{href:"https://github.com/julien-truffaut/Monocle",children:[(0,t.jsx)(n.em,{children:"Monocle"})," library"]}),"\nto create lenses and other optics along the way:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'import monocle.macros.GenLens\n\nval x = GenLens[Point](_.x)\n// x: monocle.package.Lens[Point, Double] = repl.MdocSession$MdocApp$$anon$1@1b1555ca\nval y = GenLens[Point](_.y)\n// y: monocle.package.Lens[Point, Double] = repl.MdocSession$MdocApp$$anon$2@1ba92b68\n\n(diagram(OpticFocus(x, point)).toNamespace("x") +\n diagram(OpticFocus(y, point)).toNamespace("y")).render("x+y")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"x+y",src:i(9221).A+"",width:"580",height:"231"})}),"\n",(0,t.jsx)(n.p,{children:"Lenses provide several methods to manipulate data:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"x.get(point)\n// res10: Double = 0.0\ny.set(20)(point)\n// res11: Point = Point(x = 0.0, y = 20.0)\ny.modify(_ + 20)(point)\n// res12: Point = Point(x = 0.0, y = 30.0)\n"})}),"\n",(0,t.jsxs)(n.p,{children:["If we can read and write each coordinate field, we can interpolate them separately\nand update the point field by field.\nWe do this by piping ",(0,t.jsx)(n.code,{children:"Interpolation.double"})," through ",(0,t.jsx)(n.code,{children:"x"})," and ",(0,t.jsx)(n.code,{children:"y"})," lenses\nand combining the resulting interpolations:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val pointInterpolation = (\n x.interpolateWith(Interpolation.double) +\n y.interpolateWith(Interpolation.double))\n// pointInterpolation: Interpolation[Point] = reftree.geometry.Interpolation$$anonfun$apply$4@2bccedad\n\nval points = pointInterpolation.sample(Point(0, 0), Point(10, 20), 5).toList\n// points: List[Point] = List(\n// Point(x = 0.0, y = 0.0),\n// Point(x = 2.5, y = 5.0),\n// Point(x = 5.0, y = 10.0),\n// Point(x = 7.5, y = 15.0),\n// Point(x = 10.0, y = 20.0)\n// )\n\ndiagram(points).render("points")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"points",src:i(7692).A+"",width:"1176",height:"363"})}),"\n",(0,t.jsxs)(n.p,{children:["Of course, ",(0,t.jsx)(n.code,{children:"reftree"})," already defines this as ",(0,t.jsx)(n.code,{children:"Point.interpolation"}),"."]}),"\n",(0,t.jsx)(n.p,{children:"Using the same approach, we can build a polyline interpolator\n(assuming the polylines being interpolated consist of equal number of points):"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Data.polyline1\n// res14: Polyline = Polyline(\n// points = List(Point(x = 0.0, y = 10.0), Point(x = 10.0, y = 20.0))\n// )\nData.polyline2\n// res15: Polyline = Polyline(\n// points = List(Point(x = 20.0, y = 30.0), Point(x = 40.0, y = 50.0))\n// )\n\nval polylineInterpolation = (GenLens[Polyline](_.points)\n .interpolateEachWith(Point.interpolation))\n// polylineInterpolation: Interpolation[Polyline] = reftree.geometry.Interpolation$$anonfun$apply$4@8ad0725\n\nval polylines = polylineInterpolation.sample(Data.polyline1, Data.polyline2, 3).toList\n// polylines: List[Polyline] = List(\n// Polyline(points = List(Point(x = 0.0, y = 10.0), Point(x = 10.0, y = 20.0))),\n// Polyline(points = List(Point(x = 10.0, y = 20.0), Point(x = 25.0, y = 35.0))),\n// Polyline(points = List(Point(x = 20.0, y = 30.0), Point(x = 40.0, y = 50.0)))\n// )\n\ndiagram(polylines).render("polylines")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"polylines",src:i(5846).A+"",width:"1491",height:"664"})}),"\n",(0,t.jsxs)(n.p,{children:["We are finally ready to implement our first substantial interpolator: one that morphs graph edges.\n",(0,t.jsxs)(n.em,{children:["The following approach is inspired by Mike Bostock\u2019s ",(0,t.jsx)(n.a,{href:"https://bl.ocks.org/mbostock/3916621",children:"path tween"}),",\nhowever ",(0,t.jsx)(n.code,{children:"reftree"})," puts more emphasis on types and even includes its own\n",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/geometry/Path.scala",children:"SVG path parser and simplification algorithm"}),"."]})]}),"\n",(0,t.jsx)(n.p,{children:"The resulting animation should look like this:"}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edges-100",src:i(8914).A+"",width:"361",height:"194"})}),"\n",(0,t.jsxs)(n.p,{children:["An edge is drawn with an ",(0,t.jsx)(n.a,{href:"https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths",children:"SVG path"}),",\nwhich consists of several commands, e.g. \u201cmove to\u201d, \u201cline to\u201d, \u201cbezier curve to\u201d.\nHere is a minimized SVG snippet for an actual edge:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Data.edge1\n// res17: xml.Node = \n\ndiagram(Data.edge1).render("edge")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edge",src:i(1842).A+"",width:"1365",height:"703"})}),"\n",(0,t.jsxs)(n.p,{children:["As you can see, the commands themselves are given in the ",(0,t.jsx)(n.code,{children:"d"})," attribute inside the ",(0,t.jsx)(n.code,{children:"path"})," element\nin a rather obscure format. Luckily, we have lenses and other optics at our disposal\nto plumb through this mess."]}),"\n",(0,t.jsxs)(n.p,{children:["First, let\u2019s get to the ",(0,t.jsx)(n.code,{children:"path"})," element. ",(0,t.jsx)(n.code,{children:"reftree"})," implements a few things that will help us:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"XmlSvgApi"}),", an implementation of several useful SVG operations for ",(0,t.jsx)(n.em,{children:"scala-xml"}),".\nIn particular, if offers a CSS selector-like method for matching elements of certain type and/or class."]}),"\n",(0,t.jsxs)(n.li,{children:["An optic that focuses on an element deep inside XML or any other recursive data structure: ",(0,t.jsx)(n.code,{children:"Optics.collectFirst"}),".\nIt is actually an ",(0,t.jsx)(n.code,{children:"Optional"}),", not a ",(0,t.jsx)(n.code,{children:"Lens"}),", since the element might be missing."]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val edgePathElement = Optics.collectFirst(XmlSvgApi.select("path"))\n// edgePathElement: monocle.package.Optional[xml.Node, xml.Node] = monocle.Optional$$anon$6@3927a8ff\n\ndiagram(OpticFocus(edgePathElement, Data.edge1)).render("edgePathElement")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edgePathElement",src:i(3135).A+"",width:"1408",height:"703"})}),"\n",(0,t.jsxs)(n.p,{children:["Next, we need to \u201cdescend\u201d to the ",(0,t.jsx)(n.code,{children:"d"})," attribute. Here is where optics really shine:\nwe can compose ",(0,t.jsx)(n.code,{children:"Optional[A, B]"})," with ",(0,t.jsx)(n.code,{children:"Optional[B, C]"})," to get an ",(0,t.jsx)(n.code,{children:"Optional[A, C]"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val d = XmlSvgApi.attr("d")\n// d: monocle.package.Optional[xml.Node, String] = monocle.POptional$$anon$1@6b0726d8\nval edgePathString = edgePathElement composeOptional d\n// edgePathString: monocle.POptional[xml.Node, xml.Node, String, String] = monocle.POptional$$anon$1@276a0f90\n\ndiagram(OpticFocus(edgePathString, Data.edge1)).render("edgePathString")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edgePathString",src:i(8908).A+"",width:"1403",height:"703"})}),"\n",(0,t.jsx)(n.p,{children:"Next, we will use an isomorphism, another kind of optic, to view\nthe string as a nice case class:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Path.stringIso\n// res21: monocle.package.Iso[String, Path] = monocle.PIso$$anon$9@471ea077\n\nval edgePath = edgePathString composeIso Path.stringIso\n// edgePath: monocle.POptional[xml.Node, xml.Node, Path, Path] = monocle.POptional$$anon$1@43fa983b\n\ndiagram(edgePath.getOption(Data.edge1)).render("edgePath")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edgePath",src:i(3959).A+"",width:"1435",height:"701"})}),"\n",(0,t.jsxs)(n.p,{children:["And finally, another isomorphism takes us from a ",(0,t.jsx)(n.code,{children:"Path"})," to its sampled representation\nas a ",(0,t.jsx)(n.code,{children:"Polyline"}),". (",(0,t.jsx)(n.em,{children:"Purists will say that this is not really an isomorphism because\nit\u2019s not reversible, but with a lot of points you can get pretty close ;)"}),")"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Path.polylineIso(points = 4)\n// res23: monocle.package.Iso[Path, Polyline] = monocle.PIso$$anon$9@1199577a\n\ndef edgePolyline(points: Int) = edgePath composeIso Path.polylineIso(points)\n\ndiagram(edgePolyline(4).getOption(Data.edge1)).render("edgePolyline")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edgePolyline",src:i(7274).A+"",width:"1727",height:"532"})}),"\n",(0,t.jsx)(n.p,{children:"Let\u2019s interpolate!"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'def edgeInterpolation(points: Int) = edgePolyline(points).interpolateWith(Polyline.interpolation)\n\ndef edges(points: Int, frames: Int) = (Data.edge1 +:\n edgeInterpolation(points).sample(Data.edge1, Data.edge2, frames, inclusive = false) :+\n Data.edge2)\n\nAnimatedGifRenderer.renderFrames(\n edges(4, 4).map(Frame(_)),\n Paths.get(ImagePath, "visualize", "edges-4.gif"),\n RenderingOptions(density = 200),\n AnimationOptions(framesPerSecond = 1)\n)\n\nAnimatedGifRenderer.renderFrames(\n edges(100, 32).map(Frame(_)),\n Paths.get(ImagePath, "visualize", "edges-100.gif"),\n RenderingOptions(density = 200),\n AnimationOptions(framesPerSecond = 8)\n)\n'})}),"\n",(0,t.jsx)(n.p,{children:"With 4 points and 4 frames:"}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edges-4",src:i(5047).A+"",width:"361",height:"194"})}),"\n",(0,t.jsx)(n.p,{children:"With 100 points and 32 frames:"}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"edges-100",src:i(8914).A+"",width:"361",height:"194"})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsxs)(n.em,{children:["Interpolating the entire image is left as an exercise for the reader,\nalthough the impatient will find the complete implementation\n",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/svg/animation/GraphInterpolation.scala",children:"here"}),"."]})}),"\n",(0,t.jsxs)(n.p,{children:["Notice that we never touched XML directly.\nIn fact, equipped with the same set of optics for another format or representation,\nwe would be able to operate on it without changing the code too much.\nCase in point: ",(0,t.jsx)(n.code,{children:"reftree"})," supports both\n",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/jvm/src/main/scala/reftree/svg/XmlSvgApi.scala",children:(0,t.jsx)(n.em,{children:"scala-xml"})})," and\n",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/js/src/main/scala/reftree/svg/DomSvgApi.scala",children:(0,t.jsx)(n.em,{children:"scala-js-dom"})})," (for Scala.js),\nwith only 50 lines of implementation-specific code for each backend.\nThis goes to show the flexibility and usefulness of optics."]}),"\n",(0,t.jsx)(n.h2,{id:"zipping-it-up",children:"Zipping it up"}),"\n",(0,t.jsxs)(n.p,{children:["In the previous section we saw ",(0,t.jsx)(n.code,{children:"Optics.collectFirst"})," \u2014 an optic that is able to perform\nmodifications deep inside SVG. How do we go about implementing something like this,\nor, more generally, how do we edit recursive data structures such as XML?"]}),"\n",(0,t.jsxs)(n.p,{children:["This solution is called a \u201cZipper\u201d, and was introduced by G\xe9rard Huet in 1997.\nIt consists of a \u201ccursor\u201d pointing to a location anywhere in a tree \u2014 \u201ccurrent focus\u201d.\nThe cursor can be moved freely with operations like ",(0,t.jsx)(n.code,{children:"moveDownLeft"}),", ",(0,t.jsx)(n.code,{children:"moveRight"}),", ",(0,t.jsx)(n.code,{children:"moveUp"}),", etc.\nCurrent focus can be updated, deleted, or new nodes can be inserted to its left or right.\nZippers are immutable, and every operation returns a new Zipper.\nAll the changes made to the tree can be committed, yielding a new modified version of the original tree."]}),"\n",(0,t.jsxs)(n.p,{children:["My ",(0,t.jsx)(n.a,{href:"https://github.com/stanch/zipper#zipper--an-implementation-of-huets-zipper",children:"zipper library"}),"\nprovides a few useful movements and operations. Just like optics, it\u2019s rather generic and flexible.\nThe zipper can operate on any type, as long as an instance of the ",(0,t.jsx)(n.code,{children:"Unzip"})," typeclass is available,\nwhich can be automatically derived in many cases.\n(",(0,t.jsxs)(n.em,{children:["Note that the derivation of ",(0,t.jsx)(n.code,{children:"Unzip"})," for SVG can be found\n",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/svg/api/BaseSvgApi.scala",children:"here"}),"."]}),")"]}),"\n",(0,t.jsx)(n.p,{children:"Consider a simple XML tree:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Data.simpleXml\n// res27: xml.Node = \n\ndiagram(Data.simpleXml).render("simpleXml")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"simpleXml",src:i(4792).A+"",width:"1277",height:"703"})}),"\n",(0,t.jsx)(n.p,{children:"When we wrap a Zipper around this tree, it does not look very interesting yet:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'import zipper.Zipper\n\nval zipper1 = Zipper(Data.simpleXml)\n// zipper1: Zipper[xml.Node] = Zipper(List(),,List(),None)\n\n(diagram(Data.simpleXml) + diagram(zipper1)).render("zipper1")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper1",src:i(3618).A+"",width:"1321",height:"872"})}),"\n",(0,t.jsx)(n.p,{children:"We can see that it just points to the original tree.\nIn this case the focus is the root of the tree, which has no siblings,\nand the parent zipper does not exist, since we are at the top level."}),"\n",(0,t.jsx)(n.p,{children:"To move down the tree, we \u201cunzip\u201d it, separating the child nodes into\nthe focused node and its left and right siblings:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val zipper2 = zipper1.moveDownLeft\n// zipper2: Zipper[xml.Node] = Zipper(List(),,List(, , ),Some(Zipper(List(),,List(),None)))\n\n(diagram(zipper1) + diagram(zipper2)).render("zipper1+2")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper1+2",src:i(2385).A+"",width:"1366",height:"1042"})}),"\n",(0,t.jsx)(n.p,{children:"The new Zipper links to the old one,\nwhich will allow us to return to the root of the tree when we are done applying changes.\nThis link however prevents us from seeing the picture clearly.\nLet\u2019s look at the second zipper alone:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'diagram(zipper2).render("zipper2b")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper2b",src:i(5823).A+"",width:"1178",height:"872"})}),"\n",(0,t.jsxs)(n.p,{children:["Great! We have ",(0,t.jsx)(n.code,{children:"2"})," in focus and ",(0,t.jsx)(n.code,{children:"3, 4, 5"})," as right siblings. What happens if we move right a bit?"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val zipper3 = zipper2.moveRightBy(2)\n// zipper3: Zipper[xml.Node] = Zipper(List(, ),,List(),Some(Zipper(List(),,List(),None)))\n\ndiagram(zipper3).render("zipper3")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper3",src:i(1248).A+"",width:"1113",height:"872"})}),"\n",(0,t.jsx)(n.p,{children:"This is interesting! Notice that the left siblings are \u201cinverted\u201d.\nThis allows to move left and right in constant time, because the sibling\nadjacent to the focus is always at the head of the list."}),"\n",(0,t.jsx)(n.p,{children:"This also allows us to insert new siblings easily:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val zipper4 = zipper3.insertLeft()\n// zipper4: Zipper[xml.Node] = Zipper(List(, , ),,List(),Some(Zipper(List(),,List(),None)))\n\ndiagram(zipper4).render("zipper4")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper4",src:i(4095).A+"",width:"1235",height:"872"})}),"\n",(0,t.jsx)(n.p,{children:"And, as you might know, we can delete nodes and update the focus:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val zipper5 = zipper4.deleteAndMoveRight.set()\n// zipper5: Zipper[xml.Node] = Zipper(List(, , ),,List(),Some(Zipper(List(),,List(),None)))\n\ndiagram(zipper5).render("zipper5")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper5",src:i(5078).A+"",width:"556",height:"667"})}),"\n",(0,t.jsx)(n.p,{children:"Finally, when we move up, the siblings at the current level are \u201czipped\u201d\ntogether and their parent node is updated:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val zipper6 = zipper5.moveUp\n// zipper6: Zipper[xml.Node] = Zipper(List(),,List(),None)\n\ndiagram(zipper6).render("zipper6")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"zipper6",src:i(6317).A+"",width:"794",height:"703"})}),"\n",(0,t.jsxs)(n.p,{children:["When we are done editing, the ",(0,t.jsx)(n.code,{children:".commit"})," shorthand can be used for going\nall the way up (applying all the changes) and returning the focus.\nNotice how all the unchanged nodes are shared between the old and the new XML."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'val notSoSimpleXml = zipper6.commit\n// notSoSimpleXml: xml.Node = \n\n(diagram(Data.simpleXml) + diagram(notSoSimpleXml)).render("notSoSimpleXml")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"notSoSimpleXml",src:i(4259).A+"",width:"1560",height:"703"})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsxs)(n.em,{children:["Using an XML zipper, a determined reader can easily implement advanced lenses,\nsuch as ",(0,t.jsx)(n.code,{children:"Optics.collectFirst"}),", ",(0,t.jsx)(n.code,{children:"Optics.collectLeftByKey"}),", etc, all found\n",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/util/Optics.scala",children:"here"}),"."]})}),"\n",(0,t.jsx)(n.p,{children:"To conclude, here is an animation of a zipper and the tree it operates on\n(from my previous talk), produced (as we know now) not without zippers\u2019 help:"}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"tree+zipper",src:i(1864).A+"",width:"1241",height:"690"})}),"\n",(0,t.jsxs)(n.p,{children:["That\u2019s all! Thank you for reading this far.\nI hope you are leaving this page with some great ",(0,t.jsx)(n.code,{children:"reftree"})," use-cases in mind :)"]})]})}function g(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(h,{...e})}):h(e)}},9696:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/queue-668a708310e8e78475fbc9b757c69eb3.gif"},1864:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/tree+zipper-b1cf73da287eda26f64b667ce8dcf311.gif"},9341:(e,n,i)=>{i.d(n,{A:()=>t});const t="data:image/png;base64,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"},108:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/bob-6e8dad95c440d488af22193a37ae0d7f.png"},1842:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edge-0a8b76cae995e0a8c485063cb500de8a.png"},3959:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edgePath-e11472fa0a55e29fd9de9876ff597ceb.png"},3135:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edgePathElement-8070f24cd6faf257dbf2357bbc26ab75.png"},8908:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edgePathString-e95d3068168658ba325a9e873dbf2612.png"},7274:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edgePolyline-7c4044c84e38ce242eefda017e1e188c.png"},8914:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edges-100-9cbc68a0debf543244c33b7d26b22709.gif"},5047:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/edges-4-788922eab8ebbf8f1d0ffaa9a071132b.gif"},2634:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/generic-2c235d68ea9ba3134644dfbbfcfc8d0e.png"},4259:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/notSoSimpleXml-d176756e1aae1946e6d6fe906c9d9bbc.png"},4447:(e,n,i)=>{i.d(n,{A:()=>t});const t="data:image/png;base64,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"},4967:(e,n,i)=>{i.d(n,{A:()=>t});const t="data:image/png;base64,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"},7692:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/points-5c9c6c00db5175c0d948a09163a40637.png"},5846:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/polylines-00e6358bcbdecbf9807c0bc2ade2d3f9.png"},3342:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/reftree-89a18ab93544f7f9592b99af3dca2ca0.png"},4792:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/simpleXml-33e8a5f26cea7dd70c3808d86690488b.png"},9221:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/x+y-c7fbf58ef886ad0cf3d32f18259c15be.png"},2385:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper1+2-bc931dc2a9a17d909a27f5ae5d1e095b.png"},3618:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper1-5f0e8ccac0548193671b75d4f96125e4.png"},5823:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper2b-b4d938aec16be4bc5d0601737ed9bd53.png"},1248:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper3-25b272311b7ec6ce2e1b0ce993d16055.png"},4095:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper4-cbb2cf5c5de815041892e7f3b09703f8.png"},5078:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper5-56ccb910b4b659418162c70c7d32c364.png"},6317:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/zipper6-5e74a23116f43ca88bf512b027cbdd9e.png"}}]); \ No newline at end of file diff --git a/assets/js/b3d9ed0f.5a9d3f91.js b/assets/js/b3d9ed0f.69be21ab.js similarity index 99% rename from assets/js/b3d9ed0f.5a9d3f91.js rename to assets/js/b3d9ed0f.69be21ab.js index f37531f..8272b2d 100644 --- a/assets/js/b3d9ed0f.5a9d3f91.js +++ b/assets/js/b3d9ed0f.69be21ab.js @@ -1 +1 @@ -"use strict";(self.webpackChunkreftree=self.webpackChunkreftree||[]).push([[814],{6444:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>d,contentTitle:()=>c,default:()=>m,frontMatter:()=>l,metadata:()=>o,toc:()=>h});var s=a(4848),r=a(8453),t=a(3554),i=a.n(t);const l={sidebar_position:1,description:"This talk takes advantage of reftree to observe several immutable data structures in action and uncover their inner beauty. Having surfaced immutability\u2019s crucial tricks, we move our focus to lenses and zippers \u2014 handy tools that combine the convenience of the \u201cmutable world\u201d with the expressiveness of functional programming."},c="Unzipping Immutability",o={id:"talks/Immutability",title:"Unzipping Immutability",description:"This talk takes advantage of reftree to observe several immutable data structures in action and uncover their inner beauty. Having surfaced immutability\u2019s crucial tricks, we move our focus to lenses and zippers \u2014 handy tools that combine the convenience of the \u201cmutable world\u201d with the expressiveness of functional programming.",source:"@site/../site-gen/target/mdoc/talks/Immutability.md",sourceDirName:"talks",slug:"/talks/Immutability",permalink:"/reftree/docs/talks/Immutability",draft:!1,unlisted:!1,editUrl:"https://github.com/stanch/reftree/tree/main/docs/../site-gen/target/mdoc/talks/Immutability.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1,description:"This talk takes advantage of reftree to observe several immutable data structures in action and uncover their inner beauty. Having surfaced immutability\u2019s crucial tricks, we move our focus to lenses and zippers \u2014 handy tools that combine the convenience of the \u201cmutable world\u201d with the expressiveness of functional programming."},sidebar:"mainSidebar",previous:{title:"Talks / Demos",permalink:"/reftree/docs/talks/"},next:{title:"Visualize your data structures!",permalink:"/reftree/docs/talks/Visualize"}},d={},h=[{value:"Immutable data structures",id:"immutable-data-structures",level:2},{value:"Lists",id:"lists",level:3},{value:"Queues",id:"queues",level:3},{value:"Vectors",id:"vectors",level:3},{value:"Finger Trees",id:"finger-trees",level:3},{value:"Lenses",id:"lenses",level:2},{value:"Zippers",id:"zippers",level:2},{value:"Useful resources",id:"useful-resources",level:2},{value:"Books, papers and talks",id:"books-papers-and-talks",level:3},{value:"Scala libraries",id:"scala-libraries",level:3}];function p(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",img:"img",li:"li",p:"p",pre:"pre",ul:"ul",...(0,r.R)(),...e.components},{Details:t}=n;return t||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"unzipping-immutability",children:"Unzipping Immutability"}),"\n",(0,s.jsx)(n.p,{children:"This page contains the materials for my talk \u201cUnzipping Immutability\u201d."}),"\n","\n",(0,s.jsx)(i(),{controls:!0,style:{marginBottom:"1em"},url:"https://www.youtube.com/watch?v=dOj-wk5MQ3k"}),"\n",(0,s.jsxs)(t,{children:[(0,s.jsx)("summary",{children:"Older videos"}),(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["LX Scala, April 2016: ",(0,s.jsx)(n.a,{href:"https://vimeo.com/162214356",children:"https://vimeo.com/162214356"})]}),"\n",(0,s.jsxs)(n.li,{children:["Pixels Camp, October 2016: ",(0,s.jsx)(n.a,{href:"https://www.youtube.com/watch?v=yeMvhuD689A",children:"https://www.youtube.com/watch?v=yeMvhuD689A"})]}),"\n"]})]}),"\n",(0,s.jsx)(n.p,{children:"You can use this page in two ways:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"as a reference/refresher on the concepts covered in the talk;"}),"\n",(0,s.jsx)(n.li,{children:"as an interactive playground where you can try the same commands I presented."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Throughout this page we will assume the following\ndeclarations (each section might add its own):"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"import reftree.core._\nimport reftree.diagram._\nimport reftree.render._\nimport reftree.demo.Data._\nimport scala.collection.immutable._\nimport java.nio.file.Paths\nimport Diagram.{sourceCodeCaption => diagram}\n"})}),"\n",(0,s.jsx)(n.p,{children:"To start an interactive session, just run"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"$ sbt demo\n@ render(List(1, 2, 3))\n"})}),"\n",(0,s.jsxs)(n.p,{children:["and open ",(0,s.jsx)(n.code,{children:"diagram.png"})," in your favorite image viewer (hopefully one that\nreloads images automatically on file change). You will also need to have\n",(0,s.jsx)(n.a,{href:"http://www.graphviz.org/",children:"GraphViz"})," installed. ",(0,s.jsx)(n.em,{children:"The interactive session\nalready has all the necessary imports in scope."})]}),"\n",(0,s.jsx)(n.h2,{id:"immutable-data-structures",children:"Immutable data structures"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'// extra declarations for this section\nval renderer = Renderer(\n renderingOptions = RenderingOptions(density = 100),\n directory = Paths.get(ImagePath, "immutability")\n)\nimport renderer._\n'})}),"\n",(0,s.jsx)(n.h3,{id:"lists",children:"Lists"}),"\n",(0,s.jsx)(n.p,{children:"We\u2019ll start with one of the simplest structures: a list.\nIt consists of a number of cells pointing to each other:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val list = List(1, 2, 3)\n// list: List[Int] = List(1, 2, 3)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(list).render("list")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"list",src:a(8603).A+"",width:"322",height:"590"})}),"\n",(0,s.jsx)(n.p,{children:"Elements can be added to or removed from the front of the list with no effort,\nbecause we can share the same cells across several lists.\nThis would not be possible with a mutable list,\nsince modifying the shared part would modify every data structure making use of it."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val add = 0 :: list\n// add: List[Int] = List(0, 1, 2, 3)\nval remove = list.tail\n// remove: List[Int] = List(2, 3)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(list) + diagram(add) + diagram(remove)).render("lists")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"lists",src:a(2600).A+"",width:"455",height:"722"})}),"\n",(0,s.jsxs)(n.p,{children:["However we can\u2019t easily add elements at the end of the list, since the last cell\nis pointing to the empty list (",(0,s.jsx)(n.code,{children:"Nil"}),") and is immutable, i.e. cannot be changed.\nThus we are forced to create a new list every time:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(Animation\n .startWith(List(1))\n .iterate(_ :+ 2, _ :+ 3, _ :+ 4)\n .build()\n .render("list-append", tweakAnimation = _.withOnionSkinLayers(3)))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"list-append",src:a(2773).A+"",width:"675",height:"722"})}),"\n",(0,s.jsx)(n.p,{children:"This certainly does not look efficient compared to adding elements at the front:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(Animation\n .startWith(List(1))\n .iterate(2 :: _, 3 :: _, 4 :: _)\n .build()\n .render("list-prepend"))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"list-prepend",src:a(2515).A+"",width:"457",height:"722"})}),"\n",(0,s.jsx)(n.h3,{id:"queues",children:"Queues"}),"\n",(0,s.jsx)(n.p,{children:"If we want to add elements on both sides efficiently, we need a different data structure: a queue.\nThe queue below, also known as a \u201cBanker\u2019s Queue\u201d, has two lists: one for prepending and one for appending."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val queue1 = Queue(1, 2, 3)\n// queue1: Queue[Int] = Queue(1, 2, 3)\nval queue2 = (queue1 :+ 4).tail\n// queue2: Queue[Int] = Queue(2, 3, 4)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(queue1) + diagram(queue2)).render("queues", _.withVerticalSpacing(1.2))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"queues",src:a(5623).A+"",width:"607",height:"992"})}),"\n",(0,s.jsx)(n.p,{children:"This way we can add and remove elements very easily at both ends.\nExcept when we try to remove an element and the respective list is empty!\nIn this case the queue will rotate the other list to make use of its elements.\nAlthough this operation is expensive, the usage pattern intended for a queue\nmakes it rare enough to yield great average (\u201cammortized\u201d) performance:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(Animation\n .startWith(Queue(1, 2, 3))\n .repeat(3)(_.iterate(2)(q => q :+ (q.max + 1)).iterate(2)(_.tail))\n .build(Diagram.toStringCaption(_).withAnchor("queue"))\n .render("queue"))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"queue",src:a(9696).A+"",width:"540",height:"922"})}),"\n",(0,s.jsx)(n.h3,{id:"vectors",children:"Vectors"}),"\n",(0,s.jsxs)(n.p,{children:["One downside common to both lists and queues we saw before is that to get an element by index,\nwe need to potentially traverse the whole structure. A ",(0,s.jsx)(n.code,{children:"Vector"})," is a powerful data structure\naddressing this shortcoming and available in Scala (among other languages, like Clojure)."]}),"\n",(0,s.jsx)(n.p,{children:"Internally vectors utilize up to 6 layers of arrays, where 32 elements sit on the first layer,\n1024 \u2014 on the second, 32^3 \u2014 on the third, etc.\nTherefore getting any element by its index requires at most 6 pointer dereferences,\nwhich can be deemed constant time (yes, the trick is that the number of elements that can\nbe stored is limited by 2^31)."}),"\n",(0,s.jsx)(n.p,{children:"The internal 32-element arrays form the basic structural sharing blocks.\nFor small vectors they will be recreated on most operations:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val vector1 = (1 to 20).toVector\n// vector1: Vector[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)\nval vector2 = vector1 :+ 21\n// vector2: Vector[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(vector1) + diagram(vector2)).render("vectors", _.withVerticalSpacing(2))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"vectors",src:a(1999).A+"",width:"2022",height:"601"})}),"\n",(0,s.jsx)(n.p,{children:"However as more layers leap into action, a huge chunk of the data can be shared:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val vector3 = (1 to 100).toVector\n// vector3: Vector[Int] = Vector(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, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)\nval vector4 = vector3 :+ 21\n// vector4: Vector[Int] = Vector(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, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 21)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(vector3) + diagram(vector4)).render("big-vectors", _.withVerticalSpacing(2))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"big-vectors",src:a(5390).A+"",width:"3785",height:"853"})}),"\n",(0,s.jsxs)(n.p,{children:["If you want to know more, this structure is covered in great detail by Jean Niklas L\u2019orange\n",(0,s.jsx)(n.a,{href:"http://hypirion.com/musings/understanding-persistent-vector-pt-1",children:"in his blog"}),".\nI also highly recommend watching ",(0,s.jsx)(n.a,{href:"https://www.youtube.com/watch?v=pNhBQJN44YQ",children:"this talk"})," by Daniel Spiewak."]}),"\n",(0,s.jsx)(n.h3,{id:"finger-trees",children:"Finger Trees"}),"\n",(0,s.jsxs)(n.p,{children:["To conclude this section, I would like to share a slightly less popular, but beautifully designed\ndata structure called \u201cfinger tree\u201d described in ",(0,s.jsx)(n.a,{href:"http://www.cs.ox.ac.uk/ralf.hinze/publications/FingerTrees.pdf",children:"this paper"}),"\nby Hinze and Paterson. Enjoy the read and this animation of a finger tree getting filled with some numbers:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'import de.sciss.fingertree.{FingerTree, Measure}\nimport reftree.contrib.FingerTreeInstances._\n\nimplicit val measure = Measure.Indexed\n\nAnimation\n .startWith(FingerTree(1))\n .iterateWithIndex(21)((t, i) => t :+ (i + 1))\n .build(Diagram(_).withCaption("Finger Tree").withAnchor("tree"))\n .render("finger", _.withDensity(75).withVerticalSpacing(2))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"finger",src:a(7580).A+"",width:"1730",height:"1160"})}),"\n",(0,s.jsx)(n.h2,{id:"lenses",children:"Lenses"}),"\n",(0,s.jsxs)(n.p,{children:["So far we were looking into \u201cstandard\u201d data structures,\nbut in our code we often have to deal with custom data structures comprising our domain model.\nUpdating this sort of data can be tricky if it\u2019s immutable.\nFor case classes Scala gives us the ",(0,s.jsx)(n.code,{children:"copy"})," method:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"case class Employee(\n name: String,\n salary: Long\n)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'employee\n// res7: Employee = Employee(name = "Michael", salary = 4000L)\nval raisedEmployee = employee.copy(salary = employee.salary + 10)\n// raisedEmployee: Employee = Employee(name = "Michael", salary = 4010L)\n'})}),"\n",(0,s.jsxs)(n.p,{children:["However once composition comes into play, the resulting nested immutable data structures\nwould require a lot of ",(0,s.jsx)(n.code,{children:"copy"})," calls:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"case class Employee(\n name: String,\n salary: Long\n)\n\ncase class Startup(\n name: String,\n founder: Employee,\n team: List[Employee]\n)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'startup\n// res8: Startup = Startup(\n// name = "Acme",\n// founder = Employee(name = "Michael", salary = 4000L),\n// team = List(\n// Employee(name = "Adam", salary = 2100L),\n// Employee(name = "Bella", salary = 2100L),\n// Employee(name = "Chad", salary = 1980L),\n// Employee(name = "Delia", salary = 1850L)\n// )\n// )\nval raisedFounder = startup.copy(\n founder = startup.founder.copy(\n salary = startup.founder.salary + 10\n )\n)\n// raisedFounder: Startup = Startup(\n// name = "Acme",\n// founder = Employee(name = "Michael", salary = 4010L),\n// team = List(\n// Employee(name = "Adam", salary = 2100L),\n// Employee(name = "Bella", salary = 2100L),\n// Employee(name = "Chad", salary = 1980L),\n// Employee(name = "Delia", salary = 1850L)\n// )\n// )\n'})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"// extra declarations for this section\nimport reftree.contrib.SimplifiedInstances.{list => listInstance}\nimport reftree.contrib.OpticInstances._\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(startup) + diagram(raisedFounder)).render("startup")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"startup",src:a(7056).A+"",width:"1977",height:"701"})}),"\n",(0,s.jsx)(n.p,{children:"Ouch!"}),"\n",(0,s.jsxs)(n.p,{children:["A common solution to this problem is a \u201clens\u201d.\nIn the simplest case a lens is a pair of functions to get and set a value of type ",(0,s.jsx)(n.code,{children:"B"})," inside a value of type ",(0,s.jsx)(n.code,{children:"A"}),".\nIt\u2019s called a lens because it focuses on some part of the data and allows to update it.\nFor example, here is a lens that focuses on an employee\u2019s salary\n(using the excellent ",(0,s.jsx)(n.a,{href:"https://github.com/julien-truffaut/Monocle",children:"Monocle library"}),"):"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'import monocle.macros.GenLens\n\nval salaryLens = GenLens[Employee](_.salary)\n// salaryLens: monocle.package.Lens[Employee, Long] = repl.MdocSession$MdocApp$$anon$1@1637d34f\n\nsalaryLens.get(startup.founder)\n// res10: Long = 4000L\nsalaryLens.modify(s => s + 10)(startup.founder)\n// res11: Employee = Employee(name = "Michael", salary = 4010L)\n'})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(OpticFocus(salaryLens, startup.founder)).render("salaryLens")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"salaryLens",src:a(7817).A+"",width:"592",height:"363"})}),"\n",(0,s.jsx)(n.p,{children:"We can also define a lens that focuses on the startup\u2019s founder:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'val founderLens = GenLens[Startup](_.founder)\n// founderLens: monocle.package.Lens[Startup, Employee] = repl.MdocSession$MdocApp$$anon$2@552a02e5\n\nfounderLens.get(startup)\n// res13: Employee = Employee(name = "Michael", salary = 4000L)\n'})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(OpticFocus(founderLens, startup)).render("founderLens")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"founderLens",src:a(1738).A+"",width:"1832",height:"701"})}),"\n",(0,s.jsx)(n.p,{children:"It\u2019s not apparent yet how this would help, but the trick is that lenses can be composed:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'val founderSalaryLens = founderLens composeLens salaryLens\n// founderSalaryLens: monocle.PLens[Startup, Startup, Long, Long] = monocle.PLens$$anon$1@7a1aec99\n\nfounderSalaryLens.get(startup)\n// res15: Long = 4000L\nfounderSalaryLens.modify(s => s + 10)(startup)\n// res16: Startup = Startup(\n// name = "Acme",\n// founder = Employee(name = "Michael", salary = 4010L),\n// team = List(\n// Employee(name = "Adam", salary = 2100L),\n// Employee(name = "Bella", salary = 2100L),\n// Employee(name = "Chad", salary = 1980L),\n// Employee(name = "Delia", salary = 1850L)\n// )\n// )\n'})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(OpticFocus(founderSalaryLens, startup)).render("founderSalaryLens")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"founderSalaryLens",src:a(5288).A+"",width:"1866",height:"701"})}),"\n",(0,s.jsx)(n.p,{children:"One interesting thing is that lenses can focus on anything, not just direct attributes of the data.\nHere is a traversal \u2014 a more generic kind of lens \u2014 that focuses on all vowels in a string:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(OpticFocus(vowelTraversal, "example")).render("vowelTraversal")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"vowelTraversal",src:a(2964).A+"",width:"494",height:"193"})}),"\n",(0,s.jsx)(n.p,{children:"We can use it to give our founder a funny name:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'val employeeNameLens = GenLens[Employee](_.name)\n// employeeNameLens: monocle.package.Lens[Employee, String] = repl.MdocSession$MdocApp$$anon$3@47f42c18\nval founderVowelTraversal = founderLens composeLens employeeNameLens composeTraversal vowelTraversal\n// founderVowelTraversal: monocle.PTraversal[Startup, Startup, Char, Char] = monocle.PTraversal$$anon$2@4283ed79\n\nfounderVowelTraversal.modify(v => v.toUpper)(startup)\n// res19: Startup = Startup(\n// name = "Acme",\n// founder = Employee(name = "MIchAEl", salary = 4000L),\n// team = List(\n// Employee(name = "Adam", salary = 2100L),\n// Employee(name = "Bella", salary = 2100L),\n// Employee(name = "Chad", salary = 1980L),\n// Employee(name = "Delia", salary = 1850L)\n// )\n// )\n'})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(OpticFocus(founderVowelTraversal, startup)).render("founderVowelTraversal")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"founderVowelTraversal",src:a(7289).A+"",width:"1889",height:"701"})}),"\n",(0,s.jsxs)(n.p,{children:["So far we have replaced the ",(0,s.jsx)(n.code,{children:"copy"})," boilerplate with a number of lens declarations.\nHowever most of the time our goal is just to update data."]}),"\n",(0,s.jsxs)(n.p,{children:["In Scala there is a great library called ",(0,s.jsx)(n.a,{href:"https://github.com/adamw/quicklens",children:"quicklens"}),"\nthat allows to do exactly that, creating all the necessary lenses under the hood:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'import com.softwaremill.quicklens._\n\nval raisedCeo = startup.modify(_.founder.salary).using(s => s + 10)\n// raisedCeo: Startup = Startup(\n// name = "Acme",\n// founder = Employee(name = "Michael", salary = 4010L),\n// team = List(\n// Employee(name = "Adam", salary = 2100L),\n// Employee(name = "Bella", salary = 2100L),\n// Employee(name = "Chad", salary = 1980L),\n// Employee(name = "Delia", salary = 1850L)\n// )\n// )\n'})}),"\n",(0,s.jsx)(n.p,{children:"You might think this is approaching the syntax for updating mutable data,\nbut actually we have already surpassed it, since lenses are much more flexible:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'val raisedEveryone = startup.modifyAll(_.founder.salary, _.team.each.salary).using(s => s + 10)\n// raisedEveryone: Startup = Startup(\n// name = "Acme",\n// founder = Employee(name = "Michael", salary = 4010L),\n// team = List(\n// Employee(name = "Adam", salary = 2110L),\n// Employee(name = "Bella", salary = 2110L),\n// Employee(name = "Chad", salary = 1990L),\n// Employee(name = "Delia", salary = 1860L)\n// )\n// )\n'})}),"\n",(0,s.jsx)(n.h2,{id:"zippers",children:"Zippers"}),"\n",(0,s.jsx)(n.p,{children:"In our domain models we are often faced with recursive data structures.\nConsider this example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"case class Employee(\n name: String,\n salary: Long\n)\n\ncase class Hierarchy(\n employee: Employee,\n team: List[Hierarchy]\n)\n\ncase class Company(\n name: String,\n hierarchy: Hierarchy\n)\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"Hierarchy"})," class refers to itself.\nLet\u2019s grab a company object and display its hierarchy as a tree:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"// extra declarations for this section\nimport zipper._\nimport reftree.contrib.SimplifiedInstances.option\nimport reftree.contrib.ZipperInstances._\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(company.hierarchy).render("company")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"company",src:a(3422).A+"",width:"2283",height:"1210"})}),"\n",(0,s.jsxs)(n.p,{children:["What if we want to navigate through this tree and modify it along the way?\nWe can use ",(0,s.jsx)(n.a,{href:"#lenses",children:"lenses"}),", but the recursive nature of the tree allows for a better solution."]}),"\n",(0,s.jsxs)(n.p,{children:["This solution is called a \u201cZipper\u201d, and was introduced by G\xe9rard Huet in 1997.\nIt consists of a \u201ccursor\u201d pointing to a location anywhere in the tree \u2014 \u201ccurrent focus\u201d.\nThe cursor can be moved freely with operations like ",(0,s.jsx)(n.code,{children:"moveDownLeft"}),", ",(0,s.jsx)(n.code,{children:"moveRight"}),", ",(0,s.jsx)(n.code,{children:"moveUp"}),", etc.\nCurrent focus can be updated, deleted, or new nodes can be inserted to its left or right.\nZippers are immutable, and every operation returns a new Zipper.\nAll the changes made to the tree can be committed, yielding a new modified version of the original tree."]}),"\n",(0,s.jsx)(n.p,{children:"Here is how we would insert a new employee into the hierarchy:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val updatedHierarchy = Zipper(company.hierarchy).moveDownRight.moveDownRight.insertRight(newHire).commit\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(company.hierarchy) + diagram(updatedHierarchy)).render("updatedHierarchy")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"updatedHierarchy",src:a(365).A+"",width:"2505",height:"1210"})}),"\n",(0,s.jsxs)(n.p,{children:["My ",(0,s.jsx)(n.a,{href:"https://github.com/stanch/zipper#zipper--an-implementation-of-huets-zipper",children:"zipper library"}),"\nprovides a few useful movements and operations."]}),"\n",(0,s.jsx)(n.p,{children:"Let\u2019s consider a simpler recursive data structure:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"case class Tree(x: Int, c: List[Tree] = List.empty)\n"})}),"\n",(0,s.jsx)(n.p,{children:"and a simple tree:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"simpleTree\n// res23: Tree = Tree(\n// x = 1,\n// c = List(\n// Tree(x = 2, c = List()),\n// Tree(x = 3, c = List()),\n// Tree(x = 4, c = List()),\n// Tree(x = 5, c = List(Tree(x = 6, c = List()), Tree(x = 7, c = List())))\n// )\n// )\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(simpleTree).render("simpleTree")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"simpleTree",src:a(977).A+"",width:"925",height:"721"})}),"\n",(0,s.jsx)(n.p,{children:"When we wrap a Zipper around this tree, it does not look very interesting yet:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper1 = Zipper(simpleTree)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(simpleTree) + diagram(zipper1)).render("zipper1")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper1",src:a(3596).A+"",width:"998",height:"890"})}),"\n",(0,s.jsx)(n.p,{children:"We can see that it just points to the original tree and has some other empty fields.\nMore specifically, a Zipper consists of four pointers:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"case class Zipper[A](\n left: List[A], // left siblings of the focus\n focus: A, // the current focus\n right: List[A], // right siblings of the focus\n top: Option[Zipper[A]] // the parent zipper\n)\n"})}),"\n",(0,s.jsx)(n.p,{children:"In this case the focus is the root of the tree, which has no siblings,\nand the parent zipper does not exist, since we are at the top level."}),"\n",(0,s.jsx)(n.p,{children:"One thing we can do right away is modify the focus:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper2 = zipper1.update(focus => focus.copy(x = focus.x + 99))\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(simpleTree) + diagram(zipper1) + diagram(zipper2)).render("zipper2")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper2",src:a(7879).A+"",width:"1299",height:"890"})}),"\n",(0,s.jsx)(n.p,{children:"We just created a new tree! To obtain it, we have to commit the changes:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val tree2 = zipper2.commit\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(simpleTree) + diagram(tree2)).render("tree2")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"tree2",src:a(4025).A+"",width:"947",height:"721"})}),"\n",(0,s.jsx)(n.p,{children:"If you were following closely,\nyou would notice that nothing spectacular happened yet:\nwe could\u2019ve easily obtained the same result by modifying the tree directly:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val tree2b = simpleTree.copy(x = simpleTree.x + 99)\n\nassert(tree2b == tree2)\n"})}),"\n",(0,s.jsx)(n.p,{children:"The power of Zipper becomes apparent when we go one or more levels deep.\nTo move down the tree, we \u201cunzip\u201d it, separating the child nodes into\nthe focused node and its left and right siblings:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper3 = zipper1.moveDownLeft\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(zipper1) + diagram(zipper3)).render("zipper1+3")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper1+3",src:a(3146).A+"",width:"973",height:"1060"})}),"\n",(0,s.jsx)(n.p,{children:"The new Zipper links to the old one,\nwhich will allow us to return to the root of the tree when we are done applying changes.\nThis link however prevents us from seeing the picture clearly.\nLet\u2019s look at the second zipper alone:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(zipper3).render("zipper3")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper3",src:a(4862).A+"",width:"927",height:"758"})}),"\n",(0,s.jsxs)(n.p,{children:["Great! We have ",(0,s.jsx)(n.code,{children:"2"})," in focus and ",(0,s.jsx)(n.code,{children:"3, 4, 5"})," as right siblings. What happens if we move right a bit?"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper4 = zipper3.moveRightBy(2)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(zipper4).render("zipper4")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper4",src:a(3358).A+"",width:"818",height:"758"})}),"\n",(0,s.jsx)(n.p,{children:"This is interesting! Notice that the left siblings are \u201cinverted\u201d.\nThis allows to move left and right in constant time, because the sibling\nadjacent to the focus is always at the head of the list."}),"\n",(0,s.jsx)(n.p,{children:"This also allows us to insert new siblings easily:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper5 = zipper4.insertLeft(Tree(34))\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(zipper5).render("zipper5")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper5",src:a(2712).A+"",width:"935",height:"758"})}),"\n",(0,s.jsx)(n.p,{children:"And, as you might know, we can delete nodes and update the focus:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper6 = zipper5.deleteAndMoveRight.set(Tree(45))\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(zipper6).render("zipper6")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper6",src:a(7203).A+"",width:"531",height:"494"})}),"\n",(0,s.jsx)(n.p,{children:"Finally, when we move up, the siblings at the current level are \u201czipped\u201d\ntogether and their parent node is updated:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper7 = zipper6.moveUp\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(zipper7).render("zipper7")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper7",src:a(1994).A+"",width:"777",height:"626"})}),"\n",(0,s.jsxs)(n.p,{children:["You can probably guess by now that ",(0,s.jsx)(n.code,{children:".commit"})," is a shorthand for going\nall the way up (applying all the changes) and returning the focus:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val tree3a = zipper6.moveUp.focus\nval tree3b = zipper6.commit\n\nassert(tree3a == tree3b)\n"})}),"\n",(0,s.jsx)(n.p,{children:"Here is an animation of the navigation process:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'val movement = Animation\n .startWith(Zipper(Data.simpleTree))\n .iterate(\n _.moveDownLeft,\n _.moveRight, _.moveRight, _.moveRight,\n _.moveDownLeft,\n _.moveRight, _.moveLeft,\n _.top.get,\n _.moveLeft, _.moveLeft, _.moveLeft,\n _.top.get\n )\n\nval trees = movement\n .build(z => Diagram(ZipperFocus(z, Data.simpleTree)).withCaption("Tree").withAnchor("tree"))\n .toNamespace("tree")\n\nval zippers = movement\n .build(Diagram(_).withCaption("Zipper").withAnchor("zipper").withColor(2))\n .toNamespace("zipper")\n\n(trees + zippers).render("tree+zipper")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"tree+zipper",src:a(1864).A+"",width:"1241",height:"690"})}),"\n",(0,s.jsx)(n.h2,{id:"useful-resources",children:"Useful resources"}),"\n",(0,s.jsx)(n.h3,{id:"books-papers-and-talks",children:"Books, papers and talks"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"http://www.amazon.com/Purely-Functional-Structures-Chris-Okasaki/dp/0521663504",children:"Purely functional data structures"})," by Chris Okasaki,\nand/or ",(0,s.jsx)(n.a,{href:"https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf",children:"his PhD thesis"})," \u2014 ",(0,s.jsx)(n.em,{children:"the"})," introduction to immutable data structures"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"http://cstheory.stackexchange.com/a/1550",children:"What\u2019s new in purely functional data structures since Okasaki"})," \u2014 an excellent StackExchange answer\nwith pointers for further reading"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://www.youtube.com/watch?v=pNhBQJN44YQ",children:"Extreme cleverness"})," by Daniel Spiewak \u2014 a superb talk\ncovering several immutable data structures (implemented ",(0,s.jsx)(n.a,{href:"https://github.com/djspiewak/extreme-cleverness",children:"here"}),")"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"http://hypirion.com/musings/understanding-persistent-vector-pt-1",children:"Understanding Clojure\u2019s Persistent Vectors, part 1"}),"\nand ",(0,s.jsx)(n.a,{href:"http://hypirion.com/musings/understanding-persistent-vector-pt-2",children:"part 2"})," \u2014 a series of blog posts by Jean Niklas L\u2019orange"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"http://www.cs.ox.ac.uk/ralf.hinze/publications/FingerTrees.pdf",children:"Finger Trees"})," and\n",(0,s.jsx)(n.a,{href:"http://www.cs.ox.ac.uk/ralf.hinze/publications/Brother12.pdf",children:"1-2 Brother Trees"})," described by Hinze and Paterson"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf",children:"Huet\u2019s original Zipper paper"})," \u2014 a great short read\nintroducing the Zipper"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"http://dspace.library.uu.nl/bitstream/handle/1874/2532/2001-33.pdf",children:"Weaving a web"})," by Hinze and Jeuring \u2014\nanother interesting Zipper-like approach"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"scala-libraries",children:"Scala libraries"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://github.com/stanch/zipper",children:"zipper"})," \u2014 my Zipper implementation"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://github.com/julien-truffaut/Monocle",children:"Monocle"})," \u2014 an \u201coptics\u201d library"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://github.com/adamw/quicklens",children:"Quicklens"})," \u2014 a simpler way to update nested case classes"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://github.com/Sciss/FingerTree",children:"FingerTree"})," \u2014 an implementation of the Finger Tree data structure"]}),"\n"]})]})}function m(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(p,{...e})}):p(e)}},7580:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/finger-b0999bed13c76359869b636227548cc6.gif"},5390:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/big-vectors-4b0f5120bf4a32b8802dc41e520eb37a.png"},3422:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/company-3188e3cf359d9f1c7224ffceed126083.png"},1738:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/founderLens-005975324dcee19c77494d453f8647c2.png"},5288:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/founderSalaryLens-89e3e6d28efff27c4c8f563cfa49d3ee.png"},7289:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/founderVowelTraversal-d35014cf80294acb4990d554bcedb0d3.png"},2773:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/list-append-063e66f9ba76613ce2a8ba351ea27ee8.gif"},2515:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/list-prepend-d90a987fd8bfc72246aa9c61dbfcf1c8.gif"},8603:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/list-a190da16976869e8e07bee62d501a9f7.png"},2600:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/lists-58f15f89774ee685a16243b40e9ba7c2.png"},5623:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/queues-5efd3f3abf9c377155a0b4b6de6a77f9.png"},7817:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/salaryLens-0d89d07dc97f512aa7fe6b50f061cfcd.png"},977:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/simpleTree-bbaebacbd585b97eea7d64d94848caf8.png"},7056:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/startup-2a750a1231daf1c2d9c548867f54ffc1.png"},4025:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/tree2-9e8f1e7e745ac62ee27f909cf0770ae0.png"},365:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/updatedHierarchy-ca0b5d651e31d5815f0d14c96fd928ad.png"},1999:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/vectors-d5f0252feb82955ab0584957427fb307.png"},2964:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/vowelTraversal-6534f12953b90dc5b6d40f6ee44e6c27.png"},3146:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper1+3-dafaaae54d3abc9716c59963ceb6f171.png"},3596:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper1-4a80c9c87d37df30e02c064945cde2f6.png"},7879:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper2-1176392f67e450e82bfa63dabbeacf90.png"},4862:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper3-9ab483dc84adf5005cafecc77e51e394.png"},3358:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper4-283827a5d36341e55ced1d024b328633.png"},2712:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper5-ce83ff83213a95c042b81012c81a28fc.png"},7203:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper6-4431d6974f1d299e656fa1e5c8d54fb0.png"},1994:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper7-cd17a63fb6f4732884da00b544ffa549.png"},9696:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/queue-668a708310e8e78475fbc9b757c69eb3.gif"},1864:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/tree+zipper-b1cf73da287eda26f64b667ce8dcf311.gif"}}]); \ No newline at end of file +"use strict";(self.webpackChunkreftree=self.webpackChunkreftree||[]).push([[814],{6444:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>d,contentTitle:()=>c,default:()=>m,frontMatter:()=>l,metadata:()=>o,toc:()=>h});var s=a(4848),r=a(8453),t=a(3554),i=a.n(t);const l={sidebar_position:1,description:"This talk takes advantage of reftree to observe several immutable data structures in action and uncover their inner beauty. Having surfaced immutability\u2019s crucial tricks, we move our focus to lenses and zippers \u2014 handy tools that combine the convenience of the \u201cmutable world\u201d with the expressiveness of functional programming."},c="Unzipping Immutability",o={id:"talks/Immutability",title:"Unzipping Immutability",description:"This talk takes advantage of reftree to observe several immutable data structures in action and uncover their inner beauty. Having surfaced immutability\u2019s crucial tricks, we move our focus to lenses and zippers \u2014 handy tools that combine the convenience of the \u201cmutable world\u201d with the expressiveness of functional programming.",source:"@site/../site-gen/target/mdoc/talks/Immutability.md",sourceDirName:"talks",slug:"/talks/Immutability",permalink:"/reftree/docs/talks/Immutability",draft:!1,unlisted:!1,editUrl:"https://github.com/stanch/reftree/tree/main/docs/../site-gen/target/mdoc/talks/Immutability.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1,description:"This talk takes advantage of reftree to observe several immutable data structures in action and uncover their inner beauty. Having surfaced immutability\u2019s crucial tricks, we move our focus to lenses and zippers \u2014 handy tools that combine the convenience of the \u201cmutable world\u201d with the expressiveness of functional programming."},sidebar:"mainSidebar",previous:{title:"Talks / Demos",permalink:"/reftree/docs/talks/"},next:{title:"Visualize your data structures!",permalink:"/reftree/docs/talks/Visualize"}},d={},h=[{value:"Immutable data structures",id:"immutable-data-structures",level:2},{value:"Lists",id:"lists",level:3},{value:"Queues",id:"queues",level:3},{value:"Vectors",id:"vectors",level:3},{value:"Finger Trees",id:"finger-trees",level:3},{value:"Lenses",id:"lenses",level:2},{value:"Zippers",id:"zippers",level:2},{value:"Useful resources",id:"useful-resources",level:2},{value:"Books, papers and talks",id:"books-papers-and-talks",level:3},{value:"Scala libraries",id:"scala-libraries",level:3}];function p(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",img:"img",li:"li",p:"p",pre:"pre",ul:"ul",...(0,r.R)(),...e.components},{Details:t}=n;return t||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"unzipping-immutability",children:"Unzipping Immutability"}),"\n",(0,s.jsx)(n.p,{children:"This page contains the materials for my talk \u201cUnzipping Immutability\u201d."}),"\n","\n",(0,s.jsx)(i(),{controls:!0,style:{marginBottom:"1em"},url:"https://www.youtube.com/watch?v=dOj-wk5MQ3k"}),"\n",(0,s.jsxs)(t,{children:[(0,s.jsx)("summary",{children:"Older videos"}),(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["LX Scala, April 2016: ",(0,s.jsx)(n.a,{href:"https://vimeo.com/162214356",children:"https://vimeo.com/162214356"})]}),"\n",(0,s.jsxs)(n.li,{children:["Pixels Camp, October 2016: ",(0,s.jsx)(n.a,{href:"https://www.youtube.com/watch?v=yeMvhuD689A",children:"https://www.youtube.com/watch?v=yeMvhuD689A"})]}),"\n"]})]}),"\n",(0,s.jsx)(n.p,{children:"You can use this page in two ways:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"as a reference/refresher on the concepts covered in the talk;"}),"\n",(0,s.jsx)(n.li,{children:"as an interactive playground where you can try the same commands I presented."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Throughout this page we will assume the following\ndeclarations (each section might add its own):"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"import reftree.core._\nimport reftree.diagram._\nimport reftree.render._\nimport reftree.demo.Data._\nimport scala.collection.immutable._\nimport java.nio.file.Paths\nimport Diagram.{sourceCodeCaption => diagram}\n"})}),"\n",(0,s.jsx)(n.p,{children:"To start an interactive session, just run"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"$ sbt demo\n@ render(List(1, 2, 3))\n"})}),"\n",(0,s.jsxs)(n.p,{children:["and open ",(0,s.jsx)(n.code,{children:"diagram.png"})," in your favorite image viewer (hopefully one that\nreloads images automatically on file change). You will also need to have\n",(0,s.jsx)(n.a,{href:"http://www.graphviz.org/",children:"GraphViz"})," installed. ",(0,s.jsx)(n.em,{children:"The interactive session\nalready has all the necessary imports in scope."})]}),"\n",(0,s.jsx)(n.h2,{id:"immutable-data-structures",children:"Immutable data structures"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'// extra declarations for this section\nval renderer = Renderer(\n renderingOptions = RenderingOptions(density = 100),\n directory = Paths.get(ImagePath, "immutability")\n)\nimport renderer._\n'})}),"\n",(0,s.jsx)(n.h3,{id:"lists",children:"Lists"}),"\n",(0,s.jsx)(n.p,{children:"We\u2019ll start with one of the simplest structures: a list.\nIt consists of a number of cells pointing to each other:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val list = List(1, 2, 3)\n// list: List[Int] = List(1, 2, 3)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(list).render("list")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"list",src:a(8603).A+"",width:"322",height:"590"})}),"\n",(0,s.jsx)(n.p,{children:"Elements can be added to or removed from the front of the list with no effort,\nbecause we can share the same cells across several lists.\nThis would not be possible with a mutable list,\nsince modifying the shared part would modify every data structure making use of it."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val add = 0 :: list\n// add: List[Int] = List(0, 1, 2, 3)\nval remove = list.tail\n// remove: List[Int] = List(2, 3)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(list) + diagram(add) + diagram(remove)).render("lists")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"lists",src:a(2600).A+"",width:"455",height:"722"})}),"\n",(0,s.jsxs)(n.p,{children:["However we can\u2019t easily add elements at the end of the list, since the last cell\nis pointing to the empty list (",(0,s.jsx)(n.code,{children:"Nil"}),") and is immutable, i.e. cannot be changed.\nThus we are forced to create a new list every time:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(Animation\n .startWith(List(1))\n .iterate(_ :+ 2, _ :+ 3, _ :+ 4)\n .build()\n .render("list-append", tweakAnimation = _.withOnionSkinLayers(3)))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"list-append",src:a(2773).A+"",width:"675",height:"722"})}),"\n",(0,s.jsx)(n.p,{children:"This certainly does not look efficient compared to adding elements at the front:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(Animation\n .startWith(List(1))\n .iterate(2 :: _, 3 :: _, 4 :: _)\n .build()\n .render("list-prepend"))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"list-prepend",src:a(2515).A+"",width:"457",height:"722"})}),"\n",(0,s.jsx)(n.h3,{id:"queues",children:"Queues"}),"\n",(0,s.jsx)(n.p,{children:"If we want to add elements on both sides efficiently, we need a different data structure: a queue.\nThe queue below, also known as a \u201cBanker\u2019s Queue\u201d, has two lists: one for prepending and one for appending."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val queue1 = Queue(1, 2, 3)\n// queue1: Queue[Int] = Queue(1, 2, 3)\nval queue2 = (queue1 :+ 4).tail\n// queue2: Queue[Int] = Queue(2, 3, 4)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(queue1) + diagram(queue2)).render("queues", _.withVerticalSpacing(1.2))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"queues",src:a(5623).A+"",width:"607",height:"992"})}),"\n",(0,s.jsx)(n.p,{children:"This way we can add and remove elements very easily at both ends.\nExcept when we try to remove an element and the respective list is empty!\nIn this case the queue will rotate the other list to make use of its elements.\nAlthough this operation is expensive, the usage pattern intended for a queue\nmakes it rare enough to yield great average (\u201cammortized\u201d) performance:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(Animation\n .startWith(Queue(1, 2, 3))\n .repeat(3)(_.iterate(2)(q => q :+ (q.max + 1)).iterate(2)(_.tail))\n .build(Diagram.toStringCaption(_).withAnchor("queue"))\n .render("queue"))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"queue",src:a(9696).A+"",width:"540",height:"922"})}),"\n",(0,s.jsx)(n.h3,{id:"vectors",children:"Vectors"}),"\n",(0,s.jsxs)(n.p,{children:["One downside common to both lists and queues we saw before is that to get an element by index,\nwe need to potentially traverse the whole structure. A ",(0,s.jsx)(n.code,{children:"Vector"})," is a powerful data structure\naddressing this shortcoming and available in Scala (among other languages, like Clojure)."]}),"\n",(0,s.jsx)(n.p,{children:"Internally vectors utilize up to 6 layers of arrays, where 32 elements sit on the first layer,\n1024 \u2014 on the second, 32^3 \u2014 on the third, etc.\nTherefore getting any element by its index requires at most 6 pointer dereferences,\nwhich can be deemed constant time (yes, the trick is that the number of elements that can\nbe stored is limited by 2^31)."}),"\n",(0,s.jsx)(n.p,{children:"The internal 32-element arrays form the basic structural sharing blocks.\nFor small vectors they will be recreated on most operations:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val vector1 = (1 to 20).toVector\n// vector1: Vector[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)\nval vector2 = vector1 :+ 21\n// vector2: Vector[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(vector1) + diagram(vector2)).render("vectors", _.withVerticalSpacing(2))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"vectors",src:a(1999).A+"",width:"2022",height:"601"})}),"\n",(0,s.jsx)(n.p,{children:"However as more layers leap into action, a huge chunk of the data can be shared:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val vector3 = (1 to 100).toVector\n// vector3: Vector[Int] = Vector(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, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100)\nval vector4 = vector3 :+ 21\n// vector4: Vector[Int] = Vector(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, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 21)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(vector3) + diagram(vector4)).render("big-vectors", _.withVerticalSpacing(2))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"big-vectors",src:a(5390).A+"",width:"3785",height:"853"})}),"\n",(0,s.jsxs)(n.p,{children:["If you want to know more, this structure is covered in great detail by Jean Niklas L\u2019orange\n",(0,s.jsx)(n.a,{href:"http://hypirion.com/musings/understanding-persistent-vector-pt-1",children:"in his blog"}),".\nI also highly recommend watching ",(0,s.jsx)(n.a,{href:"https://www.youtube.com/watch?v=pNhBQJN44YQ",children:"this talk"})," by Daniel Spiewak."]}),"\n",(0,s.jsx)(n.h3,{id:"finger-trees",children:"Finger Trees"}),"\n",(0,s.jsxs)(n.p,{children:["To conclude this section, I would like to share a slightly less popular, but beautifully designed\ndata structure called \u201cfinger tree\u201d described in ",(0,s.jsx)(n.a,{href:"http://www.cs.ox.ac.uk/ralf.hinze/publications/FingerTrees.pdf",children:"this paper"}),"\nby Hinze and Paterson. Enjoy the read and this animation of a finger tree getting filled with some numbers:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'import de.sciss.fingertree.{FingerTree, Measure}\nimport reftree.contrib.FingerTreeInstances._\n\nimplicit val measure = Measure.Indexed\n\nAnimation\n .startWith(FingerTree(1))\n .iterateWithIndex(21)((t, i) => t :+ (i + 1))\n .build(Diagram(_).withCaption("Finger Tree").withAnchor("tree"))\n .render("finger", _.withDensity(75).withVerticalSpacing(2))\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"finger",src:a(7580).A+"",width:"1730",height:"1160"})}),"\n",(0,s.jsx)(n.h2,{id:"lenses",children:"Lenses"}),"\n",(0,s.jsxs)(n.p,{children:["So far we were looking into \u201cstandard\u201d data structures,\nbut in our code we often have to deal with custom data structures comprising our domain model.\nUpdating this sort of data can be tricky if it\u2019s immutable.\nFor case classes Scala gives us the ",(0,s.jsx)(n.code,{children:"copy"})," method:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"case class Employee(\n name: String,\n salary: Long\n)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'employee\n// res7: Employee = Employee(name = "Michael", salary = 4000L)\nval raisedEmployee = employee.copy(salary = employee.salary + 10)\n// raisedEmployee: Employee = Employee(name = "Michael", salary = 4010L)\n'})}),"\n",(0,s.jsxs)(n.p,{children:["However once composition comes into play, the resulting nested immutable data structures\nwould require a lot of ",(0,s.jsx)(n.code,{children:"copy"})," calls:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"case class Employee(\n name: String,\n salary: Long\n)\n\ncase class Startup(\n name: String,\n founder: Employee,\n team: List[Employee]\n)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'startup\n// res8: Startup = Startup(\n// name = "Acme",\n// founder = Employee(name = "Michael", salary = 4000L),\n// team = List(\n// Employee(name = "Adam", salary = 2100L),\n// Employee(name = "Bella", salary = 2100L),\n// Employee(name = "Chad", salary = 1980L),\n// Employee(name = "Delia", salary = 1850L)\n// )\n// )\nval raisedFounder = startup.copy(\n founder = startup.founder.copy(\n salary = startup.founder.salary + 10\n )\n)\n// raisedFounder: Startup = Startup(\n// name = "Acme",\n// founder = Employee(name = "Michael", salary = 4010L),\n// team = List(\n// Employee(name = "Adam", salary = 2100L),\n// Employee(name = "Bella", salary = 2100L),\n// Employee(name = "Chad", salary = 1980L),\n// Employee(name = "Delia", salary = 1850L)\n// )\n// )\n'})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"// extra declarations for this section\nimport reftree.contrib.SimplifiedInstances.{list => listInstance}\nimport reftree.contrib.OpticInstances._\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(startup) + diagram(raisedFounder)).render("startup")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"startup",src:a(7056).A+"",width:"1977",height:"701"})}),"\n",(0,s.jsx)(n.p,{children:"Ouch!"}),"\n",(0,s.jsxs)(n.p,{children:["A common solution to this problem is a \u201clens\u201d.\nIn the simplest case a lens is a pair of functions to get and set a value of type ",(0,s.jsx)(n.code,{children:"B"})," inside a value of type ",(0,s.jsx)(n.code,{children:"A"}),".\nIt\u2019s called a lens because it focuses on some part of the data and allows to update it.\nFor example, here is a lens that focuses on an employee\u2019s salary\n(using the excellent ",(0,s.jsx)(n.a,{href:"https://github.com/julien-truffaut/Monocle",children:"Monocle library"}),"):"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'import monocle.macros.GenLens\n\nval salaryLens = GenLens[Employee](_.salary)\n// salaryLens: monocle.package.Lens[Employee, Long] = repl.MdocSession$MdocApp$$anon$1@239b5404\n\nsalaryLens.get(startup.founder)\n// res10: Long = 4000L\nsalaryLens.modify(s => s + 10)(startup.founder)\n// res11: Employee = Employee(name = "Michael", salary = 4010L)\n'})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(OpticFocus(salaryLens, startup.founder)).render("salaryLens")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"salaryLens",src:a(7817).A+"",width:"592",height:"363"})}),"\n",(0,s.jsx)(n.p,{children:"We can also define a lens that focuses on the startup\u2019s founder:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'val founderLens = GenLens[Startup](_.founder)\n// founderLens: monocle.package.Lens[Startup, Employee] = repl.MdocSession$MdocApp$$anon$2@553edd38\n\nfounderLens.get(startup)\n// res13: Employee = Employee(name = "Michael", salary = 4000L)\n'})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(OpticFocus(founderLens, startup)).render("founderLens")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"founderLens",src:a(1738).A+"",width:"1832",height:"701"})}),"\n",(0,s.jsx)(n.p,{children:"It\u2019s not apparent yet how this would help, but the trick is that lenses can be composed:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'val founderSalaryLens = founderLens composeLens salaryLens\n// founderSalaryLens: monocle.PLens[Startup, Startup, Long, Long] = monocle.PLens$$anon$1@1d13cddb\n\nfounderSalaryLens.get(startup)\n// res15: Long = 4000L\nfounderSalaryLens.modify(s => s + 10)(startup)\n// res16: Startup = Startup(\n// name = "Acme",\n// founder = Employee(name = "Michael", salary = 4010L),\n// team = List(\n// Employee(name = "Adam", salary = 2100L),\n// Employee(name = "Bella", salary = 2100L),\n// Employee(name = "Chad", salary = 1980L),\n// Employee(name = "Delia", salary = 1850L)\n// )\n// )\n'})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(OpticFocus(founderSalaryLens, startup)).render("founderSalaryLens")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"founderSalaryLens",src:a(5288).A+"",width:"1866",height:"701"})}),"\n",(0,s.jsx)(n.p,{children:"One interesting thing is that lenses can focus on anything, not just direct attributes of the data.\nHere is a traversal \u2014 a more generic kind of lens \u2014 that focuses on all vowels in a string:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(OpticFocus(vowelTraversal, "example")).render("vowelTraversal")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"vowelTraversal",src:a(2964).A+"",width:"494",height:"193"})}),"\n",(0,s.jsx)(n.p,{children:"We can use it to give our founder a funny name:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'val employeeNameLens = GenLens[Employee](_.name)\n// employeeNameLens: monocle.package.Lens[Employee, String] = repl.MdocSession$MdocApp$$anon$3@39284867\nval founderVowelTraversal = founderLens composeLens employeeNameLens composeTraversal vowelTraversal\n// founderVowelTraversal: monocle.PTraversal[Startup, Startup, Char, Char] = monocle.PTraversal$$anon$2@71a232ba\n\nfounderVowelTraversal.modify(v => v.toUpper)(startup)\n// res19: Startup = Startup(\n// name = "Acme",\n// founder = Employee(name = "MIchAEl", salary = 4000L),\n// team = List(\n// Employee(name = "Adam", salary = 2100L),\n// Employee(name = "Bella", salary = 2100L),\n// Employee(name = "Chad", salary = 1980L),\n// Employee(name = "Delia", salary = 1850L)\n// )\n// )\n'})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(OpticFocus(founderVowelTraversal, startup)).render("founderVowelTraversal")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"founderVowelTraversal",src:a(7289).A+"",width:"1889",height:"701"})}),"\n",(0,s.jsxs)(n.p,{children:["So far we have replaced the ",(0,s.jsx)(n.code,{children:"copy"})," boilerplate with a number of lens declarations.\nHowever most of the time our goal is just to update data."]}),"\n",(0,s.jsxs)(n.p,{children:["In Scala there is a great library called ",(0,s.jsx)(n.a,{href:"https://github.com/adamw/quicklens",children:"quicklens"}),"\nthat allows to do exactly that, creating all the necessary lenses under the hood:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'import com.softwaremill.quicklens._\n\nval raisedCeo = startup.modify(_.founder.salary).using(s => s + 10)\n// raisedCeo: Startup = Startup(\n// name = "Acme",\n// founder = Employee(name = "Michael", salary = 4010L),\n// team = List(\n// Employee(name = "Adam", salary = 2100L),\n// Employee(name = "Bella", salary = 2100L),\n// Employee(name = "Chad", salary = 1980L),\n// Employee(name = "Delia", salary = 1850L)\n// )\n// )\n'})}),"\n",(0,s.jsx)(n.p,{children:"You might think this is approaching the syntax for updating mutable data,\nbut actually we have already surpassed it, since lenses are much more flexible:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'val raisedEveryone = startup.modifyAll(_.founder.salary, _.team.each.salary).using(s => s + 10)\n// raisedEveryone: Startup = Startup(\n// name = "Acme",\n// founder = Employee(name = "Michael", salary = 4010L),\n// team = List(\n// Employee(name = "Adam", salary = 2110L),\n// Employee(name = "Bella", salary = 2110L),\n// Employee(name = "Chad", salary = 1990L),\n// Employee(name = "Delia", salary = 1860L)\n// )\n// )\n'})}),"\n",(0,s.jsx)(n.h2,{id:"zippers",children:"Zippers"}),"\n",(0,s.jsx)(n.p,{children:"In our domain models we are often faced with recursive data structures.\nConsider this example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"case class Employee(\n name: String,\n salary: Long\n)\n\ncase class Hierarchy(\n employee: Employee,\n team: List[Hierarchy]\n)\n\ncase class Company(\n name: String,\n hierarchy: Hierarchy\n)\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"Hierarchy"})," class refers to itself.\nLet\u2019s grab a company object and display its hierarchy as a tree:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"// extra declarations for this section\nimport zipper._\nimport reftree.contrib.SimplifiedInstances.option\nimport reftree.contrib.ZipperInstances._\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(company.hierarchy).render("company")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"company",src:a(3422).A+"",width:"2283",height:"1210"})}),"\n",(0,s.jsxs)(n.p,{children:["What if we want to navigate through this tree and modify it along the way?\nWe can use ",(0,s.jsx)(n.a,{href:"#lenses",children:"lenses"}),", but the recursive nature of the tree allows for a better solution."]}),"\n",(0,s.jsxs)(n.p,{children:["This solution is called a \u201cZipper\u201d, and was introduced by G\xe9rard Huet in 1997.\nIt consists of a \u201ccursor\u201d pointing to a location anywhere in the tree \u2014 \u201ccurrent focus\u201d.\nThe cursor can be moved freely with operations like ",(0,s.jsx)(n.code,{children:"moveDownLeft"}),", ",(0,s.jsx)(n.code,{children:"moveRight"}),", ",(0,s.jsx)(n.code,{children:"moveUp"}),", etc.\nCurrent focus can be updated, deleted, or new nodes can be inserted to its left or right.\nZippers are immutable, and every operation returns a new Zipper.\nAll the changes made to the tree can be committed, yielding a new modified version of the original tree."]}),"\n",(0,s.jsx)(n.p,{children:"Here is how we would insert a new employee into the hierarchy:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val updatedHierarchy = Zipper(company.hierarchy).moveDownRight.moveDownRight.insertRight(newHire).commit\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(company.hierarchy) + diagram(updatedHierarchy)).render("updatedHierarchy")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"updatedHierarchy",src:a(365).A+"",width:"2505",height:"1210"})}),"\n",(0,s.jsxs)(n.p,{children:["My ",(0,s.jsx)(n.a,{href:"https://github.com/stanch/zipper#zipper--an-implementation-of-huets-zipper",children:"zipper library"}),"\nprovides a few useful movements and operations."]}),"\n",(0,s.jsx)(n.p,{children:"Let\u2019s consider a simpler recursive data structure:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"case class Tree(x: Int, c: List[Tree] = List.empty)\n"})}),"\n",(0,s.jsx)(n.p,{children:"and a simple tree:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"simpleTree\n// res23: Tree = Tree(\n// x = 1,\n// c = List(\n// Tree(x = 2, c = List()),\n// Tree(x = 3, c = List()),\n// Tree(x = 4, c = List()),\n// Tree(x = 5, c = List(Tree(x = 6, c = List()), Tree(x = 7, c = List())))\n// )\n// )\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(simpleTree).render("simpleTree")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"simpleTree",src:a(977).A+"",width:"925",height:"721"})}),"\n",(0,s.jsx)(n.p,{children:"When we wrap a Zipper around this tree, it does not look very interesting yet:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper1 = Zipper(simpleTree)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(simpleTree) + diagram(zipper1)).render("zipper1")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper1",src:a(3596).A+"",width:"998",height:"890"})}),"\n",(0,s.jsx)(n.p,{children:"We can see that it just points to the original tree and has some other empty fields.\nMore specifically, a Zipper consists of four pointers:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"case class Zipper[A](\n left: List[A], // left siblings of the focus\n focus: A, // the current focus\n right: List[A], // right siblings of the focus\n top: Option[Zipper[A]] // the parent zipper\n)\n"})}),"\n",(0,s.jsx)(n.p,{children:"In this case the focus is the root of the tree, which has no siblings,\nand the parent zipper does not exist, since we are at the top level."}),"\n",(0,s.jsx)(n.p,{children:"One thing we can do right away is modify the focus:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper2 = zipper1.update(focus => focus.copy(x = focus.x + 99))\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(simpleTree) + diagram(zipper1) + diagram(zipper2)).render("zipper2")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper2",src:a(7879).A+"",width:"1299",height:"890"})}),"\n",(0,s.jsx)(n.p,{children:"We just created a new tree! To obtain it, we have to commit the changes:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val tree2 = zipper2.commit\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(simpleTree) + diagram(tree2)).render("tree2")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"tree2",src:a(4025).A+"",width:"947",height:"721"})}),"\n",(0,s.jsx)(n.p,{children:"If you were following closely,\nyou would notice that nothing spectacular happened yet:\nwe could\u2019ve easily obtained the same result by modifying the tree directly:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val tree2b = simpleTree.copy(x = simpleTree.x + 99)\n\nassert(tree2b == tree2)\n"})}),"\n",(0,s.jsx)(n.p,{children:"The power of Zipper becomes apparent when we go one or more levels deep.\nTo move down the tree, we \u201cunzip\u201d it, separating the child nodes into\nthe focused node and its left and right siblings:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper3 = zipper1.moveDownLeft\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'(diagram(zipper1) + diagram(zipper3)).render("zipper1+3")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper1+3",src:a(3146).A+"",width:"973",height:"1060"})}),"\n",(0,s.jsx)(n.p,{children:"The new Zipper links to the old one,\nwhich will allow us to return to the root of the tree when we are done applying changes.\nThis link however prevents us from seeing the picture clearly.\nLet\u2019s look at the second zipper alone:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(zipper3).render("zipper3")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper3",src:a(4862).A+"",width:"927",height:"758"})}),"\n",(0,s.jsxs)(n.p,{children:["Great! We have ",(0,s.jsx)(n.code,{children:"2"})," in focus and ",(0,s.jsx)(n.code,{children:"3, 4, 5"})," as right siblings. What happens if we move right a bit?"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper4 = zipper3.moveRightBy(2)\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(zipper4).render("zipper4")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper4",src:a(3358).A+"",width:"818",height:"758"})}),"\n",(0,s.jsx)(n.p,{children:"This is interesting! Notice that the left siblings are \u201cinverted\u201d.\nThis allows to move left and right in constant time, because the sibling\nadjacent to the focus is always at the head of the list."}),"\n",(0,s.jsx)(n.p,{children:"This also allows us to insert new siblings easily:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper5 = zipper4.insertLeft(Tree(34))\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(zipper5).render("zipper5")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper5",src:a(2712).A+"",width:"935",height:"758"})}),"\n",(0,s.jsx)(n.p,{children:"And, as you might know, we can delete nodes and update the focus:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper6 = zipper5.deleteAndMoveRight.set(Tree(45))\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(zipper6).render("zipper6")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper6",src:a(7203).A+"",width:"531",height:"494"})}),"\n",(0,s.jsx)(n.p,{children:"Finally, when we move up, the siblings at the current level are \u201czipped\u201d\ntogether and their parent node is updated:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val zipper7 = zipper6.moveUp\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'diagram(zipper7).render("zipper7")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"zipper7",src:a(1994).A+"",width:"777",height:"626"})}),"\n",(0,s.jsxs)(n.p,{children:["You can probably guess by now that ",(0,s.jsx)(n.code,{children:".commit"})," is a shorthand for going\nall the way up (applying all the changes) and returning the focus:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:"val tree3a = zipper6.moveUp.focus\nval tree3b = zipper6.commit\n\nassert(tree3a == tree3b)\n"})}),"\n",(0,s.jsx)(n.p,{children:"Here is an animation of the navigation process:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-scala",children:'val movement = Animation\n .startWith(Zipper(Data.simpleTree))\n .iterate(\n _.moveDownLeft,\n _.moveRight, _.moveRight, _.moveRight,\n _.moveDownLeft,\n _.moveRight, _.moveLeft,\n _.top.get,\n _.moveLeft, _.moveLeft, _.moveLeft,\n _.top.get\n )\n\nval trees = movement\n .build(z => Diagram(ZipperFocus(z, Data.simpleTree)).withCaption("Tree").withAnchor("tree"))\n .toNamespace("tree")\n\nval zippers = movement\n .build(Diagram(_).withCaption("Zipper").withAnchor("zipper").withColor(2))\n .toNamespace("zipper")\n\n(trees + zippers).render("tree+zipper")\n'})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"tree+zipper",src:a(1864).A+"",width:"1241",height:"690"})}),"\n",(0,s.jsx)(n.h2,{id:"useful-resources",children:"Useful resources"}),"\n",(0,s.jsx)(n.h3,{id:"books-papers-and-talks",children:"Books, papers and talks"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"http://www.amazon.com/Purely-Functional-Structures-Chris-Okasaki/dp/0521663504",children:"Purely functional data structures"})," by Chris Okasaki,\nand/or ",(0,s.jsx)(n.a,{href:"https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf",children:"his PhD thesis"})," \u2014 ",(0,s.jsx)(n.em,{children:"the"})," introduction to immutable data structures"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"http://cstheory.stackexchange.com/a/1550",children:"What\u2019s new in purely functional data structures since Okasaki"})," \u2014 an excellent StackExchange answer\nwith pointers for further reading"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://www.youtube.com/watch?v=pNhBQJN44YQ",children:"Extreme cleverness"})," by Daniel Spiewak \u2014 a superb talk\ncovering several immutable data structures (implemented ",(0,s.jsx)(n.a,{href:"https://github.com/djspiewak/extreme-cleverness",children:"here"}),")"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"http://hypirion.com/musings/understanding-persistent-vector-pt-1",children:"Understanding Clojure\u2019s Persistent Vectors, part 1"}),"\nand ",(0,s.jsx)(n.a,{href:"http://hypirion.com/musings/understanding-persistent-vector-pt-2",children:"part 2"})," \u2014 a series of blog posts by Jean Niklas L\u2019orange"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"http://www.cs.ox.ac.uk/ralf.hinze/publications/FingerTrees.pdf",children:"Finger Trees"})," and\n",(0,s.jsx)(n.a,{href:"http://www.cs.ox.ac.uk/ralf.hinze/publications/Brother12.pdf",children:"1-2 Brother Trees"})," described by Hinze and Paterson"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf",children:"Huet\u2019s original Zipper paper"})," \u2014 a great short read\nintroducing the Zipper"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"http://dspace.library.uu.nl/bitstream/handle/1874/2532/2001-33.pdf",children:"Weaving a web"})," by Hinze and Jeuring \u2014\nanother interesting Zipper-like approach"]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"scala-libraries",children:"Scala libraries"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://github.com/stanch/zipper",children:"zipper"})," \u2014 my Zipper implementation"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://github.com/julien-truffaut/Monocle",children:"Monocle"})," \u2014 an \u201coptics\u201d library"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://github.com/adamw/quicklens",children:"Quicklens"})," \u2014 a simpler way to update nested case classes"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://github.com/Sciss/FingerTree",children:"FingerTree"})," \u2014 an implementation of the Finger Tree data structure"]}),"\n"]})]})}function m(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(p,{...e})}):p(e)}},7580:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/finger-b0999bed13c76359869b636227548cc6.gif"},5390:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/big-vectors-4b0f5120bf4a32b8802dc41e520eb37a.png"},3422:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/company-3188e3cf359d9f1c7224ffceed126083.png"},1738:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/founderLens-005975324dcee19c77494d453f8647c2.png"},5288:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/founderSalaryLens-89e3e6d28efff27c4c8f563cfa49d3ee.png"},7289:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/founderVowelTraversal-d35014cf80294acb4990d554bcedb0d3.png"},2773:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/list-append-063e66f9ba76613ce2a8ba351ea27ee8.gif"},2515:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/list-prepend-d90a987fd8bfc72246aa9c61dbfcf1c8.gif"},8603:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/list-a190da16976869e8e07bee62d501a9f7.png"},2600:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/lists-58f15f89774ee685a16243b40e9ba7c2.png"},5623:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/queues-5efd3f3abf9c377155a0b4b6de6a77f9.png"},7817:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/salaryLens-0d89d07dc97f512aa7fe6b50f061cfcd.png"},977:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/simpleTree-bbaebacbd585b97eea7d64d94848caf8.png"},7056:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/startup-2a750a1231daf1c2d9c548867f54ffc1.png"},4025:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/tree2-9e8f1e7e745ac62ee27f909cf0770ae0.png"},365:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/updatedHierarchy-ca0b5d651e31d5815f0d14c96fd928ad.png"},1999:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/vectors-d5f0252feb82955ab0584957427fb307.png"},2964:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/vowelTraversal-6534f12953b90dc5b6d40f6ee44e6c27.png"},3146:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper1+3-dafaaae54d3abc9716c59963ceb6f171.png"},3596:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper1-4a80c9c87d37df30e02c064945cde2f6.png"},7879:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper2-1176392f67e450e82bfa63dabbeacf90.png"},4862:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper3-9ab483dc84adf5005cafecc77e51e394.png"},3358:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper4-283827a5d36341e55ced1d024b328633.png"},2712:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper5-ce83ff83213a95c042b81012c81a28fc.png"},7203:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper6-4431d6974f1d299e656fa1e5c8d54fb0.png"},1994:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/zipper7-cd17a63fb6f4732884da00b544ffa549.png"},9696:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/queue-668a708310e8e78475fbc9b757c69eb3.gif"},1864:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/tree+zipper-b1cf73da287eda26f64b667ce8dcf311.gif"}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.301f31c4.js b/assets/js/runtime~main.56fa2c0d.js similarity index 97% rename from assets/js/runtime~main.301f31c4.js rename to assets/js/runtime~main.56fa2c0d.js index c28b40a..2d90c38 100644 --- a/assets/js/runtime~main.301f31c4.js +++ b/assets/js/runtime~main.56fa2c0d.js @@ -1 +1 @@ -(()=>{"use strict";var e,r,a,t,o,c={},l={};function n(e){var r=l[e];if(void 0!==r)return r.exports;var a=l[e]={exports:{}};return c[e].call(a.exports,a,a.exports,n),a.exports}n.m=c,e=[],n.O=(r,a,t,o)=>{if(!a){var c=1/0;for(f=0;f=o)&&Object.keys(n.O).every((e=>n.O[e](a[d])))?a.splice(d--,1):(l=!1,o0&&e[f-1][2]>o;f--)e[f]=e[f-1];e[f]=[a,t,o]},n.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return n.d(r,{a:r}),r},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,n.t=function(e,t){if(1&t&&(e=this(e)),8&t)return e;if("object"==typeof e&&e){if(4&t&&e.__esModule)return e;if(16&t&&"function"==typeof e.then)return e}var o=Object.create(null);n.r(o);var c={};r=r||[null,a({}),a([]),a(a)];for(var l=2&t&&e;"object"==typeof l&&!~r.indexOf(l);l=a(l))Object.getOwnPropertyNames(l).forEach((r=>c[r]=()=>e[r]));return c.default=()=>e,n.d(o,c),o},n.d=(e,r)=>{for(var a in r)n.o(r,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:r[a]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce(((r,a)=>(n.f[a](e,r),r)),[])),n.u=e=>"assets/js/"+({42:"reactPlayerTwitch",48:"a94703ab",66:"e7ed7793",98:"a7bd4aaa",173:"reactPlayerVimeo",235:"a7456010",328:"reactPlayerDailyMotion",340:"reactPlayerWistia",350:"66c1eed3",353:"reactPlayerPreview",392:"reactPlayerVidyard",396:"9d744931",401:"17896441",446:"reactPlayerYouTube",458:"reactPlayerFilePlayer",463:"reactPlayerKaltura",533:"71ffd77d",570:"reactPlayerMixcloud",627:"reactPlayerStreamable",634:"c4f5d8e4",647:"5e95c892",672:"22c5de9a",723:"reactPlayerMux",742:"aba21aa0",814:"b3d9ed0f",887:"reactPlayerFacebook",956:"37c4c8cd",979:"reactPlayerSoundCloud"}[e]||e)+"."+{42:"f8093db6",48:"6fa9d72a",66:"d03dfad9",98:"8ba4f90d",132:"fe654836",173:"e0bb6f48",235:"d3f42fc7",237:"f4ea580f",328:"2786b40e",340:"55782d35",350:"fe49005a",353:"e875752c",392:"fc0f3075",396:"c5feb62f",401:"9e05f0ab",446:"6b5e7fe3",458:"1d2055b5",463:"77229a02",533:"ca1b41b0",570:"530faa1a",627:"2e005b20",634:"f29e2922",647:"b6ccb016",672:"0c9c5e17",723:"155186bf",742:"b00a85c6",814:"5a9d3f91",887:"c103ff05",956:"a437f795",979:"199c972c"}[e]+".js",n.miniCssF=e=>{},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t={},o="reftree:",n.l=(e,r,a,c)=>{if(t[e])t[e].push(r);else{var l,d;if(void 0!==a)for(var i=document.getElementsByTagName("script"),f=0;f{l.onerror=l.onload=null,clearTimeout(s);var o=t[e];if(delete t[e],l.parentNode&&l.parentNode.removeChild(l),o&&o.forEach((e=>e(a))),r)return r(a)},s=setTimeout(y.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=y.bind(null,l.onerror),l.onload=y.bind(null,l.onload),d&&document.head.appendChild(l)}},n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.p="/reftree/",n.gca=function(e){return e={17896441:"401",reactPlayerTwitch:"42",a94703ab:"48",e7ed7793:"66",a7bd4aaa:"98",reactPlayerVimeo:"173",a7456010:"235",reactPlayerDailyMotion:"328",reactPlayerWistia:"340","66c1eed3":"350",reactPlayerPreview:"353",reactPlayerVidyard:"392","9d744931":"396",reactPlayerYouTube:"446",reactPlayerFilePlayer:"458",reactPlayerKaltura:"463","71ffd77d":"533",reactPlayerMixcloud:"570",reactPlayerStreamable:"627",c4f5d8e4:"634","5e95c892":"647","22c5de9a":"672",reactPlayerMux:"723",aba21aa0:"742",b3d9ed0f:"814",reactPlayerFacebook:"887","37c4c8cd":"956",reactPlayerSoundCloud:"979"}[e]||e,n.p+n.u(e)},(()=>{var e={354:0,869:0};n.f.j=(r,a)=>{var t=n.o(e,r)?e[r]:void 0;if(0!==t)if(t)a.push(t[2]);else if(/^(354|869)$/.test(r))e[r]=0;else{var o=new Promise(((a,o)=>t=e[r]=[a,o]));a.push(t[2]=o);var c=n.p+n.u(r),l=new Error;n.l(c,(a=>{if(n.o(e,r)&&(0!==(t=e[r])&&(e[r]=void 0),t)){var o=a&&("load"===a.type?"missing":a.type),c=a&&a.target&&a.target.src;l.message="Loading chunk "+r+" failed.\n("+o+": "+c+")",l.name="ChunkLoadError",l.type=o,l.request=c,t[1](l)}}),"chunk-"+r,r)}},n.O.j=r=>0===e[r];var r=(r,a)=>{var t,o,c=a[0],l=a[1],d=a[2],i=0;if(c.some((r=>0!==e[r]))){for(t in l)n.o(l,t)&&(n.m[t]=l[t]);if(d)var f=d(n)}for(r&&r(a);i{"use strict";var e,r,a,t,o,c={},l={};function n(e){var r=l[e];if(void 0!==r)return r.exports;var a=l[e]={exports:{}};return c[e].call(a.exports,a,a.exports,n),a.exports}n.m=c,e=[],n.O=(r,a,t,o)=>{if(!a){var c=1/0;for(f=0;f=o)&&Object.keys(n.O).every((e=>n.O[e](a[d])))?a.splice(d--,1):(l=!1,o0&&e[f-1][2]>o;f--)e[f]=e[f-1];e[f]=[a,t,o]},n.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return n.d(r,{a:r}),r},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,n.t=function(e,t){if(1&t&&(e=this(e)),8&t)return e;if("object"==typeof e&&e){if(4&t&&e.__esModule)return e;if(16&t&&"function"==typeof e.then)return e}var o=Object.create(null);n.r(o);var c={};r=r||[null,a({}),a([]),a(a)];for(var l=2&t&&e;"object"==typeof l&&!~r.indexOf(l);l=a(l))Object.getOwnPropertyNames(l).forEach((r=>c[r]=()=>e[r]));return c.default=()=>e,n.d(o,c),o},n.d=(e,r)=>{for(var a in r)n.o(r,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:r[a]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce(((r,a)=>(n.f[a](e,r),r)),[])),n.u=e=>"assets/js/"+({42:"reactPlayerTwitch",48:"a94703ab",66:"e7ed7793",98:"a7bd4aaa",173:"reactPlayerVimeo",235:"a7456010",328:"reactPlayerDailyMotion",340:"reactPlayerWistia",350:"66c1eed3",353:"reactPlayerPreview",392:"reactPlayerVidyard",396:"9d744931",401:"17896441",446:"reactPlayerYouTube",458:"reactPlayerFilePlayer",463:"reactPlayerKaltura",533:"71ffd77d",570:"reactPlayerMixcloud",627:"reactPlayerStreamable",634:"c4f5d8e4",647:"5e95c892",672:"22c5de9a",723:"reactPlayerMux",742:"aba21aa0",814:"b3d9ed0f",887:"reactPlayerFacebook",956:"37c4c8cd",979:"reactPlayerSoundCloud"}[e]||e)+"."+{42:"f8093db6",48:"6fa9d72a",66:"d03dfad9",98:"8ba4f90d",132:"fe654836",173:"e0bb6f48",235:"d3f42fc7",237:"f4ea580f",328:"2786b40e",340:"55782d35",350:"fe49005a",353:"e875752c",392:"fc0f3075",396:"c5feb62f",401:"9e05f0ab",446:"6b5e7fe3",458:"1d2055b5",463:"77229a02",533:"ca1b41b0",570:"530faa1a",627:"2e005b20",634:"f29e2922",647:"b6ccb016",672:"1b68f151",723:"155186bf",742:"b00a85c6",814:"69be21ab",887:"c103ff05",956:"a437f795",979:"199c972c"}[e]+".js",n.miniCssF=e=>{},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t={},o="reftree:",n.l=(e,r,a,c)=>{if(t[e])t[e].push(r);else{var l,d;if(void 0!==a)for(var i=document.getElementsByTagName("script"),f=0;f{l.onerror=l.onload=null,clearTimeout(s);var o=t[e];if(delete t[e],l.parentNode&&l.parentNode.removeChild(l),o&&o.forEach((e=>e(a))),r)return r(a)},s=setTimeout(y.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=y.bind(null,l.onerror),l.onload=y.bind(null,l.onload),d&&document.head.appendChild(l)}},n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.p="/reftree/",n.gca=function(e){return e={17896441:"401",reactPlayerTwitch:"42",a94703ab:"48",e7ed7793:"66",a7bd4aaa:"98",reactPlayerVimeo:"173",a7456010:"235",reactPlayerDailyMotion:"328",reactPlayerWistia:"340","66c1eed3":"350",reactPlayerPreview:"353",reactPlayerVidyard:"392","9d744931":"396",reactPlayerYouTube:"446",reactPlayerFilePlayer:"458",reactPlayerKaltura:"463","71ffd77d":"533",reactPlayerMixcloud:"570",reactPlayerStreamable:"627",c4f5d8e4:"634","5e95c892":"647","22c5de9a":"672",reactPlayerMux:"723",aba21aa0:"742",b3d9ed0f:"814",reactPlayerFacebook:"887","37c4c8cd":"956",reactPlayerSoundCloud:"979"}[e]||e,n.p+n.u(e)},(()=>{var e={354:0,869:0};n.f.j=(r,a)=>{var t=n.o(e,r)?e[r]:void 0;if(0!==t)if(t)a.push(t[2]);else if(/^(354|869)$/.test(r))e[r]=0;else{var o=new Promise(((a,o)=>t=e[r]=[a,o]));a.push(t[2]=o);var c=n.p+n.u(r),l=new Error;n.l(c,(a=>{if(n.o(e,r)&&(0!==(t=e[r])&&(e[r]=void 0),t)){var o=a&&("load"===a.type?"missing":a.type),c=a&&a.target&&a.target.src;l.message="Loading chunk "+r+" failed.\n("+o+": "+c+")",l.name="ChunkLoadError",l.type=o,l.request=c,t[1](l)}}),"chunk-"+r,r)}},n.O.j=r=>0===e[r];var r=(r,a)=>{var t,o,c=a[0],l=a[1],d=a[2],i=0;if(c.some((r=>0!==e[r]))){for(t in l)n.o(l,t)&&(n.m[t]=l[t]);if(d)var f=d(n)}for(r&&r(a);i Getting started | RefTree - + diff --git a/docs/Guide/index.html b/docs/Guide/index.html index 9c5dbf2..bc49beb 100644 --- a/docs/Guide/index.html +++ b/docs/Guide/index.html @@ -4,7 +4,7 @@ User guide | RefTree - + diff --git a/docs/index.html b/docs/index.html index 25633eb..99bd499 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,7 +4,7 @@ Overview | RefTree - + diff --git a/docs/talks/Immutability/index.html b/docs/talks/Immutability/index.html index 61aa14e..1ad7152 100644 --- a/docs/talks/Immutability/index.html +++ b/docs/talks/Immutability/index.html @@ -4,7 +4,7 @@ Unzipping Immutability | RefTree - + @@ -113,15 +113,15 @@

LensesMonocle library):

-
import monocle.macros.GenLens

val salaryLens = GenLens[Employee](_.salary)
// salaryLens: monocle.package.Lens[Employee, Long] = repl.MdocSession$MdocApp$$anon$1@1637d34f

salaryLens.get(startup.founder)
// res10: Long = 4000L
salaryLens.modify(s => s + 10)(startup.founder)
// res11: Employee = Employee(name = "Michael", salary = 4010L)
+
import monocle.macros.GenLens

val salaryLens = GenLens[Employee](_.salary)
// salaryLens: monocle.package.Lens[Employee, Long] = repl.MdocSession$MdocApp$$anon$1@239b5404

salaryLens.get(startup.founder)
// res10: Long = 4000L
salaryLens.modify(s => s + 10)(startup.founder)
// res11: Employee = Employee(name = "Michael", salary = 4010L)
diagram(OpticFocus(salaryLens, startup.founder)).render("salaryLens")

salaryLens

We can also define a lens that focuses on the startup’s founder:

-
val founderLens = GenLens[Startup](_.founder)
// founderLens: monocle.package.Lens[Startup, Employee] = repl.MdocSession$MdocApp$$anon$2@552a02e5

founderLens.get(startup)
// res13: Employee = Employee(name = "Michael", salary = 4000L)
+
val founderLens = GenLens[Startup](_.founder)
// founderLens: monocle.package.Lens[Startup, Employee] = repl.MdocSession$MdocApp$$anon$2@553edd38

founderLens.get(startup)
// res13: Employee = Employee(name = "Michael", salary = 4000L)
diagram(OpticFocus(founderLens, startup)).render("founderLens")

founderLens

It’s not apparent yet how this would help, but the trick is that lenses can be composed:

-
val founderSalaryLens = founderLens composeLens salaryLens
// founderSalaryLens: monocle.PLens[Startup, Startup, Long, Long] = monocle.PLens$$anon$1@7a1aec99

founderSalaryLens.get(startup)
// res15: Long = 4000L
founderSalaryLens.modify(s => s + 10)(startup)
// res16: Startup = Startup(
// name = "Acme",
// founder = Employee(name = "Michael", salary = 4010L),
// team = List(
// Employee(name = "Adam", salary = 2100L),
// Employee(name = "Bella", salary = 2100L),
// Employee(name = "Chad", salary = 1980L),
// Employee(name = "Delia", salary = 1850L)
// )
// )
+
val founderSalaryLens = founderLens composeLens salaryLens
// founderSalaryLens: monocle.PLens[Startup, Startup, Long, Long] = monocle.PLens$$anon$1@1d13cddb

founderSalaryLens.get(startup)
// res15: Long = 4000L
founderSalaryLens.modify(s => s + 10)(startup)
// res16: Startup = Startup(
// name = "Acme",
// founder = Employee(name = "Michael", salary = 4010L),
// team = List(
// Employee(name = "Adam", salary = 2100L),
// Employee(name = "Bella", salary = 2100L),
// Employee(name = "Chad", salary = 1980L),
// Employee(name = "Delia", salary = 1850L)
// )
// )
diagram(OpticFocus(founderSalaryLens, startup)).render("founderSalaryLens")

founderSalaryLens

One interesting thing is that lenses can focus on anything, not just direct attributes of the data. @@ -129,7 +129,7 @@

Lenses
diagram(OpticFocus(vowelTraversal, "example")).render("vowelTraversal")

vowelTraversal

We can use it to give our founder a funny name:

-
val employeeNameLens = GenLens[Employee](_.name)
// employeeNameLens: monocle.package.Lens[Employee, String] = repl.MdocSession$MdocApp$$anon$3@47f42c18
val founderVowelTraversal = founderLens composeLens employeeNameLens composeTraversal vowelTraversal
// founderVowelTraversal: monocle.PTraversal[Startup, Startup, Char, Char] = monocle.PTraversal$$anon$2@4283ed79

founderVowelTraversal.modify(v => v.toUpper)(startup)
// res19: Startup = Startup(
// name = "Acme",
// founder = Employee(name = "MIchAEl", salary = 4000L),
// team = List(
// Employee(name = "Adam", salary = 2100L),
// Employee(name = "Bella", salary = 2100L),
// Employee(name = "Chad", salary = 1980L),
// Employee(name = "Delia", salary = 1850L)
// )
// )
+
val employeeNameLens = GenLens[Employee](_.name)
// employeeNameLens: monocle.package.Lens[Employee, String] = repl.MdocSession$MdocApp$$anon$3@39284867
val founderVowelTraversal = founderLens composeLens employeeNameLens composeTraversal vowelTraversal
// founderVowelTraversal: monocle.PTraversal[Startup, Startup, Char, Char] = monocle.PTraversal$$anon$2@71a232ba

founderVowelTraversal.modify(v => v.toUpper)(startup)
// res19: Startup = Startup(
// name = "Acme",
// founder = Employee(name = "MIchAEl", salary = 4000L),
// team = List(
// Employee(name = "Adam", salary = 2100L),
// Employee(name = "Bella", salary = 2100L),
// Employee(name = "Chad", salary = 1980L),
// Employee(name = "Delia", salary = 1850L)
// )
// )
diagram(OpticFocus(founderVowelTraversal, startup)).render("founderVowelTraversal")

founderVowelTraversal

So far we have replaced the copy boilerplate with a number of lens declarations. diff --git a/docs/talks/Visualize/index.html b/docs/talks/Visualize/index.html index bc098e9..45eb272 100644 --- a/docs/talks/Visualize/index.html +++ b/docs/talks/Visualize/index.html @@ -4,7 +4,7 @@ Visualize your data structures! | RefTree - + @@ -71,14 +71,14 @@

Inside Now, what does it look like? The best way to find out is to visualize a RefTree of a RefTree!

diagram(Shortcuts.refTree(bob)).render("reftree")
-

reftree

+

reftree

As you can see, it contains values (Val) and references (Ref).

How do we get from RefTree to an image though? This is where GraphViz comes in. From a RefTree we can obtain a graph definition that can be rendered by GraphViz:

-
Shortcuts.graph(bob).encode
// res5: String = """digraph "Diagram" {
// graph [ ranksep=0.8 bgcolor="#ffffff00" ]
// node [ shape="plaintext" fontname="Source Code Pro" fontcolor="#000000ff" ]
// edge [ arrowsize=0.7 color="#000000ff" ]
// "-repl.MdocSession$MdocApp$Person1720614161" [ id="-repl.MdocSession$MdocApp$Person1720614161" label=<<table cellspacing="0" cellpadding="6" cellborder="0" columns="*" bgcolor="#ffffff00" style="rounded"><tr><td port="n" rowspan="2">Person</td><td bgcolor="#ffffff00"><i>name</i></td><td bgcolor="#ffffff00"><i>age</i></td></tr><hr/><tr><td port="0" bgcolor="#ffffff00">&middot;</td><td bgcolor="#ffffff00">42</td></tr></table>> ] [ color="#104e8bff" fontcolor="#104e8bff" ]
// "-java.lang.String337059875" [ id="-java.lang.String337059875" label=<<table cellspacing="0" cellpadding="6" cellborder="0" columns="*" bgcolor="#ffffff00" style="rounded"><tr><td port="n" rowspan="2">&quot;Bob&quot;</td></tr></table>> ] [ color="#104e8bff" fontcolor="#104e8bff" ]
// "-repl.MdocSession$MdocApp$Person1720614161":"0":"s" -> "-java.lang.String337059875":"n":"n" [ id="-repl.MdocSession$MdocApp$Person1720614161-0-java.lang.String337059875" ] [ color="#104e8bff" ]
// }"""
+
Shortcuts.graph(bob).encode
// res5: String = """digraph "Diagram" {
// graph [ ranksep=0.8 bgcolor="#ffffff00" ]
// node [ shape="plaintext" fontname="Source Code Pro" fontcolor="#000000ff" ]
// edge [ arrowsize=0.7 color="#000000ff" ]
// "-repl.MdocSession$MdocApp$Person342877447" [ id="-repl.MdocSession$MdocApp$Person342877447" label=<<table cellspacing="0" cellpadding="6" cellborder="0" columns="*" bgcolor="#ffffff00" style="rounded"><tr><td port="n" rowspan="2">Person</td><td bgcolor="#ffffff00"><i>name</i></td><td bgcolor="#ffffff00"><i>age</i></td></tr><hr/><tr><td port="0" bgcolor="#ffffff00">&middot;</td><td bgcolor="#ffffff00">42</td></tr></table>> ] [ color="#104e8bff" fontcolor="#104e8bff" ]
// "-java.lang.String1172745511" [ id="-java.lang.String1172745511" label=<<table cellspacing="0" cellpadding="6" cellborder="0" columns="*" bgcolor="#ffffff00" style="rounded"><tr><td port="n" rowspan="2">&quot;Bob&quot;</td></tr></table>> ] [ color="#104e8bff" fontcolor="#104e8bff" ]
// "-repl.MdocSession$MdocApp$Person342877447":"0":"s" -> "-java.lang.String1172745511":"n":"n" [ id="-repl.MdocSession$MdocApp$Person342877447-0-java.lang.String1172745511" ] [ color="#104e8bff" ]
// }"""

Going even further, we can ask GraphViz for an SVG output:

-
Shortcuts.svg(bob)
// res6: xml.Node = <svg width="171pt" height="168pt" viewBox="0.00 0.00 171.00 168.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="graph0" class="graph"><title>Diagram</title><polygon fill="transparent" stroke="transparent" points="-4,4 -4,-164 167,-164 167,4 -4,4"/><!-- &#45;repl.MdocSession$MdocApp$Person1720614161 --><g id="-repl.MdocSession$MdocApp$Person1720614161" class="node"><title>-repl.MdocSession$MdocApp$Person1720614161</title><path fill="transparent" stroke="transparent" d="M20,-100C20,-100 143,-100 143,-100 149,-100 155,-106 155,-112 155,-112 155,-144 155,-144 155,-150 149,-156 143,-156 143,-156 20,-156 20,-156 14,-156 8,-150 8,-144 8,-144 8,-112 8,-112 8,-106 14,-100 20,-100"/><text text-anchor="start" x="15.5" y="-124.3" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">Person</text><polygon fill="transparent" stroke="transparent" points="71.5,-128 71.5,-155 117.5,-155 117.5,-128 71.5,-128"/><text text-anchor="start" x="77.5" y="-138.8" font-family="Source Code Pro" font-style="italic" font-size="14.00" fill="#104e8b">name</text><polygon fill="transparent" stroke="transparent" points="117.5,-128 117.5,-155 154.5,-155 154.5,-128 117.5,-128"/><text text-anchor="start" x="123.5" y="-138.8" font-family="Source Code Pro" font-style="italic" font-size="14.00" fill="#104e8b">age</text><polygon fill="transparent" stroke="transparent" points="71.5,-101 71.5,-128 117.5,-128 117.5,-101 71.5,-101"/><text text-anchor="start" x="90" y="-110.8" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">·</text><polygon fill="transparent" stroke="transparent" points="117.5,-101 117.5,-128 154.5,-128 154.5,-101 117.5,-101"/><text text-anchor="start" x="127.5" y="-110.8" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">42</text><polygon fill="#104e8b" stroke="#104e8b" points="71.5,-101 71.5,-156 71.5,-156 71.5,-101 71.5,-101"/><polygon fill="#104e8b" stroke="#104e8b" points="117.5,-128 117.5,-156 117.5,-156 117.5,-128 117.5,-128"/><polygon fill="#104e8b" stroke="#104e8b" points="71.5,-128 71.5,-128 117.5,-128 117.5,-128 71.5,-128"/><polygon fill="#104e8b" stroke="#104e8b" points="117.5,-128 117.5,-128 155.5,-128 155.5,-128 117.5,-128"/><polygon fill="#104e8b" stroke="#104e8b" points="117.5,-100 117.5,-128 117.5,-128 117.5,-100 117.5,-100"/><path fill="none" stroke="#104e8b" d="M20,-100C20,-100 143,-100 143,-100 149,-100 155,-106 155,-112 155,-112 155,-144 155,-144 155,-150 149,-156 143,-156 143,-156 20,-156 20,-156 14,-156 8,-150 8,-144 8,-144 8,-112 8,-112 8,-106 14,-100 20,-100"/></g><!-- &#45;java.lang.String337059875 --><g id="-java.lang.String337059875" class="node"><title>-java.lang.String337059875</title><path fill="transparent" stroke="transparent" d="M76.5,-4C76.5,-4 112.5,-4 112.5,-4 117.5,-4 122.5,-9 122.5,-14 122.5,-14 122.5,-24 122.5,-24 122.5,-29 117.5,-34 112.5,-34 112.5,-34 76.5,-34 76.5,-34 71.5,-34 66.5,-29 66.5,-24 66.5,-24 66.5,-14 66.5,-14 66.5,-9 71.5,-4 76.5,-4"/><text text-anchor="start" x="73.5" y="-15.3" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">&quot;Bob&quot;</text><path fill="none" stroke="#104e8b" d="M76.5,-4C76.5,-4 112.5,-4 112.5,-4 117.5,-4 122.5,-9 122.5,-14 122.5,-14 122.5,-24 122.5,-24 122.5,-29 117.5,-34 112.5,-34 112.5,-34 76.5,-34 76.5,-34 71.5,-34 66.5,-29 66.5,-24 66.5,-24 66.5,-14 66.5,-14 66.5,-9 71.5,-4 76.5,-4"/></g><!-- &#45;repl.MdocSession$MdocApp$Person1720614161&#45;&gt;&#45;java.lang.String337059875 --><g id="-repl.MdocSession$MdocApp$Person1720614161-0-java.lang.String337059875" class="edge"><title>-repl.MdocSession$MdocApp$Person1720614161:s-&gt;-java.lang.String337059875:n</title><path fill="none" stroke="#104e8b" d="M94.5,-100C94.5,-73.19 94.5,-64.76 94.5,-41.1"/><polygon fill="#104e8b" stroke="#104e8b" points="96.95,-41 94.5,-34 92.05,-41 96.95,-41"/></g></g></svg>
+
Shortcuts.svg(bob)
// res6: xml.Node = <svg width="171pt" height="168pt" viewBox="0.00 0.00 171.00 168.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="graph0" class="graph"><title>Diagram</title><polygon fill="transparent" stroke="transparent" points="-4,4 -4,-164 167,-164 167,4 -4,4"/><!-- &#45;repl.MdocSession$MdocApp$Person342877447 --><g id="-repl.MdocSession$MdocApp$Person342877447" class="node"><title>-repl.MdocSession$MdocApp$Person342877447</title><path fill="transparent" stroke="transparent" d="M20,-100C20,-100 143,-100 143,-100 149,-100 155,-106 155,-112 155,-112 155,-144 155,-144 155,-150 149,-156 143,-156 143,-156 20,-156 20,-156 14,-156 8,-150 8,-144 8,-144 8,-112 8,-112 8,-106 14,-100 20,-100"/><text text-anchor="start" x="15.5" y="-124.3" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">Person</text><polygon fill="transparent" stroke="transparent" points="71.5,-128 71.5,-155 117.5,-155 117.5,-128 71.5,-128"/><text text-anchor="start" x="77.5" y="-138.8" font-family="Source Code Pro" font-style="italic" font-size="14.00" fill="#104e8b">name</text><polygon fill="transparent" stroke="transparent" points="117.5,-128 117.5,-155 154.5,-155 154.5,-128 117.5,-128"/><text text-anchor="start" x="123.5" y="-138.8" font-family="Source Code Pro" font-style="italic" font-size="14.00" fill="#104e8b">age</text><polygon fill="transparent" stroke="transparent" points="71.5,-101 71.5,-128 117.5,-128 117.5,-101 71.5,-101"/><text text-anchor="start" x="90" y="-110.8" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">·</text><polygon fill="transparent" stroke="transparent" points="117.5,-101 117.5,-128 154.5,-128 154.5,-101 117.5,-101"/><text text-anchor="start" x="127.5" y="-110.8" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">42</text><polygon fill="#104e8b" stroke="#104e8b" points="71.5,-101 71.5,-156 71.5,-156 71.5,-101 71.5,-101"/><polygon fill="#104e8b" stroke="#104e8b" points="117.5,-128 117.5,-156 117.5,-156 117.5,-128 117.5,-128"/><polygon fill="#104e8b" stroke="#104e8b" points="71.5,-128 71.5,-128 117.5,-128 117.5,-128 71.5,-128"/><polygon fill="#104e8b" stroke="#104e8b" points="117.5,-128 117.5,-128 155.5,-128 155.5,-128 117.5,-128"/><polygon fill="#104e8b" stroke="#104e8b" points="117.5,-100 117.5,-128 117.5,-128 117.5,-100 117.5,-100"/><path fill="none" stroke="#104e8b" d="M20,-100C20,-100 143,-100 143,-100 149,-100 155,-106 155,-112 155,-112 155,-144 155,-144 155,-150 149,-156 143,-156 143,-156 20,-156 20,-156 14,-156 8,-150 8,-144 8,-144 8,-112 8,-112 8,-106 14,-100 20,-100"/></g><!-- &#45;java.lang.String1172745511 --><g id="-java.lang.String1172745511" class="node"><title>-java.lang.String1172745511</title><path fill="transparent" stroke="transparent" d="M76.5,-4C76.5,-4 112.5,-4 112.5,-4 117.5,-4 122.5,-9 122.5,-14 122.5,-14 122.5,-24 122.5,-24 122.5,-29 117.5,-34 112.5,-34 112.5,-34 76.5,-34 76.5,-34 71.5,-34 66.5,-29 66.5,-24 66.5,-24 66.5,-14 66.5,-14 66.5,-9 71.5,-4 76.5,-4"/><text text-anchor="start" x="73.5" y="-15.3" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">&quot;Bob&quot;</text><path fill="none" stroke="#104e8b" d="M76.5,-4C76.5,-4 112.5,-4 112.5,-4 117.5,-4 122.5,-9 122.5,-14 122.5,-14 122.5,-24 122.5,-24 122.5,-29 117.5,-34 112.5,-34 112.5,-34 76.5,-34 76.5,-34 71.5,-34 66.5,-29 66.5,-24 66.5,-24 66.5,-14 66.5,-14 66.5,-9 71.5,-4 76.5,-4"/></g><!-- &#45;repl.MdocSession$MdocApp$Person342877447&#45;&gt;&#45;java.lang.String1172745511 --><g id="-repl.MdocSession$MdocApp$Person342877447-0-java.lang.String1172745511" class="edge"><title>-repl.MdocSession$MdocApp$Person342877447:s-&gt;-java.lang.String1172745511:n</title><path fill="none" stroke="#104e8b" d="M94.5,-100C94.5,-73.19 94.5,-64.76 94.5,-41.1"/><polygon fill="#104e8b" stroke="#104e8b" points="96.95,-41 94.5,-34 92.05,-41 96.95,-41"/></g></g></svg>

At this point you might be guessing how we can use this as a basis for our animation approach. Every state of a data structure will be a separate frame in the SVG format. However, an animation consisting of these frames alone would be too jumpy. @@ -114,7 +114,7 @@

Functio inside a data structure of type A and provide read-write access to it. We will use the excellent Monocle library to create lenses and other optics along the way:

-
import monocle.macros.GenLens

val x = GenLens[Point](_.x)
// x: monocle.package.Lens[Point, Double] = repl.MdocSession$MdocApp$$anon$1@3ecdc46e
val y = GenLens[Point](_.y)
// y: monocle.package.Lens[Point, Double] = repl.MdocSession$MdocApp$$anon$2@2426d136

(diagram(OpticFocus(x, point)).toNamespace("x") +
diagram(OpticFocus(y, point)).toNamespace("y")).render("x+y")
+
import monocle.macros.GenLens

val x = GenLens[Point](_.x)
// x: monocle.package.Lens[Point, Double] = repl.MdocSession$MdocApp$$anon$1@1b1555ca
val y = GenLens[Point](_.y)
// y: monocle.package.Lens[Point, Double] = repl.MdocSession$MdocApp$$anon$2@1ba92b68

(diagram(OpticFocus(x, point)).toNamespace("x") +
diagram(OpticFocus(y, point)).toNamespace("y")).render("x+y")

x+y

Lenses provide several methods to manipulate data:

x.get(point)
// res10: Double = 0.0
y.set(20)(point)
// res11: Point = Point(x = 0.0, y = 20.0)
y.modify(_ + 20)(point)
// res12: Point = Point(x = 0.0, y = 30.0)
@@ -122,12 +122,12 @@

Functio and update the point field by field. We do this by piping Interpolation.double through x and y lenses and combining the resulting interpolations:

-
val pointInterpolation = (
x.interpolateWith(Interpolation.double) +
y.interpolateWith(Interpolation.double))
// pointInterpolation: Interpolation[Point] = reftree.geometry.Interpolation$$anonfun$apply$4@22a4cc89

val points = pointInterpolation.sample(Point(0, 0), Point(10, 20), 5).toList
// points: List[Point] = List(
// Point(x = 0.0, y = 0.0),
// Point(x = 2.5, y = 5.0),
// Point(x = 5.0, y = 10.0),
// Point(x = 7.5, y = 15.0),
// Point(x = 10.0, y = 20.0)
// )

diagram(points).render("points")
+
val pointInterpolation = (
x.interpolateWith(Interpolation.double) +
y.interpolateWith(Interpolation.double))
// pointInterpolation: Interpolation[Point] = reftree.geometry.Interpolation$$anonfun$apply$4@2bccedad

val points = pointInterpolation.sample(Point(0, 0), Point(10, 20), 5).toList
// points: List[Point] = List(
// Point(x = 0.0, y = 0.0),
// Point(x = 2.5, y = 5.0),
// Point(x = 5.0, y = 10.0),
// Point(x = 7.5, y = 15.0),
// Point(x = 10.0, y = 20.0)
// )

diagram(points).render("points")

points

Of course, reftree already defines this as Point.interpolation.

Using the same approach, we can build a polyline interpolator (assuming the polylines being interpolated consist of equal number of points):

-
Data.polyline1
// res14: Polyline = Polyline(
// points = List(Point(x = 0.0, y = 10.0), Point(x = 10.0, y = 20.0))
// )
Data.polyline2
// res15: Polyline = Polyline(
// points = List(Point(x = 20.0, y = 30.0), Point(x = 40.0, y = 50.0))
// )

val polylineInterpolation = (GenLens[Polyline](_.points)
.interpolateEachWith(Point.interpolation))
// polylineInterpolation: Interpolation[Polyline] = reftree.geometry.Interpolation$$anonfun$apply$4@30694434

val polylines = polylineInterpolation.sample(Data.polyline1, Data.polyline2, 3).toList
// polylines: List[Polyline] = List(
// Polyline(points = List(Point(x = 0.0, y = 10.0), Point(x = 10.0, y = 20.0))),
// Polyline(points = List(Point(x = 10.0, y = 20.0), Point(x = 25.0, y = 35.0))),
// Polyline(points = List(Point(x = 20.0, y = 30.0), Point(x = 40.0, y = 50.0)))
// )

diagram(polylines).render("polylines")
+
Data.polyline1
// res14: Polyline = Polyline(
// points = List(Point(x = 0.0, y = 10.0), Point(x = 10.0, y = 20.0))
// )
Data.polyline2
// res15: Polyline = Polyline(
// points = List(Point(x = 20.0, y = 30.0), Point(x = 40.0, y = 50.0))
// )

val polylineInterpolation = (GenLens[Polyline](_.points)
.interpolateEachWith(Point.interpolation))
// polylineInterpolation: Interpolation[Polyline] = reftree.geometry.Interpolation$$anonfun$apply$4@8ad0725

val polylines = polylineInterpolation.sample(Data.polyline1, Data.polyline2, 3).toList
// polylines: List[Polyline] = List(
// Polyline(points = List(Point(x = 0.0, y = 10.0), Point(x = 10.0, y = 20.0))),
// Polyline(points = List(Point(x = 10.0, y = 20.0), Point(x = 25.0, y = 35.0))),
// Polyline(points = List(Point(x = 20.0, y = 30.0), Point(x = 40.0, y = 50.0)))
// )

diagram(polylines).render("polylines")

polylines

We are finally ready to implement our first substantial interpolator: one that morphs graph edges. The following approach is inspired by Mike Bostock’s path tween, @@ -150,20 +150,20 @@

Functio
  • An optic that focuses on an element deep inside XML or any other recursive data structure: Optics.collectFirst. It is actually an Optional, not a Lens, since the element might be missing.
  • -
    val edgePathElement = Optics.collectFirst(XmlSvgApi.select("path"))
    // edgePathElement: monocle.package.Optional[xml.Node, xml.Node] = monocle.Optional$$anon$6@60d718e3

    diagram(OpticFocus(edgePathElement, Data.edge1)).render("edgePathElement")
    +
    val edgePathElement = Optics.collectFirst(XmlSvgApi.select("path"))
    // edgePathElement: monocle.package.Optional[xml.Node, xml.Node] = monocle.Optional$$anon$6@3927a8ff

    diagram(OpticFocus(edgePathElement, Data.edge1)).render("edgePathElement")

    edgePathElement

    Next, we need to “descend” to the d attribute. Here is where optics really shine: we can compose Optional[A, B] with Optional[B, C] to get an Optional[A, C]:

    -
    val d = XmlSvgApi.attr("d")
    // d: monocle.package.Optional[xml.Node, String] = monocle.POptional$$anon$1@72eadaae
    val edgePathString = edgePathElement composeOptional d
    // edgePathString: monocle.POptional[xml.Node, xml.Node, String, String] = monocle.POptional$$anon$1@762e80fe

    diagram(OpticFocus(edgePathString, Data.edge1)).render("edgePathString")
    +
    val d = XmlSvgApi.attr("d")
    // d: monocle.package.Optional[xml.Node, String] = monocle.POptional$$anon$1@6b0726d8
    val edgePathString = edgePathElement composeOptional d
    // edgePathString: monocle.POptional[xml.Node, xml.Node, String, String] = monocle.POptional$$anon$1@276a0f90

    diagram(OpticFocus(edgePathString, Data.edge1)).render("edgePathString")

    edgePathString

    Next, we will use an isomorphism, another kind of optic, to view the string as a nice case class:

    -
    Path.stringIso
    // res21: monocle.package.Iso[String, Path] = monocle.PIso$$anon$9@c1c32eb

    val edgePath = edgePathString composeIso Path.stringIso
    // edgePath: monocle.POptional[xml.Node, xml.Node, Path, Path] = monocle.POptional$$anon$1@54d4338b

    diagram(edgePath.getOption(Data.edge1)).render("edgePath")
    +
    Path.stringIso
    // res21: monocle.package.Iso[String, Path] = monocle.PIso$$anon$9@471ea077

    val edgePath = edgePathString composeIso Path.stringIso
    // edgePath: monocle.POptional[xml.Node, xml.Node, Path, Path] = monocle.POptional$$anon$1@43fa983b

    diagram(edgePath.getOption(Data.edge1)).render("edgePath")

    edgePath

    And finally, another isomorphism takes us from a Path to its sampled representation as a Polyline. (Purists will say that this is not really an isomorphism because it’s not reversible, but with a lot of points you can get pretty close ;))

    -
    Path.polylineIso(points = 4)
    // res23: monocle.package.Iso[Path, Polyline] = monocle.PIso$$anon$9@704fac15

    def edgePolyline(points: Int) = edgePath composeIso Path.polylineIso(points)

    diagram(edgePolyline(4).getOption(Data.edge1)).render("edgePolyline")
    +
    Path.polylineIso(points = 4)
    // res23: monocle.package.Iso[Path, Polyline] = monocle.PIso$$anon$9@1199577a

    def edgePolyline(points: Int) = edgePath composeIso Path.polylineIso(points)

    diagram(edgePolyline(4).getOption(Data.edge1)).render("edgePolyline")

    edgePolyline

    Let’s interpolate!

    def edgeInterpolation(points: Int) = edgePolyline(points).interpolateWith(Polyline.interpolation)

    def edges(points: Int, frames: Int) = (Data.edge1 +:
    edgeInterpolation(points).sample(Data.edge1, Data.edge2, frames, inclusive = false) :+
    Data.edge2)

    AnimatedGifRenderer.renderFrames(
    edges(4, 4).map(Frame(_)),
    Paths.get(ImagePath, "visualize", "edges-4.gif"),
    RenderingOptions(density = 200),
    AnimationOptions(framesPerSecond = 1)
    )

    AnimatedGifRenderer.renderFrames(
    edges(100, 32).map(Frame(_)),
    Paths.get(ImagePath, "visualize", "edges-100.gif"),
    RenderingOptions(density = 200),
    AnimationOptions(framesPerSecond = 8)
    )
    diff --git a/docs/talks/index.html b/docs/talks/index.html index ba5e518..2341273 100644 --- a/docs/talks/index.html +++ b/docs/talks/index.html @@ -4,7 +4,7 @@ Talks / Demos | RefTree - + diff --git a/index.html b/index.html index 5db1797..4e73377 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ RefTree - +