-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmy-notes.txt
1083 lines (708 loc) · 33.4 KB
/
my-notes.txt
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
Clojure is a new programming language created by Rich Hickey
clojure.org
-----------------
Clojure is a dialect of LISP
Common LISP
Scheme
Dynamic Language
key feature > macros
more things are determined at a program’s runtime rather than when a compiler looks at the source code
eg.
does not require type declarations when using variables or defining functions
eval function allows code to be loaded up and executed at runtime
REPL
(read-eval-print loop)
Interactive environment that allows code to be typed in and executed
It gives quick feedback and is helpful in incrementally writing code
Functional Language (impure)
pure functional > haskel
Clojure more functionally pure than most LISP dialects
Clojure data structures as immutable
Clojure does have fantastic support for explicit state management
Clojure encourages the use of higher-order functions such as map and reduce
Core data structures are lazy, which means things get executed only as needed
which enables some rather efficient programming constructs, eg. ability to define and use infinite sequences
>> Clojure takes advantage of its immutability and provides language-level support for safe, lock-free concurrency (using a software transactional memory model)
biggest selling points > "lockless" concurrency
programmer doesnt have to handle locking
Higher-order Functions
Clojure functions are first class, which means that functions can be passed as parameters to other functions, can be created dynamically, and can be returned from functions.
They can be stored inside other data structures like regular data.
Clojure also provides lexical closures, which is a powerful construct that allows for expressive code.
This functional aspect of Clojure makes it easy to write code using higher-level constructs like map, reduce, and filter, which apply arbitrary functions to lists of data.
Immutability
All of Clojure’s core data structures are immutable—once created they can’t change
When something needs to change, a new object is created that includes the change and is returned
Clojure’s implementation of immutability performs extremely well and doesn’t slow down programs
This immutability greatly aids Clojure’s support of concurrency-safe multithreaded programs
Lazy And Infinite Sequences
This means that elements of a Clojure Sequence are not computed until the values are needed for something else
Most core functions (such as map, reduce, and filter) produce lazy data structures that aren’t realized until needed
Further, this implies that a chain of such function calls returns a value that’s also lazy!
Clojure allows the programmer to create infinite lists of data
Concurrency And The Multicore
Multithreaded programming raises two major issue:
getting multithreaded programs to behave correctly and
using all available cores to speed up the program
Clojure helps with both
-----------------------------------
Software Transactional Memory (STM)
Clojure implements a MultiVersion Concurrency Control (MVCC) based STM model to addresses the issue of correctness of multithreaded programs
Not only does the language provide simple ways to handle mutation, but it also provides constructs that allow and enforce safe mutation.
What this means in simpler terms is that mutating the value of an object can only be done inside a transaction (think database transaction).
This has two advantages.
The first is that code becomes self-documenting.
When the value of something needs to change in a thread-safe manner, the programmer must be explicit and use a special Clojure construct for it: the ref.
The other advantage is that if you attempt to modify the value inside a ref without an STM transaction, the Clojure runtime will throw an exception.
This is how Clojure enforces the use of the transaction semantics and helps keep code thread-safe.
When a transaction needs to commit, and another thread has already committed a change to a shared ref, the later transaction is rolled back.
Clojure’s STM system even retries the failed transaction several times, and as far as the programmer is concerned,
all this happens transparently
eg.
(defn add-amount [amount]
(dosync
(ref-set total-expenditure (+ amount @total-expenditure))))
@total-expenditure is a reader macro in action, it expands to (deref totalexpenditure) and it gets the value out of the object that the ref is pointing to.
By using a ref and by making sure that updates are always performed inside a dosync block, access to the variable protected by the ref becomes thread safe.
-----------------------------------
Clojure is hosted on JVM
Clojure code gets compiled into Java Bytecode
The design of Clojure embraces interoperability with other Java libraries as one of its central goals
Java Interoperation
Use existing Java Libraries, It is trivial to called method of java class from clojure code and vice versa (lil more verbose)
eg.
Java in Clojure
user=> (.toUpperCase "clojure")
Clojure in Java
import clojure.lang.RT;
import clojure.lang.Var;
RT.loadResourceScript("clojure/file/clojure_script.clj");
Var aClojureFunction = RT.var("a-clojure-name-space", "a-clojure-function");
aClojureFunction.invoke("an argument" "another one");
Clojure code can be embedded into Java programs, thereby providing an incredibly flexible scripting capability to the end user.
Clojure itself can be used as a glue language to bring the whole system together
JVM, is one of the most efficient virtual machines
The HotSpot optimizer does amazing things to speed up code running on top of it, and Clojure benefits from all this innovation.
Despite being incredibly dynamic, Clojure code gets compiled to Java byte code and runs as fast or nearly as fast as Java code itself.
This gives programmers all of Clojure’s benefits without any major performance costs
Type Hints
You can help run Clojure programs faster by giving the evaluator type hints about any Java class you’re using, so that Clojure will know how to call methods on objects of such classes without reflection.
eg.
(defn some-function [^ClassName object]
(.start s) s)
The type hint is the ^ followed by the name of the class.
When the start method is called, Clojure will do so without any reflection. This is much faster than without the
type hint.
Extensibility
By implementing the ISeq interface, the new data structure becomes native to Clojure and behaves the same as core Clojure data structures.
Operations like map, reduce, and filter will all work on this data structure
Persistent Collections << most unique features
EASY .. core of the language is realy realy small (minimal set of syntax rules)
***************************************************************
What does it mean for a language to be functional
or
What does it mean to program in a functional style
functions are first-order
they can be treated as value
you can store function in a variable in a collection
pass is as an argument
return it
function-like constructs
control statements like if or while instead of being statements they behave like functions
in functional language they return values
if statement can be an argument to another function because it get evaluated into a value like an expression
makes language more COMPOSABLE
take different parts and compose them out of other different parts
STATELESS
functional in the sense of mathematics
function is something that has
takes value input(s)
produces value output(s)
doesnt rely on any outside global state
doesnt affect any outside global state
doesnt create any side-effects
pure functional: no IO, no reading/modifying global value
ADV: easier to write / understand
>> we need side-effects if the program is going to do something useful
a program without side-effects does nothing but make your CPU get hot
IDEA in functional programming
NOT to ELIMINATE all state changes but TO ISOLATE all state changes
PURE functional (haskel) :
enforced by language
all potential state changes have to be denoted as special exceptions
actors and monads
holes in functional purity which allows you to do something stateful
INPURE
clojure does not enforce anything by the way of language
help you structure the code in a functional way
upto the programmer to keep the side-effects out of what is ment to be purely functional
Immutable data
If we want to have out functions avoid mutating any state and avoid relying on state outside the functions
.. makes sense to make as much data immutable
then we dont have to worry about data changing
Collections in clojure are immutable
ditto for local variable
sounds like a big hinderance .. but im told its not
---------------------------------
Some data-types of the language
Any object in clojure is really just a regular java object
Some instance of Java reference type which inherits from java.lang.Object
Numbers
arbitrary-sized numbers
ratios
written as a literal with a slash between the two numbers
NIL is NULL
treated as if it has the truth-value of FALSE
***************************************************************
Persistent Collection
collection which is
* Immutable
* but we can create Cheap modified copies
supports all operations of non-persistent collections
except they return new copies instead of modifying existing ones
KEY IDEA here is that these copies are cheap
both in terms of time and space (memory)
eg. If I want to add a element to an immutable list, The new immutable list with the extra element, actually points to all of the origional data and the new list
This implementation is way trickier that normal linked list implimentation
Persistent Colleciton types:
1. List ( 1 2 3 )
Linked list
Look-up time: Sequential
2. Vector [ 1 2 3 ]
Numerically indexed collection
based on an implementation using hashing
Look-up time: Constant (near constant .. logarithmic)
3. Hashmap { "foo" 3 5 7 }
Associative collection
Key-Value pairs
In the above example key "foo" has value 3 and key 5 has value 7
----------------------------
Equality: have the same value
Identity: not just euqal but the same object in memory
In Java == when applied on
primitives: equality check
reference types: identity check
In clojure == always does equality test
Because all objects are immutable, so while creating a large vector we can create a hash of that vector and just compare this hash while doing an equality check
String in clojure is just java.lang.String
.. so equality test of Strings in clojure just uses regular java's euqals() method
----------------------------
SYMBOLS
(Identifier as value)
Symbol is a data-type like a string
Symbol is a sequence of characters where there are rules as to what those characters can be
But symbols in clojure are not as restrictive as identifiers from other language
can have ! . - ? + =
eg.
blue
EE!35?
sassy-sally
semi_sweet
Symbol as Value:
Can be an item in a collection or can be passed as argument to a function or return from a function
Symbols can serve as identifiers representing variables and symbols just representing symbol's value
-----------------------------
KEYWORD
like a symbol, but not
Starts with a colon
eg.
:blue
:EE!35?
:sassy-sally
:semi_sweet
Useful to have a data type like a symbol but distinguishable from a symbol
-----------------------------
METADATA
data that describes other data
In clojure we can attach a map as metadata to any other data-types
meta-data can also not be modified
equality checks ignore metadatas
***************************************************************
var
not all clojure data-types are immutable
a mutable reference
an object that is a pointer to another object
>> var is a mutable collection of one that holds a reference
----------------------
namespace
mapping of symbols to vars
an associative collection that maps the symbols keys to values that are vars
each namespace is identified by a unique name
eg.
Namespace.Dog
Symbol.cute > var
Symbol.ugly > var
Namespace.Cat
Symbol.sleepy > var
This is how code is organised in clojure
when we write a function it is assigned to a var which mapped to some symbol in some namespace and thats how it is found
-------------------------
Clojure Runtime
READER
The reader is the entity that reads source code and converts it into s-expressions.
These s-expressions are composed of ordinary Clojure data structures such as symbols, lists, vectors, sets, and hash maps.
To summarize, the reader converts source code into an AST implemented using Clojure’s own data structures.
The reader does this by using parentheses (and other brackets) as delimiters of each s-expression. In essence, the combination of the brackets and the reader allows code to be written in the AST directly.
Homoiconic
(homo means same, iconic means representation)
Programming languages where code is written in the language’s own data structures.
Homoiconicity is also what makes Clojure’s macro system possible
Further, the reader invalidates the need to write language parsers because the reader does that already.
These data structures are then (after reading a complete literal) passed to the evaluator to produce a result
EVALUATOR
traverse data structures from reader
Symbol Evaluation
SYMBOLS have to be resolved into VARs
rabbit:
evaluator finds the symbol in the current namespace
pig/rabbit:
symbol rabbit in the namespace pig
List Evaluation
LISTS evaluate into FUNCTION CALLS
Rule
First item in the list is the specifier of the function which resolves to a var which points to a function "falcon"
Remmaining items are the arguments to that funciton
eg. (falcon 8 2 "goodbye")
Function call to function "falcon" in current namespace with 8, 2 and "goodbye" as arguments
eg. (iguana/hippo [nil 3] (kangaroo))
Function call to function "hippo" in namespace iguana, called with arguments of vector containing two items nil and 3 and the second argument is the value returned by the call to kangaroo
(function mapped to by the var kangaroo is called with no arguments)
Special Forms
A reserved symbol denoting special list evaluation
Not a function call but something else
If a list begins with any of these symbols that list is evaluated in a special way particular to that form
def
if
do
let
quote
var
fn
loop
recur
throw
try
.
new
set!
-----------------------------
if
(if condition a b?)
when if is executed condition expression returns either true or false
true : expression a is going to be executed and returned
false: expression b, if specified
eg.
(if (vulture) (foo) (bar))
(if (ostrich) "hide")
>> returns the default NIL when FALSE
***************************************************************
quote
(quote expression)
Sometimes you want to write a symbol which is just meant to be a symbol and not resolve into a var in a namespace or you want to write a list which is not meant to be a function call, just to be a list
quote is a special form that returns the expression as-is, without any evaluation
eg. (fish (quote george))
gerorge is not resolved
fish is called with the symbol george
eg. (fish (quote (foo (bar) 3)))
foo and bar are not resolved
fish is called with argument that is a list starting with the symbol foo, another list and the number 3
----------------------
def
(def name object)
Special Form that is used to create or modify symbol:var mappings in the current namespace
eg.
(def beetle 3)
(def shark ["hello" "goodbye"])
If the current namespace already had a mapping to that symbol then it modifies that var, doesnt create a new one.
-----------------------
.
used to call java methods
static methods: (. class method argument*)
instance methods: (. instance method argument*)
eg.
(. Cat meow) ; Cat.meow()
(. foo bar 3 1 ) ; foo.bar(3,1)
(. (ack) bar 3 1 ) ; ack().bar(3,1)
^^^ this is a totally DYNAMIC CALL to java method
------------------------
new
(new class argument*)
Special form to instantiate java classes
class is specified as a symbol
arguments that are required by the constructor
eg. (new java.util.Date)
Posible to import the classes such that we dont have to fully qualify their name with packages
-------------------------
Standard Library Functions
There are number of things in the clojure standard library which you normally expect to find in a language as operators
eg. The arithematic operations in clojure are actually just funcions in the standard library namespace clojure.core
(clojure.core/+ 8 20) ; 28
(clojure.core/* 3 4) ; 12
-------------------------
Switching namespace
in-ns >> in namespace
(clojure.core/in-ns (quote alligator))
(def harry "hello")
(clojure.core/println harry)
(clojure.core/inns (quote trout))
(clojure.core/println harry) ; error
-------------------------
Symbol-var mapping in namespaces some in two types:
1. Interned
Normal mapping
Created or modified by DEFs
True member of a namespace
2. Referred
Borrowed from another namespace
Not a true member of a namespace, It' i's there for the sake of convenience so that we dont have to fully qualify so many names
eg.
Namespace.Dog
Symbol.cute > var
Symbol.ugly > var
Namespace.Cat
Symbol.sleepy > var
Namespace.Bird
Symbol.silly > var
>> If we refer the mappings of namespace cat into dog
Namespace.Dog
Symbol.cute > var
Symbol.ugly > var
| Symbol.sleepy > var |
Namespace.Cat
Symbol.sleepy > var
Namespace.Bird
Symbol.silly > var
>> NOTE: both Dog.sleepy and Cat.sleepy point to the same var
>> Now if we refer the mappings of namespace Dog into the namespace Bird
Namespace.Dog
Symbol.cute > var
Symbol.ugly > var
| Symbol.sleepy > var |
Namespace.Cat
Symbol.sleepy > var
Namespace.Bird
Symbol.silly > var
| Symbol.cute > var |
| Symbol.ugly > var |
>> NOTE:
only the interned mappings of Dog end up as Refered mappings in Bird
the refered mapping of Dog is not refered into bird
***************************************************************
(clojure.core/in-ns (quote alligator))
(clojure.core/refer (quote clojure.core))
(def harry (+ 3 6))
--------------
do
(do expression*)
takes a list of expressions (body of do) which are executed in order
returns the value retured by the last expression
eg.
(do
(beetle)
(print)
(shark ["hello" "goodbye"]))
--------------
let
(let [(name value)*] expresssion)
like do,
except it has the ability of binding values to names
so for the sake of this let you can establish a name say foo which has a certain value
these name value pairs are written inside a vector
so that they are easily distinguishable
eg.
(def frog (let [cow 6] (lion cow)))
(let [tiger (bat 3) manatee "yo"]
(ostrich tiger)
(urchin manatee))
effectively these name-value pairs serve as local variables
----------------
fn
(fn name? [parameter*] expression*)
Defines and returns a function
Has a body of expression and a vector for specifying parameters
The name is optional
It is used inside the function to refer to the function itself
So a function can possibly return itself or call itself
eg.
(def duck ( fn [a] (+ a 4)))
(duck 7)
; 11
(def sloth (fn b [] (println "hello") b))
(sloth)
-----------------
Lexical Scope
refers to a scheme in languages where names refer to the most immediate scope in which they are contained
eg.
(let [manatee "yo"]
(ostrich (fn [manatee] (+ manatee 4)))
(urchin manatee))
inside fn: manatee refers to the parameter
outside in let: manatee refers to its value binding
***************************************************************
Tail Recursion
Special case of recursion in which the function is recursively called as the very last thing in that function
Affords a very big optimization
Normally every function call requires its own stack frame (memoery)
However in a function call when we make the last call, we know that we know that we are not coming back i.e. not going to use the local variables again .. So we can discard the frame for that call
So now if the function is recursively called 1,000 times
Without optimization: 1,000 stack frames
With optimization: 1 stack frame
Every time we make a tail recursive call we overwrite the existing frame instead of making a new one
In most dialects of LISP the compiler will identify these case of tail recursion and automatically make this optimization
In Clojure however because JVM doesnt have this optimization for tail recursion we have to explicitly denote these tail recursive calls
---------------------
recur
(recur argument*)
Compiler checks to make sure it is used in the tail position
eg.
(def mouse
(fn [a]
(print a)
(if (< a 1000)
(recur (+ a 4)))))
(mouse 7)
-----------------------
loop
(loop [(name value)*] expression*)
Just like let
Block of code where we can bind values to names
Except
If we can recur in a loop it takes us back to the top and rebind the values to names
eg.
(loop [a 7]
(print a)
(if (< a 1000)
(recur (+ a 4))))
------------------------
Clojure Runtime (EVALUATOR)
2 step process
compile
evaluator looks at the reader data and compiles into java bytecode
* analyse special forms
* resolve symbols
* process macros
execute
executes java bytecode
* effect special forms
* call functions
>> Because Symbols are resolved at compile time has an important consequence:
In most modern languages the order in which we define functions does not really matter, even in dynamic languages
eg.
(def duck (fn [] (frog))) ; throws error
(def frog (fn [] (print "hello"))
(duck)
throws error because the compiler expects all symbols to resolve to a var in a namespace or to a local name (parameter or name in a let)
both the following two fixes will work
reordering definitinos
(def frog (fn [] (print "hello"))
(def duck (fn [] (frog)))
(duck)
or
add a empty symbol-var mapping for frog (this var can change at anytime)
(def frog)
(def duck (fn [] (frog)))
(def frog (fn [] (print "hello"))
(duck)
------------------------
macro
a function that modifies the reader data
Function that takes data from the reader and TRANSFORMS the reader-data before the it gets compiled into bytecode
Macros serve as syntactic conveniences
remove boiler plate code by creating macros
eg.
(monkey (racoon 1 2) [4 5 6])
Macro get Unevaluated Input
calls macro function monkey with arguments, first a list containing symbol racoon, 1 and 2 and second a vector containing 4, 5 and 6
the macro gets it's arguments unevaluated, it gets the reader-data not the values returned by compiling and executing that data
Macros for Syntactic convenience
Macro return a value and it's this value that is which get put back in the reader-data in place of where the macro call was
Macros are processed
Inside-Out
eg. monkey is the outer one, it is processed first
Recursively
If there are any macro calls in what gets returned by a macro then they get processed too
>> (print (racoon 1 2) "yo" [4 5 6])
eg.
(defn lobsert [x] (print x))
>> (def lobster (fn [x] (print x)))
Macros act as a hook between the read and evaluate phases of the Clojure runtime.
Macros are Clojure functions, but they accept s-expressions as their arguments. Because s-expressions are data structures, they can be transformed and returned, and the return values are used in place of the original forms.
This ability to programmatically manipulate code is what it means to have access to the AST as a simple data structure.
-----------------------------
Metaprogramming with Macros
Idea of programs generating or manipulating other programs (or themselves)
reduce boilerplate code
build syntactic abstractions (DSLs) on top of the core language
***************************************************************
defmacro
(defmacro name [parameter*] expression*)
defmacro is used to create macros
defmacro is itself a macro in the namespace clojure.core
defmacro in the background creates a function
Because macro is just a function not a seperate data-type
Except the var that it is bind to, In its' metadata has a key "macro" with value as true to denote that this is a macro
--------------------------
sequence (seqs)
It is a logical list
Is not a concrete collection type, but an abstraction for collections
Clojure defines many algorithms in terms of sequences
>> Unlike most Lisps where the list is represented by a concrete, 2-slot structure, Clojure uses the ISeq interface to allow many data structures to provide access to their elements as sequences. The seq function yields an implementation of ISeq appropriate to the collection.
Features
Seqs differ from iterators in that they are persistent and immutable, not stateful cursors into a collection.
As such, they are useful for much more than foreach - functions can consume and produce seqs, they are thread safe, they can share structure etc.
Most of the sequence library functions are lazy
i.e. functions that return seqs do so incrementally, as they are consumed, and thus consume any seq arguments incrementally as well.
Functions returning lazy seqs can be implemented using the lazy-seq macro
Lazy Sequence
Collection where data of the rest is defined wholly or partly in terms of a funciton not necessarily data in memory
This allows the programmer to create infinite lists of data
Functions returning lazy seqs can be implemented using the lazy-seq macro.
lazy-seq macro
It doesn’t evaluate its body immediately but returns a sequence-like object.
This object will evaluate the body only when needed (and will also cache the result for subsequent uses,
saving CPU cycles).
eg.
(defn next-terms [term-1 term-2]
(let [term-3 (+ term-1 term-2)]
(lazy-seq
(cons term-3
(next-terms term-2 term-3)))))
(defn fibonacci [t1 t2]
(concat [t1 t2]
(next-terms t1 t2)))
(take 15 (fibonacci 0 1))
(0 1 1 2 3 5 8 13 21 34 55 89 144 233 377)
In absense of lazy sequences returned by lazy-seq macro this program would run in an infinite loop until the program runs out of memory.
And by using certain functions like take, you can intelligently produce the required number of elements from such infinite sequences.
HashMap as Sequence
tricky because hashmap is itself an unordered collection
first: returns a random key-value pair
but the same pair is retutned on each call
--------------------------
The Seq interface
first
(first coll)
Returns the first item in the collection. Calls seq on its argument. If coll is nil, returns nil.
rest
(rest coll)
Returns a sequence of the items after the first. Calls seq on its argument. If there are no more items, returns a logical sequence for which seq returns nil.
cons
(cons item seq)
Returns a new seq where item is the first element and seq is the rest.
--------------------------
Iteration
* recursion
helps us avoid using anything stateful
however in clojure we use recursion as a last resort
* sequence functions
usually when we iterate we want to do something for every item in the collection
--------------------------
structmap
A hashmap optimized for a predefined set of keys
doesnt have any associated methods
Like in java we create classes which are blueprints for instances
In clojure we create structmap basis objects which are used as blueprints for structmaps
Advantage over normal hashmap
For the set of keys defined in the basis object the structmaps created from those basis are optimized for those keys both in terms of access time and space efficiency
--------------------------
Is clojure not OO ?
What is OOP
encapsulation
data + operations on that data together through a limited interface to maintain the integrity of this data
inheritance
while defining a type being able to borrow attributes of another type
polymophism
function or operation is able to change its behavious depending upon the type/number of its arguments
run-time polymophism
compile-time polymophism
>> clojure does not enforce encapsulation
in clojure we can define some types (x) and then define some functions (y) which operate on those types
So x.y(1, 2) becomes (y x 1 2)
>> polymorphism is achieved by using generic functions (multimethods) to decide how to dispatch a function at runtime
>> inheritance > hierarchies
relationship between symbols/keywords
eg. symbol.x is parent of symbol.y
it is used in polymorphism
-----------------------------
Clojure as a language supports all kinds of paradigms:
You can write procedural code if you want, or
You can write your program in a functional style
Clojure programs can be written with an OO approach
Clojure is well suited for building custom languages (DSL) on top of a Lisp foundation
The macro system built into Clojure plays a large role in this.
Clojure is also well suited for bottom-up design.
Bottom-up design
An approach where higher-level components are built up from a collection of lower-level ones.
A problem domain is analyzed, and several low-level components are created where each represents a single concept.
These pieces are then combined to create the higher-level components as demanded by the problem at hand.
Thanks to Clojure’s functional paradigm (higher-order functions and closures) and the associated possibility of using function combination, it’s a great language for designing systems in a bottom-up manner.
It’s common to see Clojure programs that first create a mini-language that allows concepts in the problem domain to be expressed at a high level and then the problem solved in this language instead.
The bottom line is that Clojure doesn’t limit you by imposing a particular paradigm.
***************************************************************
multimethod
a function that dispatches to other functions
Generic functions that let the programmer decide how to dispatch functions at runtime
This is a far more capable approach to polymorphism
To achieve polymorphism in dynamic language
Write a function that looks at its arguments, number and types, and based passes control over to other functions
multimethod is a special function for this explicit purpose which makes it very convenient
-------------------
variatic function
variable arity (number of arguments a function takes)
just like java varargs
in the parameter list the symbol '&' is treated specially
when you proceed the last parameter with ampersand that means it is a variatic function and that parameter receives the extra arguments to the function
eg.
(fn [x & y] ...)
(fn [a b c & d] ...)
-------------------
more about Reader Syntax