Repository navigation
Expand file tree
/
Copy pathatom.xml
More file actions
1993 lines (1980 loc) · 157 KB
/
Copy pathatom.xml
File metadata and controls
1993 lines (1980 loc) · 157 KB
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>https://faded.auspicious.space</id>
<title>Typhoeus</title>
<updated>2020-06-11T10:49:24.852Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<link rel="alternate" href="https://faded.auspicious.space"/>
<link rel="self" href="https://faded.auspicious.space/atom.xml"/>
<logo>https://faded.auspicious.space/images/avatar.png</logo>
<icon>https://faded.auspicious.space/favicon.ico</icon>
<rights>All rights reserved 2020, Typhoeus</rights>
<entry>
<title type="html"><![CDATA[The Nature of Lisp]]></title>
<id>https://faded.auspicious.space/post/the-nature-of-lisp/</id>
<link href="https://faded.auspicious.space/post/the-nature-of-lisp/">
</link>
<updated>2020-05-31T13:07:56.000Z</updated>
<summary type="html"><![CDATA[<blockquote>
<p><a href="http://www.defmacro.org/ramblings/lisp.html">The Nature of Lisp</a></p>
</blockquote>
]]></summary>
<content type="html"><![CDATA[<blockquote>
<p><a href="http://www.defmacro.org/ramblings/lisp.html">The Nature of Lisp</a></p>
</blockquote>
<!-- more -->
<h1 id="introduction">Introduction</h1>
<p>When I first stumbled into Lisp advocacy on various corners of the web I was already an experienced programmer. At that point I had grokked what seemed at the time a wide range of programming languages. I was proud to have the usual suspects (C++, Java, C#, etc.) on my service record and was under impression that I knew everything there is to know about programming languages. I couldn't have possibly been more wrong.</p>
<p>My initial attempt to learn Lisp came to a crashing halt as soon as I saw some sample code. I suppose the same thought ran through my mind that ran through thousands of other minds who were ever in my shoes: "Why on Earth would anyone want to use a language with such horrific syntax?!" I couldn't be bothered to learn a language if its creators couldn't be bothered to give it a pleasant syntax. After all, I was almost blinded by the infamous Lisp parentheses!</p>
<p>The moment I regained my sight I communicated my frustrations to some members of the Lisp sect. Almost immediately I was bombarded by a standard set of responses: Lisp's parentheses are only a superficial matter, Lisp has a huge benefit of code and data being expressed in the same manner (which, obviously, is a huge improvement over XML), Lisp has tremendously powerful metaprogramming facilities that allow programs to write code and modify themselves, Lisp allows for creation of mini-languages specific to the problem at hand, Lisp blurs the distinction between run time and compile time, Lisp, Lisp, Lisp... The list was very impressive. Needless to say none of it made sense. Nobody could illustrate the usefulness of these features with specific examples because these techniques are supposedly only useful in large software systems. After many hours of debating that conventional programming languages do the job just fine, I gave up. I wasn't about to invest months into learning a language with a terrible syntax in order to understand obscure features that had no useful examples. My time has not yet come.</p>
<p>For many months the Lisp advocates pressed on. I was baffled. Many extremely intelligent people I knew and had much respect for were praising Lisp with almost religious dedication. There had to be something there, something I couldn't afford not to get my hands on! Eventually my thirst for knowledge won me over. I took the plunge, bit the bullet, got my hands dirty, and began months of mind bending exercises. It was a journey on an endless lake of frustration. I turned my mind inside out, rinsed it, and put it back in place. I went through seven rings of hell and came back. And then I got it.</p>
<p>The enlightenment came instantaneously. One moment I understood nothing, and the next moment everything clicked into place. I've achieved nirvana. Dozens of times I heard Eric Raymond's statement quoted by different people: "Lisp is worth learning for the profound enlightenment experience you will have when you finally get it; that experience will make you a better programmer for the rest of your days, even if you never actually use Lisp itself a lot." I never understood this statement. I never believed it could be true. And finally, after all the pain, it made sense! There was more truth to it than I ever could have imagined. I've achieved an almost divine state of mind, an instantaneous enlightenment experience that turned my view of computer science on its head in less than a single second.</p>
<p>That very second I became a member of the Lisp cult. I felt something a ninjitsu master must feel: I had to spread my newfound knowledge to at least ten lost souls in the course of my lifetime. I took the usual path. I was rehashing the same arguments that were given to me for years (only now they actually made sense!), hoping to convert unsuspecting bystanders. It didn't work. My persistence sparked a few people's interest but their curiosity dwindled at the mere sight of sample Lisp code. Perhaps years of advocacy would forge a few new Lispers, but I wasn't satisfied. There had to be a better way.</p>
<p>I gave the matter careful thought. Is there something inherently hard about Lisp that prevents very intelligent, experienced programmers from understanding it? No, there isn't. After all, I got it, and if I can do it, anybody can. Then what is it that makes Lisp so hard to understand? The answer, as such things usually do, came unexpectedly. Of course! Teaching anybody anything involves building advanced concepts on top of concepts they already understand! If the process is made interesting and the matter is explained properly the new concepts become as intuitive as the original building blocks that aided their understanding. That was the problem! Metaprogramming, code and data in one representation, self-modifying programs, domain specific mini-languages, none of the explanations for these concepts referenced familiar territory. How could I expect anyone to understand them! No wonder people wanted specific examples. I could as well have been speaking in Martian!</p>
<p>I shared my ideas with fellow Lispers. "Well, of course these concepts aren't explained in terms of familiar territory", they said. "They are so different, they're unlike anything these people have learned before." This was a poor excuse. "I do not believe this to be true", I said. The response was unanimous: "Why don't you give it a try?" So I did. This article is a product of my efforts. It is my attempt to explain Lisp in familiar, intuitive concepts. I urge brave souls to read on. Grab your favorite drink. Take a deep breath. Prepare to be blown away. Oh, and may the Force be with you.</p>
<h1 id="xml-reloaded">XML Reloaded</h1>
<p>A thousand mile journey starts with a single step. A journey to enlightenment is no exception and our first step just happens to be XML. What more could possibly be said about XML that hasn't already been said? It turns out, quite a bit. While there's nothing particularly interesting about XML itself, its relationship to Lisp is fascinating. XML is the all too familiar concept that Lisp advocates need so much. It is our bridge to conveying understanding to regular programmers. So let's revive the dead horse, take out the stick, and venture into XML wilderness that no one dared venture into before us. It's time to see the all too familiar moon from the other side.</p>
<p>Superficially XML is nothing more than a standardized syntax used to express arbitrary hierarchical data in human readable form. To-do lists, web pages, medical records, auto insurance claims, configuration files are all examples of potential XML use. Let's use a simple to-do list as an example (in a couple of sections you'll see it in a whole new light):</p>
<pre><code class="language-xml"><todo name="housework">
<item priority="high">Clean the house.</item>
<item priority="medium">Wash the dishes.</item>
<item priority="medium">Buy more soap.</item>
</todo>
</code></pre>
<p>What happens if we unleash our favorite XML parser on this to-do list? Once the data is parsed, how is it represented in memory? The most natural representation is, of course, a tree - a perfect data structure for hierarchical data. After all is said and done, XML is really just a tree serialized to a human readable form. Anything that can be represented in a tree can be represented in XML and vice versa. I hope you understand this idea. It's very important for what's coming next.</p>
<p>Let's take this a little further. What other type of data is often represented as a tree? At this point the list is as good as infinite so I'll give you a hint at what I'm getting at - try to remember your old compiler course. If you have a vague recollection that source code is stored in a tree after it's parsed, you're on the right track. Any compiler inevitably parses the source code into an abstract syntax tree. This isn't surprising since source code is hierarchical: functions contain arguments and blocks of code. Blocks of code contain expressions and statements. Expressions contain variables and operators. And so it goes.</p>
<p>Let's apply our corollary that any tree can easily be serialized into XML to this idea. If all source code is eventually represented as a tree, and any tree can be serialized into XML, then all source code can be converted to XML, right? Let's illustrate this interesting property by a simple example. Consider the function below:</p>
<pre><code class="language-cpp">int add(int arg1, int arg2)
{
return arg1 + arg2;
}
</code></pre>
<p>Can you convert this function definition to its XML equivalent? Turns out, it's reasonably simple. Naturally there are many ways to do this. Here is one way the resulting XML can look like:</p>
<pre><code class="language-xml"><define-function return-type="int" name="add">
<arguments>
<argument type="int">arg1</argument>
<argument type="int">arg2</argument>
</arguments>
<body>
<return>
<add value1="arg1" value2="arg2" />
</return>
</body>
</define>
</code></pre>
<p>We can go through this relatively simple exercise with any language. We can turn any source code into XML, and we can transform the resulting XML back to original source code. We can write a converter that turns Java into XML and a converter that turns XML back to Java. We could do the same for C++. (In case you're wondering if anyone is crazy enough to do it, take a look at <a href="http://www.gccxml.org/">GCC-XML</a>). Furthermore, for languages that share common features but use different syntax (which to some extent is true about most mainstream languages) we could convert source code from one language to another using XML as an intermediary representation. We could use our Java2XML converter to convert a Java program to XML. We could then run an XML2CPP converter on the resulting XML and turn it into C++ code. With any luck (if we avoid using features of Java that don't exist in C++) we'll get a working C++ program. Neat, eh?</p>
<p>All this effectively means that we can use XML for generic storage of source code. We'd be able to create a whole class of programming languages that use uniform syntax, as well as write transformers that convert existing source code to XML. If we were to actually adopt this idea, compilers for different languages wouldn't need to implement parsers for their specific grammars - they'd simply use an XML parser to turn XML directly into an abstract syntax tree.</p>
<p>By now you're probably wondering why I've embarked on the XML crusade and what it has to do with Lisp (after all, Lisp was created about thirty years before XML). I promise that everything will become clear soon enough. But before we take our second step, let's go through a small philosophical exercise. Take a good look at the XML version of our "add" function above. How would you classify it? Is it data or code? If you think about it for a moment you'll realize that there are good reasons to put this XML snippet into both categories. It's XML and it's just information encoded in a standardized format. We've already determined that it can be generated from a tree data structure in memory (that's effectively what GCC-XML does). It's lying around in a file with no apparent way to execute it. We can parse it into a tree of XML nodes and do various transformations on it. It's data. But wait a moment! When all is said and done it's the same "add" function written with a different syntax, right? Once parsed, its tree could be fed into a compiler and we could execute it. We could easily write a small interpreter for this XML code and we could execute it directly. Alternatively, we could transform it into Java or C++ code, compile it, and run it. It's code.</p>
<p>So, where are we? Looks like we've just arrived to an interesting point. A concept that has traditionally been so hard to understand is now amazingly simple and intuitive. Code is also always data! Does it mean that data is also always code? As crazy as this sounds this very well might be the case. Remember how I promised that you'll see our to-do list in a whole new light? Let me reiterate on that promise. But we aren't ready to discuss this just yet. For now let's continue walking down our path.</p>
<p>A little earlier I mentioned that we could easily write an interpreter to execute our XML snippet of the add function. Of course this sounds like a purely theoretical exercise. Who in their right mind would want to do that for practical purposes? Well, it turns out quite a few people would disagree. You've likely encountered and used their work at least once in your career, too. Do I have you out on the edge of your seat? If so, let's move on!</p>
<h1 id="ant-reloaded">Ant Reloaded</h1>
<p>Now that we've made the trip to the dark side of the moon, let's not leave quite yet. We may still learn something by exploring it a little more, so let's take another step. We begin by closing our eyes and remembering a cold rainy night in the winter of 2000. A prominent developer by the name of [James Duncan Davidson][1] was hacking his way through <a href="http://tomcat.apache.org/">Tomcat</a> servlet container. As the time came to build the changes he carefully saved all his files and ran make. Errors. Lots of errors. Something was wrong. After careful examination James exclaimed: "Is my command not executing because I have a space in front of my tab?!" Indeed, this was the problem. Again. James has had enough. He could sense the full moon through the clouds and it made him adventurous. He created a fresh Java project and quickly hacked together a simple but surprisingly useful utility. This spark of genius used Java property files for information on how to build the project. James could now write the equivalent of the makefile in a nice format without worrying about the damned spaces ever again. His utility did all the hard work by interpreting the property file and taking appropriate actions to build the project. It was neat. Another Neat Tool. <a href="http://ant.apache.org/">Ant</a>.</p>
<p>After using Ant to build Tomcat for a few months it became clear that Java property files are not sufficient to express complicated build instructions. Files needed to be checked out, copied, compiled, sent to another machine, and unit tested. In case of failure e-mails needed to be sent out to appropriate people. In case of success "Bad to the Bone" needed to be played at the highest possible volume. At the end of the track volume had to be restored to its original level. Yes, Java property files didn't cut it anymore. James needed a more flexible solution. He didn't feel like writing his own parser (especially since he wanted an industry standard solution). XML seemed like a reasonable alternative. In a couple of days Ant was ported to XML. It was the best thing since sliced bread.</p>
<p>So how does Ant work? It's pretty simple. It takes an XML file with specific build instructions (you decide if they're data or code) and interprets them by running specialized Java code for each XML element. It's actually much simpler than it sounds. A simple XML instruction like the one below causes a Java class with an equivalent name to be loaded and its code to be executed.</p>
<pre><code class="language-xml"><copy todir="../new/dir">
<fileset dir="src_dir"/>
</copy>
</code></pre>
<p>The snippet above copies a source directory to a destination directory. Ant locates a "copy" task (a Java class, really), sets appropriate parameters (todir and fileset) by calling appropriate Java methods and then executes the task. Ant comes with a set of core tasks and anyone can extend it with tasks of their own simply by writing Java classes that follow certain conventions. Ant finds these classes and executes them whenever XML elements with appropriate names are encountered. Pretty simple. Effectively Ant accomplishes what we were talking about in the previous section: it acts as an interpreter for a language that uses XML as its syntax by translating XML elements to appropriate Java instructions. We could write an "add" task and have Ant execute it when it encounters the XML snippet for addition presented in the previous section! Considering that Ant is an extremely popular project, the ideas presented in the previous section start looking more sane. After all, they're being used every day in what probably amounts to thousands of companies!</p>
<p>So far I've said nothing about why Ant actually goes through all the trouble of interpreting XML. Don't try to look for the answer on its website either - you'll find nothing of value. Nothing relevant to our discussion, anyway. Let's take another step. It's time to find out why.</p>
<h1 id="why-xml">Why XML?</h1>
<p>Sometimes right decisions are made without full conscious understanding of all the issues involved. I'm not sure if James knew why he chose XML - it was likely a subconscious decision. At the very least, the reasons I saw on Ant's website for using XML are all the wrong reasons. It appears that the main concerns revolved around portability and extensibility. I fail to see how XML helps advance these goals in Ant's case. What is the advantage of using interpreted XML over simple Java source code? Why not create a set of classes with a nice API for commonly used tasks (copying directories, compiling, etc.) and using those directly from Java source code? This would run on every platform that runs Java (which Ant requires anyway), it's infinitely extensible, and it has the benefit of having a more pleasant, familiar syntax. So why XML? Can we find a good reason for using it?</p>
<p>It turns out that we can (although as I mentioned earlier I'm not sure if James was consciously aware of it). XML has the property of being far more flexible in terms of introduction of semantic constructs than Java could ever hope to be. Don't worry, I'm not falling into the trap of using big words to describe incomprehensible concepts. This is actually a relatively simple idea, though it may take some effort to explain. Buckle your seat-belt. We're about to make a giant leap towards achieving nirvana.</p>
<p>How can we represent 'copy' example above in Java code? Here's one way to do it:</p>
<pre><code class="language-java">CopyTask copy = new CopyTask();
Fileset fileset = new Fileset();
fileset.setDir("src_dir");
copy.setToDir("../new/dir");
copy.setFileset(fileset);
copy.execute();
</code></pre>
<p>The code is almost the same, albeit a little longer than the original XML. So what's different? The answer is that the XML snippet introduces a special semantic construct for copying. If we could do it in Java it would look like this:</p>
<pre><code class="language-java">copy("../new/dir")
{
fileset("src_dir");
}
</code></pre>
<p>Can you see the difference? The code above (if it were possible in Java) is a special operator for copying files - similar to a for loop or a new foreach construct introduced in Java 5. If we had an automatic converter from XML to Java it would likely produce the above gibberish. The reason for this is that Java's accepted syntax tree grammar is fixed by the language specification - we have no way of modifying it. We can add packages, classes, methods, but we cannot extend Java to make addition of new operators possible. Yet we can do it to our heart's content in XML - its syntax tree isn't restricted by anything except our interpreter! If the idea is still unclear, consider introducing a special operator 'unless' to Java:</p>
<pre><code class="language-java">unless(someObject.canFly())
{
someObject.transportByGround();
}
</code></pre>
<p>In the previous two examples we extend the Java language to introduce an operator for copying files and a conditional operator unless. We would do this by modifying the abstract syntax tree grammar that Java compiler accepts. Naturally we cannot do it with standard Java facilities, but we can easily do it in XML. Because our XML interpreter parses the abstract syntax tree that results from it, we can extend it to include any operator we like.</p>
<p>For complex operators this ability provides tremendous benefits. Can you imagine writing special operators for checking out source code, compiling files, running unit testing, sending email? Try to come up with some. If you're dealing with a specialized problem (in our case it's building projects) these operators can do wonders to decrease the amount of code you have to type and to increase clarity and code reuse. Interpreted XML makes this extremely easy to accomplish because it's a simple data file that stores hierarchical data. We do not have this option in Java because it's hierarchical structure is fixed (as you will soon find out, we do have this option in Lisp). Perhaps this is one of the reasons why Ant is so successful?</p>
<p>I urge you to take a look at recent evolution of Java and C# (especially the recently released specification for C# 3.0). The languages are being evolved by abstracting away commonly used functionality and adding it in the form of operators. New C# operators for built-in queries is one example. This is accomplished by relatively traditional means: language creators modify the accepted abstract syntax tree and add implementations of certain features. Imagine the possibilities if the programmer could modify the abstract syntax tree himself! Whole new sub-languages could be built for specialized domains (for example a language for building projects, like Ant). Can you come up with other examples? Think about these concepts for a bit, but don't worry about them too much. We'll come back to these issues after introducing a few more ideas. By then things will be a little more clear.</p>
<h1 id="almost-lisp">Almost Lisp</h1>
<p>Let's forget about the operator business for the moment and try to expand our horizons beyond the constraints of Ant's design. I mentioned earlier that Ant can be extended by writing conventional Java classes. Ant interpreter then attempts to match XML elements to appropriately named Java classes and if the match is found the task is executed. An interesting question begs to be asked. Why not extend Ant in Ant itself? After all, core tasks contain a lot of conventional programming language constructs ('if' being a perfect example). If Ant provided constructs to develop tasks in Ant itself we'd reach a higher degree of portability. We'd be dependent on a core set of tasks (a standard library, if you will) and we wouldn't care if Java runtime is present: the core set could be implemented in anything. The rest of the tasks would be built on top of the core using Ant-XML itself. Ant would then become a generic, extensible, XML-based programming language. Consider the possibilities:</p>
<pre><code class="language-xml"><task name="Test">
<echo message="Hello World!"/>
</task>
<Test />
</code></pre>
<p>If ant supported the "task" construct, the example above would print "Hello World!". In fact, we could write a "task" task in Java and make Ant able to extend itself using Ant-XML! Ant would then be able to build more complicated primitives on top of simple ones, just like any other programming language! This is an example of "XML" based programming language we were talking about in the beginning of this tutorial. Not very useful (can you tell why?) but pretty damn cool.</p>
<p>By the way, take a look at our 'Test' task once again. Congratulations. You're looking at Lisp code. What on Earth am I talking about? It doesn't look anything like Lisp? Don't worry, we'll fix that in a bit. Confused? Good. Let's clear it all up!</p>
<h1 id="a-better-xml">A Better XML</h1>
<p>I mentioned in the previous section that self-extending Ant wouldn't be very useful. The reason for that is XML's verbosity. It's not too bad for data files but the moment you try writing reasonably complex code the amount of typing you have to do quickly starts to get in the way and progresses to becoming unusable for any real project. Have you ever tried writing Ant build scripts? I have, and once they get complex enough having to do it in XML becomes really annoying. Imagine having to type almost everything in Java twice because you have to close every element. Wouldn't that drive you nuts?</p>
<p>The solution to this problem involves using a less verbose alternative to XML. Remember, XML is just a format for representing hierarchical data. We don't have to use XML's angle brackets to serialize trees. We could come up with many other formats. One such format (incidentally, the one Lisp uses) is called an s-expression. S-expressions accomplish the same goals as XML. They're just a lot less verbose, which makes them much better suited for typing code. I will explain s-expressions in a little while, but before I do I have to clear up a few things about XML. Let's consider our XML example for copying files:</p>
<pre><code class="language-xml"><copy todir="../new/dir">
<fileset dir="src_dir"/>
</copy>
</code></pre>
<p>Think of what the parse tree of this snippet would look like in memory. We'd have a 'copy' node that contains a fileset node. But what about attributes? How do they fit into our picture? If you've ever used XML to describe data and wondered whether you should use an element or an attribute, you're not alone. Nobody can really figure this out and doing it right tends to be black magic rather than science. The reason for that is that attributes are really subsets of elements. Anything attributes can do, elements can do as well. The reason attributes were introduced is to curb XML's verbosity. Take a look at another version of our 'copy' snippet:</p>
<pre><code class="language-xml"><copy>
<todir>../new/dir</todir>
<fileset>
<dir>src_dir</dir>
</fileset>
</copy>
</code></pre>
<p>The two snippets hold exactly the same information. However, we use attributes to avoid typing the same thing more than once. Imagine if attributes weren't part of XML specification. Writing anything in XML would drive us nuts!</p>
<p>Now that we got attributes out of the way, let's look at s-expressions. The reason we took this detour is that s-expressions do not have attributes. Because they're a lot less verbose, attributes are simply unnecessary. This is one thing we need to keep in mind when transforming XML to s-expressions. Let's take a look at an example. We could translate above snippet to s-expressions like this:</p>
<pre><code class="language-lisp">(copy
(todir "../new/dir")
(fileset (dir "src_dir")))
</code></pre>
<p>Take a good look at this representation. What's different? Angle brackets seem to be replaced by parentheses. Instead of enclosing each element into a pair of parentheses and then closing each element with a "(/element)" we simply skip the second parenthesis in "(element" and proceed. The element is then closed like this: ")". That's it! The translation is natural and very simple. It's also a lot easier to type. Do parentheses blind first time users? Maybe, but now that we're understand the reasoning behind them they're a lot easier to handle. At the very least they're better than arthritis inducing verbosity of XML. After you get used to s-expressions writing code in them is not only doable but very pleasant. And they provide all the benefits of writing code in XML (many of which we're yet to explore). Let's take a look at our 'task' code in something that looks a lot more like lisp:</p>
<pre><code class="language-lisp">(task (name "Test")
(echo (message "Hello World!")))
(Test)
</code></pre>
<p>S-expressions are called lists in Lisp lingo. Consider our 'task' element above. If we rewrite it without a line break and with comas instead of spaces it's starting to look surprisingly like a list of elements and other lists (the formatting is added to make it easier to see nested lists):</p>
<pre><code class="language-lisp">(task, (name, "test"), (echo, (message, "Hello World!")))
</code></pre>
<p>We could do the same with XML. Of course the line above isn't really a list, it's a tree, just like its XML-alternative. Don't let references to lists confuse you, it's just that lists that contain other lists and trees are effectively the same thing. Lisp may stand for List Processing, but it's really tree processing - no different than processing XML nodes.</p>
<p>Whew. After much rambling we finally got to something that looks like Lisp (and is Lisp, really). By now the mysterious Lisp parentheses as well as some claims made by Lisp advocates should become more clear. But we still have a lot of ground to cover. Ready? Let's move on!</p>
<h1 id="c-macros-reloaded">C Macros Reloaded</h1>
<p>By now you must be tired of all the XML talk. I'm tired of it as well. It's time to take a break from all the trees, s-expressions, and Ant business. Instead, let's go back to every programmer's roots. It's time to talk about C preprocessor. What's C got to do with anything, I hear you ask? Well, we now know enough to get into metaprogramming and discuss code that writes other code. Understanding this tends to be hard since all tutorials discuss it in terms of languages that you don't know. But there is nothing hard about the concept. I believe that a metaprogramming discussion based on C will make the whole thing much easier to understand. So, let's see (pun intended).</p>
<p>Why would anyone want to write a program that writes programs? How can we use something like this in the real world? What on Earth is metaprogramming, anyway? You already know all the answers, you just don't know it yet. In order to unlock the hidden vault of divine knowledge let's consider a rather mundane task of simple database access from code. We've all been there. Writing SQL queries all over the code to modify data within tables turns into repetitive hell soon enough. Even with the new C# 3.0 LINQ stuff this is a huge pain. Writing a full SQL query (albeit with a nice built in syntax) to get someone's name or to modify someone's address isn't exactly a programmer's idea of comfort. What do we do to solve these problems? Enter data access layers.</p>
<p>The idea is simple enough. You abstract database access (at least trivial queries, anyway) by creating a set of classes that mirror the tables in the database and use accessor methods to execute actual queries. This simplifies development tremendously - instead of writing SQL queries we make simple method calls (or property assignments, depending on your language of choice). Anyone who has ever used even the simplest of data access layers knows how much time it can save. Of course anyone who has ever written one knows how much time it can kill - writing a set of classes that mirror tables and convert accessors to SQL queries takes a considerable chunk of time. This seems especially silly since most of the work is manual: once you figure out the design and develop a template for your typical data access class you don't need to do any thinking. You just write code based on the same template over and over and over and over again. Many people figured out that there is a better way - there are plenty of tools that connect to the database, grab the schema, and write code for you based on a predefined (or a custom) template.</p>
<p>Anyone who has ever used such a tool knows what an amazing time saver it can be. In a few clicks you connect the tool to the database, get it to generate the data access layer source code, add the files to your project and voilà - ten minutes worth of work do a better job than hundreds of man-hours that were required previously. What happens if your database schema changes? Well, you just have to go through this short process again. Of course some of the best tools let you automate this - you simply add them as a part of your build step and every time you compile your project everything is done for you automatically. This is perfect! You barely have to do anything at all. If the schema ever changes your data access layer code updates automatically at compile time and any obsolete access in your code will result in compiler errors!</p>
<p>Data access layers are one good example, but there are plenty of others. From boilerplate GUI code, to web code, to COM and CORBA stubs, to MFC and ATL, - there are plenty of examples where the same code is written over and over again. Since writing this code is a task that can be automated completely and a programmer's time is far more expensive than CPU time, plenty of tools have been created that generate this boilerplate code automatically. What are these tools, exactly? Well, they are programs that write programs. They perform a simple task that has a mysterious name of metaprogramming. That's all there is to it.</p>
<p>We could create and use such tools in millions of scenarios but more often than not we don't. What it boils down to is a subconscious calculation - is it worth it for me to create a separate project, write a whole tool to generate something, and then use it, if I only have to write these very similar pieces about seven times? Of course not. Data access layers and COM stubs are written hundreds, thousands of times. This is why there are tools for them. For similar pieces of code that repeat only a few times, or even a few dozen times, writing code generation tools isn't even considered. The trouble to create such a tool more often than not far outweighs the benefit of using one. If only creating such tools was much easier, we could use them more often, and perhaps save many hours of our time. Let's see if we can accomplish this in a reasonable manner.</p>
<p>Surprisingly C preprocessor comes to the rescue. We've all used it in C and C++. On occasion we all wish Java had it. We use it to execute simple instructions at compile time to make small changes to our code (like selectively removing debug statements). Let's look at a quick example:</p>
<pre><code class="language-cpp">#define triple(X) X + X + X
</code></pre>
<p>What does this line do? It's a simple instruction written in the preprocessor language that instructs it to replace all instances of <code>triple(X)</code> with <code>X + X + X</code>. For example all instances of '<code>triple(5)</code>' will be replaced with '<code>5 + 5 + 5</code>' and the resulting code will be compiled by the C compiler. We're really doing a very primitive version of code generation here. If only C preprocessor was a little more powerful and included ways to connect to the database and a few more simple constructs, we could use it to develop our data access layer right there, from within our program! Consider the following example that uses an imaginary extension of the C preprocessor:</p>
<pre><code>#get-db-schema("127.0.0.1, un, pwd");
#iterate-through-tables
#for-each-table
class #table-name
{
};
#end-for-each
</code></pre>
<p>We've just connected to the database schema, iterated through all the tables, and created an empty class for each. All in a couple of lines right within our source code! Now every time we recompile the file where above code appears we'll get a freshly built set of classes that automatically update based on the schema. With a little imagination you can see how we could build a full data access layer straight from within our program, without the use of any external tools! Of course this has a certain disadvantage (aside from the fact that such an advanced version of C preprocessor doesn't exist) - we'd have to learn a whole new "compile-time language" to do this sort of work. For complex code generation this language would have to be very complex as well, it would have to support many libraries and language constructs. For example, if our generated code depended on some file located at some ftp server the preprocessor would have to be able to connect to ftp. It's a shame to create and learn a new language just to do this. Especially since there are so many nice languages already out there. Of course if we add a little creativity we can easily avoid this pitfall.</p>
<p>Why not replace the preprocessor language with C/C++ itself? We'd have full power of the language at compile time and we'd only need to learn a few simple directives to differentiate between compile time and runtime code!</p>
<pre><code class="language-cpp"><%
cout << "Enter a number: ";
cin >> n;
%>
for(int i = 0; i < <%= n %>; i++)
{
cout << "hello" << endl;
}
</code></pre>
<p>Can you see what happens here? Everything that's between <% and %> tags runs when the program is compiled. Anything outside of these tags is normal code. In the example above you'd start compiling your program in the development environment. The code between the tags would be compiled and then ran. You'd get a prompt to enter a number. You'd enter one and it would be placed inside the for loop. The for loop would then be compiled as usual and you'd be able to execute it. For example, if you'd enter 5 during the compilation of your program, the resulting code would look like this:</p>
<pre><code class="language-cpp">for(int i = 0; i < 5; i++)
{
cout << "hello" << endl;
}
</code></pre>
<p>Simple and effective. No need for a special preprocessor language. We get full power of our host language (in this case C/C++) at compile time. We could easily connect to a database and generate our data access layer source code at compile time in the same way JSP or ASP generate HTML! Creating such tools would also be tremendously quick and simple. We'd never have to create new projects with specialized GUIs. We could inline our tools right into our programs. We wouldn't have to worry about whether writing such tools is worth it because writing them would be so fast - we could save tremendous amounts of time by creating simple bits of code that do mundane code generation for us!</p>
<h1 id="hello-lisp">Hello, Lisp!</h1>
<p>Everything we've learned about Lisp so far can be summarized by a single statement: Lisp is executable XML with a friendlier syntax. We haven't said a single word about how Lisp actually operates. It's time to fill this [gap][2].</p>
<p>Lisp has a number of built in data types. Integers and strings, for example, aren't much different from what you're used to. The meaning of <em>71</em> or "<em>hello</em>" is roughly the same in Lisp as in C++ or Java. What is of more interest to us are <em>symbols</em>, <em>lists</em>, and <em>functions</em>. I will spend the rest of this section describing these data types as well as how a Lisp environment compiles and executes the source code you type into it (this is called <em>evaluation</em> in Lisp lingo). Getting through this section in one piece is important for understanding true potential of Lisp's metaprogramming, the unity of code and data, and the notion of domain specific languages. Don't think of this section as a chore though, I'll try to make it fun and accessible. Hopefully you can pick up a few interesting ideas on the way. Ok. Let's start with Lisp's symbols.</p>
<p>A symbol in Lisp is roughly equivalent to C++ or Java's notion of an identifier. It's a name you can use to access a variable (like <em>currentTime</em>, <em>arrayCount</em>, <em>n</em>, etc.) The difference is that a symbol in Lisp is a lot more liberal than its mainstream identifier alternative. In C++ or Java you're limited to alphanumeric characters and an underscore. In Lisp, you are not. For example <code>+</code> is a valid symbol. So is <code>-</code>, <code>=</code>, <code>hello-world</code>, <code>hello+world</code>, <code>*</code>, etc. (you can find the exact definition of valid Lisp symbols online). You can assign to these symbols any data-type you like. Let's ignore Lisp syntax and use pseudo-code for now. Assume that a function <code>set</code> assigns some value to a symbol (like <code>=</code> does in Java or C++). The following are all valid examples:</p>
<pre><code class="language-cpp">set(test, 5) // symbol 'test' will equal an integer 5
set(=, 5) // symbol '=' will equal an integer 5
set(test, "hello") // symbol 'test' will equal a string "hello"
set(test, =) // at this point symbol '=' is equal to 5
// therefore symbol 'test' will equal to 5
set(*, "hello") // symbol '*' will equal a string "hello"
</code></pre>
<p>At this point something must smell wrong. If we can assign strings and integers to symbols like <code>*</code>, how does Lisp do multiplication? After all, <code>*</code> means multiply, right? The answer is pretty simple. Functions in Lisp aren't special. There is a data-type, function, just like integer and string, that you assign to symbols. A multiplication function is built into Lisp and is assigned to a symbol <code>*</code>. You can reassign a different value to <code>*</code> and you'd lose the multiplication function. Or you can store the value of the function in some other variable. Again, using pseudo-code:</p>
<pre><code class="language-cpp">*(3, 4) // multiplies 3 by 4, resulting in 12
set(temp, *) // symbol '*' is equal to the multiply function
// so temp will equal to the multiply function
set(*, 3) // sets symbol '*' to equal to 3
*(3, 4) // error, symbol '*' no longer equals to a function
// it's equal to 3
temp(3, 4) // temp equals to a multiply function
// so Lisp multiplies 3 by 4 resulting in 12
set(*, temp) // symbol '*' equals multiply function again
*(3, 4) // multiplies 3 by 4, resulting in 12
</code></pre>
<p>You can even do wacky stuff like reassigning plus to minus:</p>
<pre><code class="language-cpp">set(+, -) // the value of '-' is a built in minus function
// so now symbol '+' equals to a minus function
+(5, 4) // since symbol '+' is equal to the minus function
// this results in 1
</code></pre>
<p>I've used functions quite liberally in these examples but I didn't describe them yet. A function in Lisp is just a data-type like an integer, a string, or a symbol. A function doesn't have a notion of a name like in Java or C++. Instead, it stands on its own. Effectively it is a pointer to a block of code along with some information (like a number of parameters it accepts). You only give the function a name by assigning it to a symbol, just like you assign an integer or a string. You can create a function by using a built in function for creating functions, assigned to a symbol 'fn'. Using pseudo-code:</p>
<pre><code class="language-cpp">fn [a]
{
return *(a, 2);
}
</code></pre>
<p>This returns a function that takes a single parameter named 'a' and doubles it. Note that the function has no name but you can assign it to a symbol:</p>
<pre><code class="language-lisp">set(times-two, fn [a] { return *(a, 2); })
</code></pre>
<p>We can now call this function:</p>
<pre><code class="language-cpp">times-two(5) // returns 10
</code></pre>
<p>Now that we went over symbols and functions, what about lists? Well, you already know a lot about them. Lists are simply pieces of XML written in s-expression form. A list is specified by parentheses and contains Lisp data-types (including other lists) separated by a space. For example (this is real Lisp, note that we use semicolons for comments now):</p>
<pre><code class="language-lisp">() ; an empty list
(1) ; a list with a single element, 1
(1 "test") ; a list with two elements
; an integer 1 and a string "test"
(test "hello") ; a list with two elements
; a symbol test and a string "hello"
(test (1 2) "hello") ; a list with three elements, a symbol test
; a list of two integers 1 and 2
; and a string "hello"
</code></pre>
<p>When a Lisp system encounters lists in the source code it acts exactly like Ant does when it encounters XML - it attempts to execute them. In fact, Lisp source code is only specified using lists, just like Ant source code is only specified using XML. Lisp executes lists in the following manner. The first element of the list is treated as the name of a function. The rest of the elements are treated as functions parameters. If one of the parameters is another list it is executed using the same principles and the result is passed as a parameter to the original function. That's it. We can write real code now:</p>
<pre><code class="language-lisp">(* 3 4) ; equivalent to pseudo-code *(3, 4).
; Symbol '*' is a function
; 3 and 4 are its parameters.
; Returns 12.
(times-two 5) ; returns 10
(3 4) ; error: 3 is not a function
(times-two) ; error, times-two expects one parameter
(times-two 3 4) ; error, times-two expects one parameter
(set + -) ; sets symbol '+' to be equal to whatever symbol '-'
; equals to, which is a minus function
(+ 5 4) ; returns 1 since symbol '+' is now equal
; to the minus function
(* 3 (* 2 2)) ; multiplies 3 by the second parameter
; (which is a function call that returns 4).
; Returns 12.
</code></pre>
<p>Note that so far every list we've specified was treated by a Lisp system as code. But how can we treat a list as data? Again, imagine an Ant task that accepts XML as one of its parameters. In Lisp we do this using a quote operator ' like so:</p>
<pre><code class="language-lisp">(set test '(1 2)) ; test is equal to a list of two integers, 1 and 2
(set test (1 2)) ; error, 1 is not a function
(set test '(* 3 4)) ; sets test to a list of three elements,
; a symbol *, an integer 3, and an integer 4
</code></pre>
<p>We can use a built in function head to return the first element of the list, and a built in function tail to return the rest of the list's elements:</p>
<pre><code class="language-lisp">(head '(* 3 4)) ; returns a symbol '*'
(tail '(* 3 4)) ; returns a list (3 4)
(head (tail '( * 3 4))) ; (tail '(* 3 4)) returns a list (3 4)
; and (head '(3 4)) returns 3.
(head test) ; test was set to a list in previous example
; returns a symbol '*'
</code></pre>
<p>You can think of built in Lisp functions as you think of Ant tasks. The difference is that we don't have to extend Lisp in another language (although we can), we can extend it in Lisp itself as we did with the times-two example. Lisp comes with a very compact set of built in functions - the necessary minimum. The rest of the language is implemented as a standard library in Lisp itself.</p>
<h1 id="lisp-macros">Lisp Macros</h1>
<p>So far we've looked at metaprogramming in terms of a simple templating engine similar to JSP. We've done code generation using simple string manipulations. This is generally how most code generation tools go about doing this task. But we can do much better. To get on the right track, let's start off with a question. How would we write a tool that automatically generates Ant build scripts by looking at source files in the directory structure?</p>
<p>We could take the easy way out and generate Ant XML by manipulating strings. Of course a much more abstract, expressive and extensible way is to work with XML processing libraries to generate XML nodes directly in memory. The nodes can then be serialized to strings automatically. Furthermore, our tool would be able to analyze and transform existing Ant build scripts by loading them and dealing with the XML nodes directly. We would abstract ourselves from strings and deal with higher level concepts which let us get the job done faster and easier.</p>
<p>Of course we could write Ant tasks that allow dealing with XML transformations and write our generation tool in Ant itself. Or we could just use Lisp. As we saw earlier, a list is a built in Lisp data structure and Lisp has a number of facilities for processing lists quickly and effectively (head and tail being the simplest ones). Additionally Lisp has no semantic constraints - you can have your code (and data) have any structure you want.</p>
<p>Metaprogramming in Lisp is done using a construct called a "macro". Let's try to develop a set of macros that transform data like, say, a to-do list (surprised?), into a language for dealing with to-do lists.</p>
<p>Let's recall our to-do list example. The XML looks like this:</p>
<pre><code class="language-xml"><todo name="housework">
<item priority="high">Clean the house.</item>
<item priority="medium">Wash the dishes.</item>
<item priority="medium">Buy more soap.</item>
</todo>
</code></pre>
<p>The corresponding s-expression version looks like this:</p>
<pre><code class="language-lisp">(todo "housework"
(item (priority high) "Clean the house.")
(item (priority medium) "Wash the dishes.")
(item (priority medium) "Buy more soap."))
</code></pre>
<p>Suppose we're writing a to-do manager application. We keep our to-do items serialized in a set of files and when the program starts up we want to read them and display them to the user. How would we do this with XML and some other language (say, Java)? We'd parse our XML files with the to-do lists using some XML parser, write the code that walks the XML tree and converts it to a Java data structure (because frankly, processing DOM in Java is a pain in the neck), and then use this data structure to display the data. Now, how would we do the same thing in Lisp?</p>
<p>If we were to adopt the same approach we'd parse the files using Lisp libraries responsible for parsing XML. The XML would then be presented to us as a Lisp list (an s-expression) and we'd walk the list and present relevant data to the user. Of course if we used Lisp it would make sense to persist the data as s-expressions directly as there's no reason to do an XML conversion. We wouldn't need special parsing libraries since data persisted as a set of s-expressions is valid Lisp and we could use Lisp compiler to parse it and store it in memory as a Lisp list. Note that Lisp compiler (much like .NET compiler) is available to a Lisp program at runtime.</p>
<p>But we can do better. Instead of writing code to walk the s-expression that stores our data we could write a macro that allows us to treat data as code! How do macros work? Pretty simple, really. Recall that a Lisp function is called like this:</p>
<pre><code class="language-lisp">(function-name arg1 arg2 arg3)
</code></pre>
<p>Where each argument is a valid Lisp expression that's evaluated and passed to the function. For example if we replace <em>arg1</em> above with <code>(+ 4 5)</code>, it will be evaluated and <code>9</code> would be passed to the function. A macro works the same way as a function, except its arguments are not evaluated.</p>
<pre><code class="language-lisp">(macro-name (+ 4 5))
</code></pre>
<p>In this case, <code>(+ 4 5)</code> is not evaluated and is passed to the macro as a list. The macro is then free to do what it likes with it, including evaluating it. The return value of a macro is a Lisp list that's treated as code. The original place with the macro is replaced with this code. For example, we could define a macro plus that takes two arguments and puts in the code that adds them.</p>
<p>What does it have to do with metaprogramming and our to-do list problem? Well, for one, macros are little bits of code that generate code using a list abstraction. Also, we could create macros named <em>to-do</em> and <em>item</em> that replace our data with whatever code we like, for instance code that displays the item to the user.</p>
<p>What benefits does this approach offer? We don't have to walk the list. The compiler will do it for us and will invoke appropriate macros. All we need to do is create the macros that convert our data to appropriate code!</p>
<p>For example, a macro similar to our <em>triple C</em> macro we showed earlier looks like this:</p>
<pre><code class="language-lisp">(defmacro triple (x)
'(+ ~x ~x ~x))
</code></pre>
<p>The quote prevents evaluation while the tilde allows it. Now every time triple is encountered in lisp code:</p>
<pre><code class="language-lisp">(triple 4)
</code></pre>
<p>it is replaced with the following code:</p>
<pre><code class="language-lisp">(+ 4 4 4)
</code></pre>
<p>We can create macros for our to-do list items that will get called by lisp compiler and will transform the to-do list into code. Now our to-do list will be treated as code and will be executed. Suppose all we want to do is print it to standard output for the user to read:</p>
<pre><code class="language-lisp">(defmacro item (priority note)
'(block
(print stdout tab "Priority: "
~(head (tail priority)) endl)
(print stdout tab "Note: " ~note endl endl)))
</code></pre>
<p>We've just created a very small and limited language for managing to-do lists embedded in Lisp. Such languages are very specific to a particular problem domain and are often referred to as domain specific languages or <em>DSLs</em>.</p>
<h1 id="domain-specific-languages">Domain Specific Languages</h1>
<p>In this article we've already encountered two domain specific languages: Ant (specific to dealing with project builds) and our unnamed mini-language for dealing with to-do lists. The difference is that Ant was written from scratch using XML, an XML parser, and Java while our language is embedded into Lisp and is easily created within a couple of minutes.</p>
<p>We've already discussed the benefits of DSLs, mainly why Ant is using XML, not Java source code. Lisp lets us create as many DSLs as we need for our problem. We can create domain specific languages for creating web applications, writing massively multiplayer games, doing fixed income trading, solving the protein folding problem, dealing with transactions, etc. We can layer these languages on top of each other and create a language for writing web-based trading applications by taking advantage of our web application language and bond trading language. Every day we'd reap the benefits of this approach, much like we reap the benefits of Ant.</p>
<p>Using DSLs to solve problems results in much more compact, maintainable, flexible programs. In a way we create them in Java by creating classes that help us solve the problem. The difference is that Lisp allows us to take this abstraction to the next level: we're not limited by Java's parser. Think of writing build scripts in Java itself using some supporting library. Compare it to using Ant. Now apply this same comparison to every single problem you've ever worked on and you'll begin to glimpse a small share of the benefits offered by Lisp.</p>
<h1 id="whats-next">What's next?</h1>
<p>Learning Lisp is an uphill battle. Even though in Computer Science terms Lisp is an ancient language, few people to date figured out how to teach it well enough to make it accessible. Despite great efforts by many Lisp advocates, learning Lisp today is still hard. The good news is that this won't remain the case forever since the amount of Lisp-related resources is rapidly increasing. Time is on Lisp's side.</p>
<p>Lisp is a way to escape mediocrity and to get ahead of the pack. Learning Lisp means you can get a better job today, because you can impress any reasonably intelligent interviewer with fresh insight into most aspects of software engineering. It also means you're likely to get fired tomorrow because everyone is tired of you constantly mentioning how much better the company could be doing if only its software was written in Lisp. Is it worth the effort? Everyone who has ever learned Lisp says yes. The choice, of course, remains yours.</p>
<h1 id="comments">Comments?</h1>
<p>Whew. That's enough. I've been writing this article, on and off, for months. If you find it interesting, have any questions, comments, or suggestions, please drop a note at <a href="mailto:coffeemug@gmail.com">coffeemug@gmail.com</a>. I'll be glad to hear your feedback.</p>
<p>[1]: I have never met James, nor does he know about my existence. The story is entirely fictional and is based on a few postings about Ant's history I found on the internet.</p>
<p>[2]: Lisp has many different dialects (the most popular of which are Common Lisp and Scheme). Each dialect deals with intricate details differently yet shares the same set of basic principles. Since the goal of this article is to give you an understanding of Lisp's principles I will use Blaise for examples (which at the time of this writing is vaporware). With some minor modifications these examples can be translated to other Lisp dialects.</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Amazon Web Services]]></title>
<id>https://faded.auspicious.space/post/amazon-web-services/</id>
<link href="https://faded.auspicious.space/post/amazon-web-services/">
</link>
<updated>2020-05-31T12:57:27.000Z</updated>
<summary type="html"><![CDATA[<blockquote>
<p><a href="https://adayinthelifeof.nl/2020/05/20/aws.html">Amazon Web Services</a></p>
</blockquote>
]]></summary>
<content type="html"><![CDATA[<blockquote>
<p><a href="https://adayinthelifeof.nl/2020/05/20/aws.html">Amazon Web Services</a></p>
</blockquote>
<!-- more -->
<p>More often than not, I’m using Amazon Web Services (AWS) as my “cloud”. Not only for my own projects, but almost all customers I’m working for use Amazon for hosting their applications. So over time you build up a lot of experience on AWS service: you know how to (correctly) setup VPC’s, know when to you ECS, EC2 or lambda to host code and even services like S3, SNS and SQS pose no challenges anymore.</p>
<p>But there are a lot of AWS services available. And I do mean: a LOT. Currently, there are 163 (!) different services that are available from the Amazon Dashboard, each with their own way of working, difficulties, catches and best practises.</p>
<h1 id="discovering-aws">Discovering AWS</h1>
<p>You might realise that it’s probably near impossible to dive into each service and completely understand how they work and most likely, you don’t really need to know the exact ins and outs. But, having a basic understanding on each service can be a major benefit as a developer, architect or administrator. It makes it easier to see if there is an already existing solution for your problem at hand.</p>
<p>So, I dove into each and every service to figure out what it exactly was for and how it works in the basics. I tried to experiment with as many components as possible (time and money permitting, I didn’t want to spend 15.000$ on AWS Data Exchange). I tried to capture what the service does in a single one-liner to give you a global overview.</p>
<p>I think most of them are correct enough but if you have any suggestions or corrections, please tell me!</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[软件设计,那些你不知道的事]]></title>
<id>https://faded.auspicious.space/post/software-design-things-you-dont-know/</id>
<link href="https://faded.auspicious.space/post/software-design-things-you-dont-know/">
</link>
<updated>2020-05-28T02:58:34.000Z</updated>
<summary type="html"><![CDATA[<blockquote>
<p><a href="https://juejin.im/post/5bd7f3fa5188251135281da2">软件设计,那些你不知道的事</a></p>
</blockquote>
]]></summary>
<content type="html"><![CDATA[<blockquote>
<p><a href="https://juejin.im/post/5bd7f3fa5188251135281da2">软件设计,那些你不知道的事</a></p>
</blockquote>
<!-- more -->
<p>代码质量和产出是衡量一个程序员是否优秀最直接的标准。如何提高代码质量和产出?这就要从软件重构和 review 入手。市面上有很多关于重构和 review 的书籍,但是看完之后,代码能力并不能立竿见影显著提升,只能帮助我们解决表面的 bug 和规范点,无法帮助我们发现更深层次的设计问题。</p>
<p>从设计角度来考虑 review,识别代码坏味道可以可以有效减少技术债务。技术债务是指有意或无意的做出错误的或非最优的设计决策所引发的债务。债务越积越多,最后只能重新彻底重构项目才能解决问题,这也叫做技术破产。如何解决技术债务问题,就要从根源上明确引起技术债务的重要的原因——设计坏味和重构认识不足。</p>
<h1 id="首先要明确软件设计原则">首先要明确软件设计原则</h1>
<ul>
<li><strong>抽象原则:</strong> 通过精简和概括来简化实体:精简指的是删除不必要的细节,概括是找出并定义重要的通用特征。</li>
<li><strong>非循环依赖原则:</strong> 包之间的关系不可形成循环。</li>
<li><strong>不自我重复原则:</strong> 在详细设计中,设计实体和代码和重复可能表现为类型名重复和实现重复。</li>
<li><strong>封装原则:</strong> 通过隐藏抽象的实现细节和隐藏变化等方法实现关注点分离和信息隐藏。</li>
<li><strong>信息隐藏原则:</strong> 找出棘手或可能变化的设计决策,并创建合适的模块或类型来对其他模块或类型隐藏这些决策。</li>
<li><strong>保持简单原则:</strong> 简洁是软件系统设计的重要目标,应避免引入不必要的复杂性。</li>
<li><strong>里氏替换原则:</strong> 所有的子类型都必须至少提供超类型承诺的行为且对每个超类型的引用都可替换成子类型实例。</li>
<li><strong>层次接口原则:</strong> 使用分类、概括、替换、排序等方法以层次方式组织对抽象。</li>
<li><strong>模块化原则:</strong> 通过集中和分解等手法创建高内聚、低耦合的抽象。</li>
<li><strong>开闭原则:</strong> 类型应对扩展开放,对修改关闭。具体是模块应该能够在不修改代码情况下支持新需求。</li>
<li><strong>单一职责原则:</strong> 绝不应有多个导致类需要修改的原因,如修改一个成员可能影响类的其他不相关职责,导致类难以维护。</li>
<li><strong>变化封装原则:</strong> 倡导一种信息隐藏方式,建议将可能发生变化的概念封装起来。很多设计模式都体现了这种设计原则,如策略模式、桥梁模式、观察者模式。</li>
</ul>
<h1 id="我们从设计的角度来看代码时要遵循六要素">我们从设计的角度来看代码时,要遵循六要素</h1>
<ul>
<li><strong>可理解性:</strong> 代码理解起来的难易程度。</li>
<li><strong>可修改性:</strong> 在修改既有功能时,不会导致连锁反应。</li>
<li><strong>可扩展性:</strong> 支持新功能,不会导致连锁反应。</li>
<li><strong>可重用性:</strong> 可以在代码的其他地方引用其一块代码。</li>
<li><strong>可测试性:</strong> 项目要能够支持单元测试。</li>
<li><strong>可靠性:</strong> 在正确地实现了功能的同时,也能够考虑各种异常情况如何容错。</li>
</ul>
<h1 id="了解完设计原则和六要素后我们再来看设计坏味">了解完设计原则和六要素后,我们再来看设计坏味</h1>
<ul>
<li>抽象型</li>
<li>封装型</li>
<li>层次结构</li>
<li>模块化型</li>
</ul>
<p>本文中每种坏味我们只选其中一例做具体说明。</p>
<h2 id="抽象型坏味">抽象型坏味</h2>
<p>抽象原则倡导通过精简和概括来简化实体:精简指的是删除不必要的细节,而概括指的是找出并定义通过的重要特征。交通标志是用于交流的抽象示例,而数字符号和编程语言是用于解决问题的抽象示例。</p>
<ul>
<li>缺失抽象</li>
<li>命令式抽象</li>
<li>多方面的抽象</li>
<li>不必要的抽象</li>
<li>未用的抽象</li>
<li>重复的抽象</li>
</ul>
<h3 id="缺失抽象">缺失抽象</h3>
<p>使用一系列数据或者编码字符串,而不创建类或者接口时会产生这种坏味。</p>
<h4 id="概念">概念</h4>
<p>应用抽象原则的一种实现手法是创建概念边界清晰,身份唯一的实体。由于没有创建抽象来表示实体,而是使用基本数据类型或编码字符串等原始数据来表示它,这违反了抽象原则,将这种坏味称为缺失抽象(Missing Abstraction)。不必要的抽象也违反了模块化原则。</p>
<h4 id="潜在原因">潜在原因</h4>
<ul>
<li>未做重复的设计分析</li>
<li>未重构</li>
<li>错误的将重点放在细微的性能改善上</li>
</ul>
<h4 id="示例">示例</h4>
<p>在 JDK1.0 中方法 <code>printStackTrace()</code> 以字符串的方式将栈跟踪打印到标准错误流。</p>
<p>在需要以编程方式访问栈跟踪元素的客户程序中,必须要编程代码来获取数据,如行号等,由于客户程度依赖这种字符串格式,JDK 设计人员只能在后续版本中兼容这种格式了。</p>
<pre><code class="language-java">public class Throwable {
public void printStackTrace() {
printStackTrace(System.err);
}
}
</code></pre>
<h4 id="重构建议">重构建议</h4>
<p>从 JDK1.4 起对 JAVA 的 API 进行了改进,<code>StackTraceElement</code> 类就是原来设计中缺失的对象。</p>
<pre><code class="language-java">public class Throwable {
public void printStackTrace() {
printStackTrace(System.err);
}
public StackTraceElement[] getStackTrace() {
return getOurStackTrace().clone();
}
}
/**
* @since 1.4
* @author Josh Bloch
*/
public final class StackTraceElement implements java.io.Serializable {
// Normally initialized by VM (public constructor added in 1.5)
private String declaringClass;
private String methodName;
private String fileName;
private int lineNumber;
}
</code></pre>
<ul>
<li>别名
<ol>
<li>基本类型偏执:使用基本类型对日期、金额进行编码,而不创建类时,将引发这种坏味。</li>
<li>数据泥团:在很多地方同事使用一系列数据项,而不创建类时,将引发这种坏味。</li>
</ol>
</li>
<li>现实考虑
<ol>
<li>避免过度设计:有时候,实体只是数据元素,没有任何相关联的行为。这种情况下使用类或者接口来表示它们可能导致过度设计。</li>
</ol>
</li>
</ul>
<h2 id="封装型坏味">封装型坏味</h2>
<p>封装原则倡导通过隐藏抽象的实现细节和隐藏变化等手法实现关注的分离和信息隐藏。比如开车必须知道发动机原理吗?</p>
<ul>
<li>不充分的封装</li>
<li>泄露的封装</li>
<li>缺失封装</li>
<li>未利用封装</li>
</ul>
<h3 id="不充分的封装">不充分的封装</h3>
<p>对于抽象的一个或多个成员,声明的访问权限超过了实际需求时,将导致这种坏味。例如,将字段声明为公有的类就存在「不充分封装」坏味。</p>
<h4 id="概念-2">概念</h4>
<p>封装的原则是将接口和实现分离,以便能独立修改。这种关注点分离,让客户程序只依赖抽象的接口,而对它们隐藏具体实现。修改实现不影响客户程序。对抽象的内部隐藏的不充分称为不充分的封装(Deficient Encapsulation)。</p>
<h4 id="潜在原因-2">潜在原因</h4>
<ul>
<li>为方便测试</li>
<li>在面向对象中采用过程思维</li>
<li>快速交付</li>
</ul>
<h4 id="示例-2">示例</h4>
<ul>
<li>来看看 <code>java.lang.System</code>,<code>in</code>、<code>out</code>、<code>err</code> 都被声明成 <code>final</code>,但可以通过<code>java.lang.System</code> 的 <code>setIn</code>、<code>setOut</code>、<code>setErr</code> 分别赋值。任何代码都能很方便的使用它们,比如 <code>System.out.println()</code>;</li>
<li><code>PrintStream</code> 是 Java 1.0 就有的,只支持 8 位的 ASCII 值,Java1.1 出的 <code>PrintWriter</code> 支持 Unicode,然而就是因为应用程序都能直接使用 <code>PrintStream</code> 来访问 <code>PrintStream</code> 的方法,根本不能摒弃 <code>PrintStream</code> 类。</li>
</ul>
<pre><code class="language-java">public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
public static void setIn(InputStream in) {
checkIO();
setIn0(in);
}
</code></pre>
<h4 id="重构建议-2">重构建议</h4>
<p>Java 1.6 引入了 <code>java.io.Console</code> 类,他提供了用于访问基于字符的控制台的方法。<code>reader()</code>、<code>writer()</code> 来获取 <code>Console</code> 相关的 <code>Writer</code> 和 <code>Reader</code> 对象。</p>
<ul>
<li>别名
<ol>
<li>可隐藏的公有属性、方法</li>
<li>未封装的类</li>
<li>包含未参数化方法的类</li>
</ol>
</li>
<li>现实考虑
<ol>
<li>嵌套或匿名类中过于宽松的访问性</li>
<li>性能考虑:比如前面说的 <code>java.lang.System</code></li>
</ol>
</li>
</ul>
<h2 id="模块化坏味">模块化坏味</h2>
<p>模块化原则倡导利用集中和分解等手法创建高内聚、低耦合的抽象。</p>
<ul>
<li>拆散的模块化</li>
<li>不充分的模块化</li>
<li>循环依赖式模块化</li>
<li>轮毂式模块化</li>
</ul>
<h3 id="拆散的模块化">拆散的模块化</h3>
<p>应集中放在一个抽象中的数据和方法分散在多个抽象中时,将导致这种坏味。表现为类被用作数据容器没有任何方法、类的方法更多的被其他类的成员调用。</p>
<h4 id="概念-3">概念</h4>
<p>一种重要的模块化实现手法是「将相关的数据和方法集中在一起」。如果抽象中只包含数据成员,而操作这些数据成员的方法位于其他抽象中,它就违反了这种实现手法,存在「拆散的模块化」坏味。称为拆散的模块化(Broken Modularization)。</p>
<h4 id="潜在原因-3">潜在原因</h4>
<ul>
<li>以过程思维使用面向对象语言</li>
<li>不熟悉既有设计</li>
</ul>
<h4 id="重构建议-3">重构建议</h4>
<p>对于包含大量数据类的过程型设计,可采用重构手法“将过程型设计转换为对象”。</p>
<ul>
<li>别名
<ol>
<li>被动地存储数据的类</li>
<li>数据类</li>
<li>数据记录</li>
<li>记录类</li>
<li>数据容器</li>
<li>错位的操作</li>
<li>依恋情结</li>
<li>错位的控制</li>
</ol>
</li>
<li>现实考虑
<ol>
<li>自动生成的代码</li>
<li>数据传输对象</li>
</ol>
</li>
</ul>
<h2 id="层次型坏味">层次型坏味</h2>
<p>层次结构原则倡导采用分类、归并、替换和排序等手法以层次方式组织抽象。比如地球上的 870 万种生物。</p>
<ul>
<li>缺失层次结构</li>
<li>不必要层次结构</li>
<li>为归并的层次结构</li>
<li>过宽的层次结构</li>
<li>凭空想象的层次结构</li>
<li>过深的层次结构</li>
<li>叛逆型层次结构</li>
<li>支离破碎的层次结构</li>
<li>多路径层次结构</li>
<li>循环层次结构</li>
</ul>
<h3 id="缺失的层次结构">缺失的层次结构</h3>
<p>代码片段使用条件逻辑来显式管理行为变化,而原本可以创建一个层次目录,并使用它来封装这些变化,会产生这种坏味。</p>
<h4 id="概念-4">概念</h4>
<ul>
<li>基于类型码的 <code>switch</code> 语句(或串接的 <code>if-else</code> 语句)是最著名的设计坏味之一。</li>
<li>使用类型码来处理行为变化表明没有进行有意义的分类,导致设计中缺少相应的层次结构。称为缺失层次结构(Missing Hierarchy)。</li>
</ul>
<h4 id="潜在原因-4">潜在原因</h4>
<ul>
<li>错误的采用过于简单的设计</li>
<li>过程型设计思维</li>
<li>忽视了继承也是一种设计手法</li>
</ul>
<h4 id="示例-3">示例</h4>
<p>串接的 <code>if else</code> 语句显示的检查类型 <code>AbstractButton</code>,<code>JToolBar</code> 和 <code>JTextCompont</code>并在各种条件下调用方法 <code>getMargin()</code>,这种造成的情况是将来可能在代码中的其他地方也会出现。</p>
<pre><code class="language-java">public Inserts getBorderInserts(Component c, Inserts inserts) {
if(c instanceof AbstractButton) {
margin = ((AbstractButton)c).getMargin();
} else if(c instanceof JToolBar) {
margin = ((JToolBar)c).getMargin();
} else if(c instanceof JTextComponent) {
margin = ((JTextComponent)c).getMargin();
}
}
</code></pre>
<h4 id="重构建议-4">重构建议</h4>
<ol>
<li>如果条件检查中的多个实现调用方法相同,可引入相关的接口来抽象共同的协议。</li>
<li>如果代码中包含可转换为类的条件语句,可采用重构手法“提取层次结构”来创建一个类层次结构,其中每个类都表示条件检查中的一种情形。</li>
</ol>
<ul>
<li>别名
<ol>
<li>标记类</li>
<li>继承缺位</li>
<li>紧缩的类型层次结构</li>
<li>内嵌功能</li>
</ol>
</li>
<li>现实考虑
<ol>
<li>与外部交互</li>
</ol>
</li>
</ul>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[你可能会错过的 CSS 伪选择器]]></title>
<id>https://faded.auspicious.space/post/css-pseudo-classes-you-might-have-missed/</id>
<link href="https://faded.auspicious.space/post/css-pseudo-classes-you-might-have-missed/">
</link>
<updated>2020-05-27T03:53:58.000Z</updated>
<summary type="html"><![CDATA[<blockquote>
<p><a href="https://juejin.im/post/5eaea84f51882558dc24bc94">[译]你可能会错过的 CSS 伪选择器</a></p>
</blockquote>
]]></summary>
<content type="html"><![CDATA[<blockquote>
<p><a href="https://juejin.im/post/5eaea84f51882558dc24bc94">[译]你可能会错过的 CSS 伪选择器</a></p>
</blockquote>
<!-- more -->
<blockquote>
<p>(伪)选择器可以为文档中不一定具体存在的结构指定样式,或者为某些元素、文档的标记模式、甚至是文档本身的状态所指示的幻像类指定样式。 —— CSS 权威指南:Eric Meyer、Estelle Weyl</p>
</blockquote>
<p>这篇文章鼓励构造 UI 时使用更多纯 CSS 和更少的 JS。熟悉所有的 CSS 是实现这个目标的一种方法 —— 另一种是实施最佳实践和尽可能的减少代码。</p>
<h1 id="first-line-选择首行文本"><code>::first-line</code> | 选择首行文本</h1>
<p>这个伪元素选择器选择换行之前文本的首行。</p>
<pre><code class="language-css">p:first-line {
color: lightcoral;
}
</code></pre>
<h1 id="first-letter-选择首字母"><code>::first-letter</code> | 选择首字母</h1>
<p>这个伪元素选择器应用于元素中文本的首字母。</p>
<pre><code class="language-css">.innerDiv p:first-letter {
color: lightcoral;
font-size: 40px
}
</code></pre>
<h1 id="selection-选择高亮被选中的区域"><code>::selection</code> | 选择高亮(被选中)的区域</h1>
<p>应用于任何被用户选中的高亮区域。</p>
<p>通过 <code>::selection</code> 伪元素选择器,我们可以将样式应用于高亮区域。</p>
<pre><code class="language-css">div::selection {
background: yellow;
}
</code></pre>
<h1 id="root-根元素"><code>:root</code> | 根元素</h1>
<p><code>:root</code> 伪类选中文档的根元素。在 HTML 中,为 HTML 元素。在 RSS 中,则为 RSS 元素.</p>
<p>这个伪类选择器应用于根元素,多用于存储全局 CSS 自定义属性。</p>
<h1 id="empty-仅当元素为空时触发"><code>:empty</code> | 仅当元素为空时触发</h1>
<p>这个伪类选择器将选中没有任何子项的元素。该元素必须为空。如果一个元素没有空格、可见的内容、后代元素,则为空元素。</p>
<pre><code class="language-css">div:empty {
border: 2px solid orange;
}
</code></pre>
<pre><code class="language-html"><div></div>
<div></div>
<div>
</div>
</code></pre>
<p>这个规则将应用于空的 <code>div</code> 元素。这个规则将应用于第一个和第二个 <code>div</code>,因为他们是真为空,而第三个 <code>div</code> 包含空格。</p>
<h1 id="only-child-选择仅有的子元素"><code>:only-child</code> | 选择仅有的子元素</h1>
<p>匹配父元素中没有任何兄弟元素的子元素。</p>
<pre><code class="language-css">.innerDiv p:only-child {
color: orangered;
}
</code></pre>
<h1 id="first-of-type-选择第一个指定类型的子元素"><code>:first-of-type</code> | 选择第一个指定类型的子元素</h1>
<pre><code class="language-css">.innerDiv p:first-of-type {
color: orangered;
}
</code></pre>
<p>这将应用于 <code>.innerDiv</code> 下的第一个 <code>p</code> 元素。</p>
<pre><code class="language-html"><div class="innerDiv">
<div>Div1</div>
<p>These are the necessary steps</p>
<p>hiya</p>
<p>
Do <em>not</em> push the brake at the same time as the accelerator.
</p>
<div>Div2</div>
</div>
</code></pre>
<p>这个 <code>p</code>(“These are the necessary step”)将被选中。</p>
<h1 id="last-of-type-选择最后一个指定类型的子元素"><code>:last-of-type</code> | 选择最后一个指定类型的子元素</h1>
<p>像 <code>:first-of-type</code> 一样,但是会应用于最后一个同类型的子元素。</p>
<pre><code class="language-css">.innerDiv p:last-of-type {
color: orangered;
}
</code></pre>
<p>这将应用于 <code>innerDiv</code> 下的最后一个 <code>p</code> 段落元素。</p>
<pre><code class="language-html"><div class="innerDiv">
<p>These are the necessary steps</p>
<p>hiya</p>
<div>Div1</div>
<p>
Do the same.
</p>
<div>Div2</div>
</div>
</code></pre>
<p>因此,这个 <code>p</code> 元素(“Do the same”)将被选中。</p>
<h1 id="nth-of-type-选择特定类型的子元素"><code>:nth-of-type()</code> | 选择特定类型的子元素</h1>
<p>这个选择器将从指定的父元素的孩子列表中选择某种类型的子元素。</p>
<pre><code class="language-css">.innerDiv p:nth-of-type(1) {
color: orangered;
}
</code></pre>
<h1 id="nth-last-of-type-选择列表末尾中指定类型的子元素"><code>:nth-last-of-type()</code> | 选择列表末尾中指定类型的子元素</h1>
<p>这将选择最后一个指定类型的子元素。</p>
<pre><code class="language-css">.innerDiv p:nth-last-of-type() {
color: orangered;
}
</code></pre>
<p>这将选择 <code>innerDiv</code> 列表元素中包含的最后一个段落类型子元素。</p>
<pre><code class="language-html"><div class="innerDiv">
<p>These are the necessary steps</p>
<p>hiya</p>
<div>Div1</div>
<p>
Do the same.
</p>
<div>Div2</div>
</div>
</code></pre>
<p><code>innerDiv</code> 中最后一个段落子元素 <code>p</code>(“Do the same”)将会被选中。</p>
<h1 id="link-选择一个未访问过的超链接"><code>:link</code> | 选择一个未访问过的超链接</h1>
<p>这个选择器应用于未被访问过的链接。常用于带有 <code>href</code> 属性的 <code>a</code> 锚元素。</p>
<pre><code class="language-css">a:link {
color: orangered;
}
</code></pre>
<pre><code class="language-html"><a href="/login">Login<a>
</code></pre>
<p>这将选中未被点击过带有 <code>href</code> 的指定界面的 <code>a</code> 锚点元素,选中的元素中的文字将会显示为橙色。</p>
<h1 id="checked-选择一个选中的复选框"><code>:checked</code> | 选择一个选中的复选框</h1>
<p>这个应用于已经被选中的复选框。</p>
<pre><code class="language-css">input:checked {
border: 2px solid lightcoral;
}
</code></pre>
<p>这个规则应用到所有被选中的复选框。</p>
<h1 id="valid-选择一个通过验证的元素"><code>:valid</code> | 选择一个通过验证的元素</h1>
<p>这主要用于可视化表单元素,以让用户判断是否验证通过。验证通过时,默认元素带有 <code>valid</code> 属性。</p>
<pre><code class="language-css">input:valid {
boder-color: lightsalmon;
}
</code></pre>
<h1 id="invalid-选择一个未通过验证的元素"><code>:invalid</code> | 选择一个未通过验证的元素</h1>
<p>像 <code>:valid</code> 一样,但是会应用到未通过验证的元素。</p>
<pre><code class="language-css">input[type="text"]:invalid {
border-color: red;
}
</code></pre>
<h1 id="lang-选择指定语言的元素"><code>:lang()</code> | 选择指定语言的元素</h1>
<p>应用于指定了语言的元素。</p>
<p>可以通过以下两种方式使用:</p>
<pre><code class="language-css">p:lang(fr) {
background: yellow;
}
</code></pre>
<p>或者</p>
<pre><code class="language-css">p[lang|="fr"] {
background: yellow;
}
</code></pre>
<pre><code class="language-html"><p lang="fr">Paragraph 1</p>
</code></pre>
<h1 id="not-对于选择取反这是一个运算符"><code>:not()</code> | 对于选择取反(这是一个运算符)</h1>
<p>否定伪类选择器选中相反的。</p>
<p>让我们看一个示例:</p>
<pre><code class="language-css">.innerDiv :not(p) {
color: lightcoral;
}
</code></pre>
<pre><code class="language-html"><div class="innerDiv">
<p>Paragraph 1</p>
<p>Paragraph 2</p>
<div>Div 1</div>
<p>Paragraph 3</p>
<div>Div 2</div>
</div>
</code></pre>
<p><code>Div 1</code> 和 <code>Div 2</code> 会被选中,因为他们不是 <code>p</code> 元素。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[通过 JavaScript 获取内网 IP 和外网 IP 的简单方法]]></title>
<id>https://faded.auspicious.space/post/a-simple-way-to-access-both- intranet-and-extranet-ip-through-javascript/</id>
<link href="https://faded.auspicious.space/post/a-simple-way-to-access-both- intranet-and-extranet-ip-through-javascript/">
</link>
<updated>2020-05-27T03:45:37.000Z</updated>
<summary type="html"><![CDATA[<blockquote>
<p><a href="https://www.cnblogs.com/wjyz/p/10638584.html">通过js获取内网ip和外网ip的简单方法 ...</a></p>
</blockquote>
]]></summary>
<content type="html"><![CDATA[<blockquote>
<p><a href="https://www.cnblogs.com/wjyz/p/10638584.html">通过js获取内网ip和外网ip的简单方法 ...</a></p>
</blockquote>
<!-- more -->
<h1 id="获取内网-ip">获取内网 IP</h1>
<pre><code class="language-javascript">function getIP(callback) {
let recode = {};
let RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
// 如果不存在则使用一个iframe绕过
if (!RTCPeerConnection) {
// 因为这里用到了iframe,所以在调用这个方法的script上必须有一个iframe标签
// <iframe id="iframe" sandbox="allow-same-origin" style="display:none;"></iframe>
let win = iframe.contentWindow;
RTCPeerConnection = win.RTCPeerConnection || win.mozRTCPeerConnection || win.webkitRTCPeerConnection;
}
//创建实例,生成连接
let pc = new RTCPeerConnection();
// 匹配字符串中符合ip地址的字段
function handleCandidate(candidate) {
let ip_regexp = /([0-9]{1,3}(\.[0-9]{1,3}){3}|([a-f0-9]{1,4}((:[a-f0-9]{1,4}){7}|:+[a-f0-9]{1,4}){6}))/;
let ip_isMatch = candidate.match(ip_regexp)[1];
if (!recode[ip_isMatch]) {
callback(ip_isMatch);
recode[ip_isMatch] = true;
}
}
//监听icecandidate事件
pc.onicecandidate = (ice) => {
if (ice.candidate) {
handleCandidate(ice.candidate.candidate);
}
};
//建立一个伪数据的通道
pc.createDataChannel('');
pc.createOffer((res) => {
pc.setLocalDescription(res);
}, () => { });
//延迟,让一切都能完成
setTimeout(() => {
let lines = pc.localDescription.sdp.split('\n');
lines.forEach(item => {
if (item.indexOf('a=candidate:') === 0) {
handleCandidate(item);
}
})
}, 1000);
}
</code></pre>
<p>调用该函数:</p>
<pre><code class="language-javascript">getIP(function (ip) {
console.log(ip);
})
// 192.168.1.191
// 2001::2841:aa90:2843:1983:e4d1:a9b8
</code></pre>
<h2 id="获取公网-ip">获取公网 IP</h2>
<pre><code class="language-html"><!--引入接口文件-->
<script type="text/javascript" src="http://pv.sohu.com/cityjson?ie=utf-8"></script>
<!--返回结果为 var returnCitySN = {"cip": "27.46.86.71", "cid": "440000", "cname": "广东省"};
在下面的js中,通过调用returnCitySN.cip就可以获取 外网的ip-->
</code></pre>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[一篇文章掌握 14 种 UML 图]]></title>
<id>https://faded.auspicious.space/post/mastering-14-uml-diagrams-in-a-single-article/</id>
<link href="https://faded.auspicious.space/post/mastering-14-uml-diagrams-in-a-single-article/">
</link>
<updated>2020-05-27T02:57:41.000Z</updated>
<summary type="html"><![CDATA[<blockquote>
<p><a href="https://mp.weixin.qq.com/s?__biz=MzIxMjE5MTE1Nw==&mid=2653202874&idx=3&sn=81aa87a3a8faa1c353d78ec31b30ed3d&chksm=8c99db60bbee5276a5bff014e8800b0e4513754d309a3540d4a5b1f0b59e9af05418b0c21a61&mpshare=1&scene=23&srcid=&sharer_sharetime=1588477918993&sharer_shareid=d8e58ea207a9c340f9ebfddc69dc6b8e#rd">UML科普文,一篇文章掌握14种UML图</a></p>
</blockquote>
]]></summary>
<content type="html"><![CDATA[<blockquote>
<p><a href="https://mp.weixin.qq.com/s?__biz=MzIxMjE5MTE1Nw==&mid=2653202874&idx=3&sn=81aa87a3a8faa1c353d78ec31b30ed3d&chksm=8c99db60bbee5276a5bff014e8800b0e4513754d309a3540d4a5b1f0b59e9af05418b0c21a61&mpshare=1&scene=23&srcid=&sharer_sharetime=1588477918993&sharer_shareid=d8e58ea207a9c340f9ebfddc69dc6b8e#rd">UML科普文,一篇文章掌握14种UML图</a></p>
</blockquote>
<!-- more -->
<h1 id="什么是-uml">什么是 UML?</h1>
<p>UML 是 Unified Model Language 的缩写,中文是统一建模语言,是由一整套图表组成的标准化建模语言。</p>
<h1 id="为什么要用-uml">为什么要用 UML?</h1>
<p>通过使用 UML 使得在软件开发之前, 对整个软件设计有更好的可读性,可理解性,从而降低开发风险。同时,也能方便各个开发人员之间的交流。</p>
<p>UML 提供了极富表达能力的建模语言,可以让软件开发过程中的不同人员分别得到自己感兴趣的信息。</p>
<p>Page-Jones 在《Fundamental Object-Oriented Design in UML》 一书中总结了 UML 的主要目的,如下:</p>
<ol>
<li>为用户提供现成的、有表现力的可视化建模语言,以便他们开发和交换有意义的模型。</li>
<li>为核心概念提供可扩展性(Extensibility)和特殊化(Specialization)机制。</li>
<li>独立于特定的编程语言和开发过程。</li>
<li>为了解建模语言提供一个正式的基础。</li>
<li>鼓励面向对象工具市场的发展。</li>
<li>支持更高层次的开发概念,如协作,框架,模式和组件。</li>
<li>整合最佳的工作方法(Best Practices)。</li>
</ol>
<h1 id="uml-图有哪些">UML 图有哪些?</h1>
<ul>
<li>UML 图分为结构图和行为图。</li>
<li>结构图分为类图、轮廓图、组件图、组合结构图、对象图、部署图、包图。</li>
<li>行为图又分活动图、用例图、状态机图和交互图。</li>
<li>交互图又分为序列图、时序图、通讯图、交互概览图。</li>
</ul>
<figure data-type="image" tabindex="1"><img src="https://faded.auspicious.space/post-images/1590548880541.webp" alt="" loading="lazy"></figure>
<h1 id="uml-图概览">UML 图概览</h1>
<table>
<thead>
<tr>
<th>UML 类型</th>
<th>目的</th>
<th>版本</th>
</tr>
</thead>
<tbody>
<tr>
<td>类图</td>
<td>描述了系统中对象的类型以及他们之间存在的各种静态关系。</td>
<td>UML 1.x</td>
</tr>
<tr>
<td>组件图</td>
<td>描绘了系统中组件提供的、需要的接口、端口等,以及它们之间的关系。</td>
<td>UML 1.x,UML 2.0 重新定义了</td>
</tr>
<tr>
<td>对象图</td>
<td>对象图是类图的一个实例,是系统在某个时间点的详细状态的快照。</td>
<td>UML 1.x</td>
</tr>
<tr>
<td>轮廓图</td>
<td>轮廓图提供了一种通用的扩展机制,用于为特定域和平台定制 UML 模型。</td>
<td>UML 2.0</td>
</tr>
<tr>
<td>组合结构图</td>
<td>描述了一个“组合结构”的内部结构,以及它们之间的关系。</td>
<td>UML 2.0</td>
</tr>
<tr>
<td>部署图</td>
<td>描绘了系统在包层面上的结构设计。</td>
<td>UML 1.x</td>
</tr>
<tr>
<td>包图</td>
<td>描绘了系统在包层面上的结构设计。</td>
<td>UML 2.0</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>用例图</td>
<td>指由参与者、用例、边界以及它们之间的关系构成的用于描述系统功能的视图。</td>
<td>UML 1.x</td>
</tr>
<tr>
<td>活动图</td>
<td>描述了具体业务用例的实现流程。</td>
<td>UML 1.x</td>
</tr>
<tr>
<td>状态机图</td>
<td>描述了对象在它的整个生命周期里,响应不同事件时,执行相关事件的顺序。</td>
<td>UML 1.x</td>
</tr>
<tr>
<td>序列图</td>
<td>描述了在用例的特定场景中,对象如何与其他对象交互。</td>
<td>UML 1.x</td>
</tr>
<tr>
<td>时序图</td>
<td>时序图被用来显示随时间变化,一个或多个元素的值或状态的更改。</td>
<td>UML 2.0</td>
</tr>
<tr>
<td>交互概览图</td>
<td>交互概览图与活动图类似,但是它的节点是交互图。</td>
<td>UML 2.0</td>
</tr>
<tr>
<td>通讯图</td>
<td>描述了收发消息的对象的组织关系,强调对象之间的合作关系而不是时间顺序。</td>
<td>UML 1.x 叫协作图,UML 2.0 改名了</td>
</tr>
</tbody>
</table>
<h1 id="什么是类图">什么是类图?</h1>
<h2 id="概念">概念</h2>
<p>类图是一切面向对象方法的核心建模工具。类图描述了系统中对象的类型以及它们之间存在的各种静态关系。</p>
<h2 id="目的">目的</h2>