# Topic 30 - 31 ###### tags: `The Pragmatic Programmer` ## Topic 30 Transforming Programming ### intro and example - When we think about design, we rarely think about **creating transformations**. Instead we worry about classes and modules, data structures and algorithms, languages and frameworks. - 1970s example: write a program that lists the five **longest (having the largest number of lines)** files in a directory tree. ### solved `$ find . -type f | xargs wc -l | sort -n | tail -6 | head -5` > `find . -type f` > > Write a list of all the files (-type f) in or below the current directory (.) to standard output. > 將目前目錄(.)中或以下的所有檔案(-type f)寫入標準輸出 > `xargs wc -l` > > Read lines from standard input and arrange for them all to be passed as arguments to the command wc -l. > The wc program with the -l option counts the number of lines in each of its arguments and writes each result as “countfilename” to standard output. > 從標準輸出中讀取多行 並將它們全部作為參數傳遞給命令 wc -l > 帶有 -l選項的wc程式會計算每個參數中的行數 > 並將每個結果以行數檔案名稱的格式 寫入標準輸出 > `sort -n` > > Sort standard input assuming each line starts with anumber (-n), writing the result to standard output. > 假設標準輸入每行都以數字(-n)開頭 進行排序 > 並將結果寫入標準輸出 > `tail -5` > > Read standard input and write just the last five lines tostandard output. > 讀取標準輸入 並將最後五行寫入標準輸出 > > ![](https://i.imgur.com/KXz6HBS.png) > 修正為 `tail -6 | head -5` > > last line : the total number of lines in all the files > We can strip last line off by requesting one more line from tail, and then ignoring the lastline: > > ![](https://i.imgur.com/sWUjiLu.png) :::info **This is a series of transformations:** directory name → list of files → list with line numbers → sorted list → highest five + total → highest five ::: ![](https://i.imgur.com/2j1PUGL.png) ### intro conclusion - A series of transformations like **an industrial assembly line**: feed raw data in one end and the finished product (information) comes out the other. - And we like to think about all code this way. ### main topic > **Programming Is About Code, But Programs Are About Data** - the easiest way to find the transformations is to start with the **requirement** and determine its inputs and outputs. - This is a top-down approach. (從上往下建構的方法) example: word games input: a set of letters output: is a list of three-letter words, four-letter words, or five-letter words ![](https://i.imgur.com/RjzmHJg.png) :::info The anagram finder breaks down into four separate transformations: step 0 : Initial input step 1 : All combinations of three or more letters step 2 : Signatures of the combinations step 3 : List of all dictionary words which match any of the signatures step 4 : Words grouped by length ::: ### Step 1 拆解 ![](https://i.imgur.com/8FdRcDy.png) ### using Elixir in this case ``` defp all_subsets_longer_than_three_characters(word) do word |> String.codepoints() |> Comb.subsets() |> Stream.filter(fn subset -> length(subset) >= 3 end) |> Stream.map(&List.to_string(&1)) end ``` :::info **What’s with the |> Operator?** **管道運算子** - name: pipeline operator (or forward pipe or pipe) - func: All it does is **take the value on its left and insert it as the first parameter of the function on its right** `arg |> func` is equivalent to `func(arg)` ![](https://i.imgur.com/T5n4grP.png) - pros: (1) more than just syntactic sugar (automatically thinking in terms of transforming data) : each time you see |> you’re actually seeing a place where data is flowing between one transformation and the next. (2) greatly improves the readability of our code. **Language X Doesn't Have Pipelines** - Pipelines have been around for a long time, but only in niche languages (Elm, F#, Swift, Clojure, R and Haskell) - Thinking in transformations doesn’t require a particular language syntax: it’s more a philosophy of design. - Write them as a series of assignments (寫成一系列賦值的動作): ``` const content = File.read(file_name); const lines = find_matching_lines(content, pattern) const result = truncate_lines(lines) ``` ### conclusion - It's not an official JavaScript member yet(still in stage 2 ). - Babel plugin, which will let us use pipeline operators right now before the pipelines reach stage 3 or 4. - create our own piping function ::: ### Step 2 拆解 : convert the subsets into signatures ![](https://i.imgur.com/WIR9QKd.png) ``` defp as_unique_signatures(subsets) do subsets |> Stream.map(&Dictionary.signature_of/1) end ``` ### Step 3 拆解 : remove the nils and flatten the nested lists into a single level each signature gets mapped to the list of known words with the same signature, or nil if there are no such words ``` defp find_in_dictionary(signatures) do signatures |> Stream.map(&Dictionary.lookup_by_signature/1) |> Stream.reject(&is_nil/1) |> Stream.concat(&(&1)) end ``` ### Step 4 拆解 : grouping the words by length ``` defp group_by_length(words) do words |> Enum.sort() |> Enum.group_by(&String.length/1) end ``` ### Final Step : Putting It All Together ``` def anagrams_in(word) do word |> all_subsets_longer_than_three_characters() |> as_unique_signatures() |> find_in_dictionary() |> group_by_length() end ``` ### conclusion:WHY IS THIS SO GREAT? :::info if you are a object-oriented programmer : hide data --> encapsulating(封裝) it inside objects --> a lot of coupling --> OO systems be hard to change ::: > **Don’t Hoard State; Pass It Around** * Instead of little pools of data spread all over the system, think of data as a mighty river, a flow. * The data is no longer tied to a particular group of functions, as it is in a class definition * we can greatly reduce coupling: a function can be used (and reused) anywhere its parameters match the output of some other function. * there is still a degree of coupling, but in our experience it’s more manageable than the OO-style of command and control. **ERROR HANDLING?** - **basic convention: we never pass raw values between transformations**, Instead, we wrap them in a data structure (or type) which also tells us if the contained value is valid. - How you use this concept is language specific. **First, Choose a Representation** We need a representation for our **wrapper** (the data structure that carries around a value or an error indication **攜帶值或錯誤指示的資料結構**). ``` iex(1)> File.open("/etc/passwd") {:ok, #PID<0.109.0>} iex(2)> File.open("/etc/wombat") {:error, :enoent} ``` **Elixir for example:** - functions tend to return a tuple containing either {:ok, value} or {:error, reason} - use the :ok/:error tuple as our wrapper when passing things through a pipeline **Then Handle It Inside Each Transformation** Q: Let’s write a function that returns all the lines in a file that contain a given string, truncated to the first 20 characters. * input: a file name and a string to match * output: either an :ok tuple with a list of lines or an :error tuple with some kind of reason. ``` def find_all(file_name, pattern) do File.read(file_name) |> find_matching_lines(pattern) |> truncate_lines() end ``` **With error checking by Elixir’s pattern matching** ``` defp find_matching_lines({:ok, content}, pattern) do content |> String.split(~r/\n/) |> Enum.filter(&String.match?(&1, pattern)) |> ok_unless_empty() end defp find_matching_lines(error, _), do: error # ---------- defp truncate_lines({ :ok, lines }) do lines |> Enum.map(&String.slice(&1, 0, 20)) |> ok() end defp truncate_lines(error), do: error # ---------- defp ok_unless_empty([]), do: error("nothing found") defp ok_unless_empty(result), do: ok(result) defp ok(result), do: { :ok, result } defp error(reason), do: { :error, reason } ``` ### Final conclusion * Thinking of transforming programming can be **a liberating approach to programming.** * It takes a while to get used to, but once you’ve developed the habit you’ll find your code becomes cleaner, shorter, and flatter. ## Topic 31 Inheritance Tax ### intro and history - Do you program in an object oriented language? Do you use inheritance? If so, stop! It probably isn’t what you want to do. > You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. - Inheritance first appeared in Simula 67 in 1969. - **prefix classes** : the `link` is a prefix class that adds the functionality of linked lists. => `link` would be a parent class(父類別). ``` link CLASS car; ... implementation of car link CLASS bicycle; ... implementation of bicycle ``` The `link` part was viewed as being a container (a form of polymorphism) : cars and bicycles both implemented the `link` interface because they both contained the `link` code. - After Simula came Smalltalk. - Simula (inheritance was a way of combining types) => C++ and Java - Smalltalk (inheritance was a dynamic organization of behaviors) => as Ruby and JavaScript ### intro conclusion - now OO developers use inheritance for one of two reasons: 1. who don’t like typing => add common functionality from a base class(基礎類別) into child classes(子類別) 2. who like types => express the relationship between classes: a Car is-a-kind-of Vehicle. ### PROBLEMS USING INHERITANCE TO SHARE CODE > Inheritance is coupling. ![](https://i.imgur.com/JBG0dUQ.png) - example flow 1. calls my_car.move_at 2. invoked method: the parent of Car(Vehicle) 3. if API changes: move_at becomes set_velocity, and the instance variable @speed becomes @velocity. 4. expected to break clients of Vehicle class. But the top-level is not (using a Car) => it still breaks - inheritance's pros and cons 1. is a way of defining new types => **the multiple inheritance(多重繼承)**:A Car maybe a kind of Vehicle, Asset, InsuredItem(保險標的), LoanCollateral(擔保品) and so on. (**most current OO languages don’t offer multiple inheritance**) ![](https://i.imgur.com/R3HxYdw.png) 2. design diagram shows class hierarchies(類別的層次結構) => grow into wall-covering monstrosities => make the application more brittle ![](https://i.imgur.com/rsstSAE.png) ### Don’t Pay Inheritance Taxt - three techniques that mean you never need to use inheritance again: **1. Interfaces and Protocols**(介面和協定) ![](https://i.imgur.com/8g9tO3E.png) - 用Class Car實作drivable, locatable的行為 - Drivable and Locatable are what Java calls interfaces; other languages call them protocols - Interfaces are defined like this: ![](https://i.imgur.com/7xC5N1W.png) - any class that implements Drivable must implement the two methods getSpeed and stop, and a class that’s Locatable must implement getLocation and locationIsValid. => Car will only be valid if it includes all four of these methods - we can use interfaces as types => If Car and Phone both implement Locatable, we could store both in a list of locatable items: ![](https://i.imgur.com/L1T20Rc.png) ![](https://i.imgur.com/EisypOq.png) > Prefer Interfaces to Express Polymorphism - Interfaces and protocols give us polymorphism without inheritance **2. Delegation**(委派) - Inheritance => create classes => objects have large numbers of methods => If a parent class has 20 methods => subclass wants to make use of just two => other 18 callable methods ![](https://i.imgur.com/0JYqDcB.png) - The Account class carries all of the persistence class’s API around with it. - an alternative using delegation: ![](https://i.imgur.com/SPTCvTa.png) - now expose none of the framework API to the clients of our Account class :::info Delegation v.s. Inheritance - 繼承的主角是父類,子類繼承父類方法;委派(無子父類關係)主角為委派方,將自己的一部分對象委派給另一個方法執行 - 子類只需要複用父類中部分方法時 => 使用委派 (子類不需繼承其父類中對其無用的方法) - 委派方無法更改被委派方的代碼,繼承的子類可以重寫父類的方法 ::: **3. Mixins and traits**(混合和特性) > merging functionality between existing things and new things 合併現有內容和新內容 - we want to extend classes and objects with new functionality without using inheritance. => create a set of these functions(函式集合), give that set a name, and then somehow extend a class or object with them. - a new class or object that combines the capabilities of the original and all its mixins. ![](https://i.imgur.com/3kgoZNz.png) :::info - 實現多重繼承的一個技巧 - 在層次上具有單一繼承一樣的樹結構,同時又可以實現功能的共享(方法是:把共享的功能放在Mixin 類中,再把Mixin 類插入到樹結構裡 ::: ## reference [pipeline-operator](https://cloudemployee.co.uk/blog/code/all-you-need-to-know-about-pipeline-operator-and-piping-in-javascript) [Delegation and Inheritance](https://blog.csdn.net/weixin_44160014/article/details/107209315?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-107209315-blog-45510325.pc_relevant_recovery_v2&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-107209315-blog-45510325.pc_relevant_recovery_v2&utm_relevant_index=1e)