Progblemet #4
Detta problem kommer förhoppningsvis vara lite enklare att begripa sig på jämfört med #3. Detta progblemet handlar om att visa hur olika språk hanterar polymorfism. Polymorfism är något de flesta kanske associerar med objektorientering, arv och liknande, men det är egentligen mycket mer generellt än så även om detta exempel inte visar detta.
Skriv ett program som på något sätt definierar ett interface/protokoll för ett djur. Vi har ett väldigt enkelt djur som bara har en metod make-sound, make_sound eller makeSound (beroende på vad som är standard i resp. språk). Räcker nog med två olika djur för att man ska se principen, hund och katt.
Katten/hunden har ett namn som ska sättas när man skapar djuret och namnet ska finnas med när djuret gör sitt ljud. Hundar skäller och katter jamar, så make-sound ska skriva ut ungefär detta för en hund
namn : voff
och detta för en katt
namn : mjao
Om det är relevant / möjligt i språket, gör även en demonstration i hur man verifierar att det värde man just nu håller i implementerar interfacet / protokollet. I vissa statiskt typade språk kanske det alltid leder till ett kompikeringsfel att anropa en metod på en typ som inte har metoden (t.ex. Haskell), då kan man bara skriva en kommentar att så är fallet.
För att demonstrera att detta inte kräver ett "objektorienterat" språk kommer här två möjliga lösningar i Clojure som inte är OO på det sätt som t.ex. Java, C# är.
Med hjälp av "defprotocol" och "deftype"
;; Detta definierar ett Java-interface "under huven" med namnet
;; "Animal" och en metod med namnet "make-sound"
(defprotocol Animal
(make-sound [this]))
;; Låt typen "Dog" implementerar interfacet "Animal"
(deftype Dog[name]
Animal
(make-sound [this] (println name ": voff")))
;; Låt typen "Cat" implementerar interfacet "Animal"
(deftype Cat[name]
Animal
(make-sound [this] (println name ": mjao")))
;; Skapa lite hundar och katter och säg åt dem att låta!
(doseq [animal [(Dog. "Killer") (Cat. "Tom") (Dog. "Lufsen") (Dog. "Lady")]]
(make-sound animal))
;; Clojure är ett starkt och dynamiskt typat språk, man kan därför
;; stoppa in saker av vilken typ som helst i listor. Vill man veta om
;; ett visst objekt implementerar ett visst protokoll så kan man göra
;; så här.
(doseq [maybe-animal ["not animal" (Cat. "Bill")]]
(if (satisfies? Animal maybe-animal) (make-sound maybe-animal)))
Och här är ett annat sätt att lösa samma problem i Clojure med s.k. "multimethods" och explicita arvshierarkier. Själv skulle jag nog fördra att lösa problemet på detta sätt i Clojure.
(defmulti make-sound :type)
(defmethod make-sound ::dog [this] (println (:name this) ": voff"))
(defmethod make-sound ::cat [this] (println (:name this) ": mjao"))
;; Här är "klasserna" representerad rakt upp och ner med hjälp av hash-maps
;; Namnet är sparat under nyckeln ":name" och typen under nyckeln ":type"
(defn new-dog [name] {:type ::dog :name name})
(defn new-cat [name] {:type ::cat :name name})
(doseq [animal [(new-dog "Killer") (new-cat "Tom") (new-dog "Lufsen") (new-dog "Lady")]]
(make-sound animal))
;; Går att ha en metod som tar hand om alla typer man inte känner
;; igen.
(defmethod make-sound :default [this] (println "inte en katt eller hund"))
(doseq [maybe-animal ["not animal" (new-cat "Bill")]]
(make-sound maybe-animal))
;; Det går också att skapa relatationer mellan atomer (det som börjar
;; med kolon i Lisp/Clojure är en "atom" som är en form av konstant
;; utan värde). Man kan sedan frågan om 'x' är relaterad till 'y' på
;; följande sätt.
(derive ::dog ::animal) ;; ::dog är ett barn till ::animal
(derive ::cat ::animal) ;; ::cat är ett bart till ::animal
(doseq [maybe-animal ["not animal" (new-cat "Bill")]]
(if (isa? (:type maybe-animal) ::animal) (make-sound maybe-animal)))
Multimethods är mycket mer generella än detta, man kan även använda värdet på argument som del i valet över vilken metod som anropas, något som även funktionella språk brukar stödja via s.k. pattern-matching. Kanske något för ett framtida "progblemet".
Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer