Skip to content

Commit aff716e

Browse files
committed
polish
1 parent 50bee57 commit aff716e

File tree

1 file changed

+124
-55
lines changed

1 file changed

+124
-55
lines changed

docs/src/lecture_03/lecture.md

Lines changed: 124 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# Design patterns: good practices and structured thinking
22

3-
Design guiding principles:
3+
Every software developer has a desire to write better code. A desire
4+
to improve system performance. A desire to design software that is easy to maintain, easy to understand and explain.
5+
6+
Design patterns are recommendations and good practices accumulating knowledge of experienced programmers.
7+
8+
The highest level of experience contains the design guiding principles:
49
- SOLID: Single Responsibility, Open/Closed, Liskov Substitution, Interface
510
- Segregation, Dependency Inversion
611
- DRY: Don't Repeat Yourself
@@ -9,23 +14,49 @@ Design guiding principles:
914
- YAGNI: You Aren't Gonna Need It (overengineering)
1015
- POLP: Principle of Least Privilege
1116

12-
These hig-level concepts are guiding principles for
17+
While these high-level concepts are intuitive, they are too general to give specific answers.
18+
19+
More detailed patterns arise for programming paradigms (declarative, imperative) with specific instances of functional or object-oriented programming.
1320

14-
Julia does not fit into any methodological classes like *object-oriented* or *functional* programming. The key concept of julia are:
21+
Julia is a multi-paradigm, taking features of both *object-oriented* and *functional* programming. The key concept of julia are:
1522
- *type system* of data structures
1623
- *multiple dispatch*, respecting the types,
1724
- functional programming concepts such as *closures*
1825

19-
These simple concepts allows to construct design patters common in other languages.
20-
- the language does not enforce many formalisms structures (can be limiting as well as advantageous).
21-
- the compiler cannot check for correctness of "patterns"
26+
How do I correctly use these tools to express what I want to achieve?
27+
28+
The concept of design patterns originates in the OOP paradigm. OOP defines a strict way how to write software:
29+
- class
30+
- interface
31+
- virtual methods
32+
It is often unclear how to use these concepts to solve a particular programming task.
33+
34+
A cookbook:
35+
- Gamma, E., Johnson, R., Helm, R., Johnson, R. E., & Vlissides, J. (1995). Design patterns: elements of reusable object-oriented software. Pearson Deutschland GmbH.
36+
37+
Defining 23 design patterns in three categories. Became extremely popular.
38+
39+
Fundamental tradeoff: rules vs. freedom
40+
- freedom: in the C language it is possible to access assembler instructions, use pointer aritmetics:
41+
- it is possible to write extremely efficient code
42+
- it is easy to segfault, leak memory, etc.
43+
- rules: in strict languages (strict OOP, strict functional programing) you lose freedom for certain guarantees:
44+
- e.g. strict functional programing guarantees that the program provably terminates
45+
- operations that are simple e.g. in pointer arithmetics may become clumsy and inefficient in those strict rules.
46+
- the compiler can validate the rules and complain if the code does not comply with them.
47+
48+
Julia is again a dance between freedom and strict rules. It is more inclined to freedom.
49+
Provides few simple concepts that allow to construct design patterns common in other languages.
50+
- the language does not enforce too many formalisms (via keywords (interfac, trait, etc.) but they can be
51+
- the compiler cannot check for correctness of these "patterns"
2252
- the user has a lot of freedom (and responsibility)
2353
- lots of features can be added by Julia packages (with various level of comfort)
2454
- macros
2555

56+
Read:
57+
- Hands-On Design Patterns and Best Practices with Julia Proven solutions to common problems in software design for Julia 1.x Tom Kwong, CFA
2658

27-
28-
## Closures
59+
## Functional tools: Closures
2960

3061
!!! tip "Closure (lexical closure, function closure)"
3162
A technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function together with an environment.
@@ -41,7 +72,7 @@ function adder(x)
4172
return y->x+y
4273
end
4374
```
44-
creates a function that "closes" the argument ```x```.
75+
creates a function that "closes" the argument ```x```. Try: ```f=adder(5); f(3)```.
4576

4677
```julia
4778
x = 30;
@@ -84,7 +115,7 @@ function adder(x)
84115
end
85116
```
86117
87-
Note that the structure ##1 is not directly accessible.
118+
Note that the structure ##1 is not directly accessible. Try ```f.x``` and ```g.x```.
88119
89120
### Functor = Function-like structure
90121
Each structure can have a method that is invoked when called as a function.
@@ -111,7 +142,7 @@ function train!(loss, ps, data, opt; cb = () -> ())
111142
end
112143
end
113144
```
114-
Is this confusing? What can ```cb()``` do?
145+
Is this confusing? What can ```cb()``` do and what it can not?
115146
116147
Note that function ```train!``` does not have many local variables. The important ones are arguments, i.e. exist in the scope from which the function was invoked.
117148
@@ -135,12 +166,14 @@ Usage of closures:
135166
136167
## Design Patterns of OOP from the Julia viewpoint
137168
169+
OOP is currently very popular concept (C++, Java, Python). It has strenghts and weaknesses. The Julia authors tried to keep the strength and overcome weaknesses.
170+
138171
Key features of OOP:
139172
- Encapsulation
140173
- Inheritance
141174
- Polymorphism
142175
143-
Classical OOP languages define classes that bind processing functions to the data.
176+
Classical OOP languages define classes that bind processing functions to the data. Virtual methods are defined only for the attached methods of the classes.
144177
145178
!!! tip "Encapsulation"
146179
Refers to bundling of data with the methods that operate on that data, or the restricting of direct access to some of an object's components. Encapsulation is used to hide the values or state of a structured data object inside a class, preventing direct access to them by clients in a way that could expose hidden implementation details or violate state invariance maintained by the methods.
@@ -159,6 +192,7 @@ What if I create Grass with larger size than ```max_size```?
159192
```julia
160193
grass = Grass(1,50,5)
161194
```
195+
Freedom over Rules. Maybe I would prefer to introduce some rules.
162196
163197
Some encapsulation may be handy keeping it consistent. Julia has ```inner constructor```.
164198
```julia
@@ -169,6 +203,7 @@ mutable struct Grass <: Plant
169203
Grass(id,sz,msz) = sz > msz ? error("size can not be gerater that max_size") : new(id,sz,msz)
170204
end
171205
```
206+
When defined, Julia does not provide the default outer constructor.
172207
173208
But fields are still accessible:
174209
```julia
@@ -189,11 +224,12 @@ Julia has *partial encapsulation* via a mechanism for consistency checks.
189224

190225

191226
### Encapsulation Disadvantage: the Expression Problem
192-
Matrix of methods/types(data-structures)
227+
228+
Encapsulation limits the operations I can do with an object. Sometimes too much. Consider a matrix of methods/types(data-structures)
193229

194230
Consider an existing matrix of data and functions:
195231

196-
| data \ methods | find_food | eat! | | | |
232+
| data \ methods | find_food | eat! | grow! | | |
197233
| --- | --- | --- | --- | --- | --- |
198234
| Wolf | | | | | |
199235
| Sheep | | | | | |
@@ -233,26 +269,26 @@ https://stackoverflow.com/questions/39133424/how-to-create-a-single-dispatch-obj
233269
!!! tip "Polymorphism in OOP"
234270
Polymorphism is the method in an object-oriented programming language that performs different things as per the object’s class, which calls it. With Polymorphism, a message is sent to multiple class objects, and every object responds appropriately according to the properties of the class.
235271

236-
Example animals of different classes make different sounds:
272+
Example animals of different classes make different sounds. In Python:
237273
```python
238274
sheep.make_sound()
239275
wolf.make_sound()
240276
```
241-
Will make distinct sounds (baa, bark).
277+
Will make distinct sounds (baa, Howl).
242278

243279
Can we achieve this in Julia?
244280
```
245281
make_sound(s::Sheep)=println("Baa")
246-
make_sound(w::Wolf)=println("Bark")
282+
make_sound(w::Wolf)=println("Howl")
247283
```
248284

249-
Multiple dispatch is an extension of classical polymorphism of OOP, which is only single dispatch.
285+
Multiple dispatch is an *extension* of the classical polymorphism of OOP, which is only a single dispatch.
250286

251287
!!! tip "Implementation of virtual methods"
252288
Virtual methods in OOP are typically implemented using Virtual Method Table, one for each class.
253289
![](vtable.gif)
254290

255-
NotesDuck typing:
291+
*Freedom* vs. Rules.
256292
- julia does not check if ```make_sound``` exists for all animals. May result in MethodError. Responsibility of a programmer.
257293
- define ```make_sound(A::AbstractAnimal)```
258294
- Duck typing is a type of polymorphism without static types
@@ -273,7 +309,6 @@ meet(w1::Sheep, w2::Sheep)=
273309

274310

275311

276-
277312
## Inheritance
278313

279314
!!! tip "Inheritance"
@@ -326,21 +361,21 @@ The type hierarchy is only one way of subtyping. Julia allows many variations, e
326361
fancy_method(O::Union{Sheep,Grass})=println("Fancy")
327362
```
328363

329-
Is this a good idea? It can be done completely Ad-hoc!
364+
Is this a good idea? It can be done completely Ad-hoc! Freedom over Rules.
330365

331-
There are very good usecases:
366+
There are very good use-cases:
332367
- Missing values:
333368
```x::AbstractVector{<:Union{<:Number, Missing}}```
334369

335370
!!! theorem "SubTyping issues"
336-
With parametric types, unions and other construction, subtype resoltion may become a complicated problem. Julia can even crash.
371+
With parametric types, unions and other construction, subtype resolution may become a complicated problem. Julia can even crash.
337372
https://www.youtube.com/watch?v=LT4AP7CUMAw
338373

339374

340375
### Sharing of data field via composition
341376
Composition is also recommended in OOP: https://en.wikipedia.org/wiki/Composition_over_inheritance
342377

343-
```julia
378+
```julia
344379
struct ⚥Sheep <: Animal
345380
sheep::Sheep
346381
sex::Symbol
@@ -352,11 +387,11 @@ If we want our new ⚥Sheep to behave like the original Sheep, we need to *forwa
352387
```julia
353388
eat!(a::⚥Sheep, b::Grass, w::World)=eat!(a.sheep, b, w)
354389
```
355-
and all other methods. Boring!
390+
and all other methods. Routine work. Boring!
356391
The whole process can be automated using macros ```@forward``` from Lazy.jl.
357392

358393

359-
Why so complicated? Wasn't original tree structure better?
394+
Why so complicated? Wasn't the original inheritance tree structure better?
360395

361396
- multiple inheritance:
362397
- you just compose two different "trees".
@@ -376,9 +411,9 @@ Why so complicated? Wasn't original tree structure better?
376411

377412
Idea #2: Split by the way they move!
378413

379-
Idea #3: Split by way of child care (Feeding, Ignoring, Protecting)
414+
Idea #3: Split by way of ...
380415

381-
In fact we do not have a tree, but more like a matrix/tensor:
416+
In fact, we do not have a tree, but more like a matrix/tensor:
382417

383418
| | swims | flies | walks |
384419
| --- | --- | --- | --- |
@@ -405,55 +440,89 @@ end
405440

406441
Now, we can define methods dispatching on parameters of the main type.
407442

408-
## Interfaces
443+
Composition is simpler in such a general case. Composition over inheritance.
444+
445+
## Interfaces: inheritance/subtyping without a hierarchy tree
446+
447+
In OOP languages such as Java, interfaces have a dedicated keyword such taht compiler can check correctes of the interface implementation.
448+
449+
In Julia, interfaces can be achived by defining ordinary functions. Not so strict validation by the compiler as in other languages. Freedom...
450+
451+
### Example: Iterators
409452

410-
Just functions, often informal. Not so strict validation by compiler as in other languages.
453+
Many fundamental objects can be iterated: Arrays, Tuples, Data collections...
454+
- They do not have any common "predecessor". They are almost "primitive" types.
455+
- they share just the property of being iterable
456+
- we do not want to modify them in any way
411457

412458
Example: of interface ```Iterators```
459+
defined by "duck typing" via two functions.
413460
|Required methods | Brief description|
414461
| --- | --- |
415462
|iterate(iter) | Returns either a tuple of the first item and initial state or nothing if empty|
416463
|iterate(iter, state) | Returns either a tuple of the next item and next state or nothing if no items remain|
417464

418-
Definig these two methods for any object/collection ```C``` will make the following work:
465+
Defining these two methods for any object/collection ```C``` will make the following work:
419466
```julia
420467
for o in C
421468
# do something
422469
end
423470
```
424471

425-
## Traits: cherry picking subtyping
472+
- The compiler will not check if both functions exist.
473+
- If one is missing, it will complain about it when it needs it
474+
- The error message may be less informative than in the case of formal definition
426475

427-
When a decision if the Type can or cannot perform certain method is complicated, we can infer this by a function.
476+
Note:
477+
- even iterators may have different features: they can be finite or infinite
478+
- for finite iterators we can define useful functions (```collect```)
479+
- how to pass this information in an extensible way?
428480

429-
Example ```_AsList``` from PyTorch:
430-
```python
431-
def _AsList(x):
432-
return x if isinstance(x, (list, tuple)) else [x]
481+
Poor solution: if statements.
482+
```julia
483+
function collect(iter)
484+
if iter isa Tuple...
485+
486+
end
433487
```
434-
Hold on, how about ndarray? How to add it? What else to add?
488+
The compiler can do that for us.
489+
490+
## Traits: cherry picking subtyping
491+
492+
Trait mechanism in Julia is build using the existing tools: Type System and Multiple Dispatch.
435493

436-
Types can be computed by a function:
494+
Traits have a few key parts:
437495

496+
- Trait types: the different traits a type can have.
497+
- Trait function: what traits a type has.
498+
- Trait dispatch: using the traits.
499+
500+
From iterators:
438501
```julia
439-
# trait types
440-
struct List end
441-
struct Nonlist end
442-
443-
# trait function: input general Type, return trait Type
444-
islist(::Type{<:AbstractVector}) = List()
445-
islist(::Type{<:Tuple}) = List()
446-
islist(::Type{<:Number}) = Nonlist()
447-
448-
# Trait dispatch:
449-
function aslist(x::T) where T
450-
aslist(islist(T), x) # try to call it again with different function-inferred type
451-
end
452-
aslist(::List, x) = x
453-
aslist(::Nonlist, x) = [x]
502+
# trait types:
503+
504+
abstract type IteratorSize end
505+
struct SizeUnknown <: IteratorSize end
506+
struct HasLength <: IteratorSize end
507+
struct IsInfinite <: IteratorSize end
508+
509+
# Trait function: Input is a Type, output is a Type
510+
IteratorSize(::Type{<:Tuple}) = HasLength()
511+
IteratorSize(::Type) = HasLength() # HasLength is the default
512+
513+
# ...
514+
515+
# Trait dispatch
516+
BitArray(itr) = gen_bitarray(IteratorSize(itr), itr)
517+
gen_bitarray(isz::IteratorSize, itr) = gen_bitarray_from_itr(itr)
518+
gen_bitarray(::IsInfinite, itr) = throw(ArgumentError("infinite-size iterable used in BitArray constructor"))
519+
454520
```
521+
What is needed to define for a new type that I want to iterate over?
522+
523+
Do you still miss inheritance in the OOP style?
455524

456-
Many packages:
525+
Many packages automating this with more structure:
457526
- https://github.com/andyferris/Traitor.jl
458527
- https://github.com/mauro3/SimpleTraits.jl
459528
- https://github.com/tk3369/BinaryTraits.jl

0 commit comments

Comments
 (0)