goodbye crud hello maps! datenmodellierung in … · datenmodellierung in clojure michael sperber...
Post on 17-Sep-2018
215 Views
Preview:
TRANSCRIPT
Goodbye CRUD Hello Maps! Datenmodellierung in Clojure
Michael Sperber@sperbsen
• Individualsoftware• branchenunabhängig• Scala, Clojure, Erlang, Haskell, F#, Swift, Elixir,
OCaml• Schulungen, Coaching
www.active-group.defunktionale-programmierung.de
Objekte kapselnveränderlichen Zustand
public class Person {private Long id;private String firstName;private String lastName;private String street;private String suburb;private String state;private String postCode;private int version;private Set<Phone> phones = new HashSet<Phone>();
public void addPhone(Phone phone) {if (!getPhones().contains(phone)) {
getPhones().add(phone);}
}...
}
Getter & Setterpublic String getFirstName() {
return firstName;}
public void setFirstName(String firstName) {this.firstName = firstName;
}
public String getLastName() {return lastName;
}
public void setLastName(String lastName) {this.lastName = lastName;
}
Gleichheit@Overridepublic boolean equals(Object o) {
if (this == o)return true;
if (o == null || getClass() != o.getClass())return false;
Person person = (Person) o;if (firstName != null ?
!firstName.equals(person.firstName): person.firstName != null)
return false;...
}
Hash@Overridepublic int hashCode() {int result = id != null ? id.hashCode() : 0;
result = 31 * result + (firstName != null ? firstName.hashCode() : 0);result = 31 * result + (lastName != null ? lastName.hashCode() : 0);result = 31 * result + (street != null ? street.hashCode() : 0);result = 31 * result + (suburb != null ? suburb.hashCode() : 0);result = 31 * result + (state != null ? state.hashCode() : 0);result = 31 * result + (postCode != null ? postCode.hashCode() : 0);result = 31 * result + (phones != null ? phones.hashCode() : 0);return result;
}
„Value Objects“
Warum?• als Schlüssel verwendbar• thread-sicher• einfache Unit-Tests• kein Copy-Konstruktor
keine clone-Methode
Eine Welt von Objekten
(© Trustees of the British Museum)
Imperative Programmierung
room1.exit(elephant)hallway.enter(elephant)hallway.exit(elephant)room2.enter(elephant)
Realität und Schnappschuss
OOP vs. Zustand„Though OOP came from many motivations, two were central. ... [T]he small scale one was to find a more flexible version of assignment, and then to try to eliminate it altogether. “
Alan Kay, History of Smalltalk (1993)
Clojure(ns crud.core(:require[active.clojure.record :refer :all]))
(define-record-type Dillo(make-dillo alive? weight)dillo?[alive? dillo-alive?weight dillo-weight])
Konstruktor
Prädikat
Selektor
Konstruktor, Prädikat, Selektoren(def d1 (make-dillo true 10))=> #crud.core.Dillo{:alive? true, :weight 10}
(dillo? d1)=> true(dillo? 15)=> false
(dillo-alive? d1)=> true(dillo-weight d1)=> 10
Records(define-record-type Rattlesnake(make-rattlesnake thickness length)rattlesnake?[thickness rattlesnake-thicknesslength rattlesnake-length])
(define-record-type Mouse(make-mouse blood size)mouse?[blood mouse-bloodsize mouse-size])
Konstruktion(def h1
[(make-dillo true 10)(make-rattlesnake 5 100)(make-mouse 5 17)(make-rattlesnake 7 200)])
Funktion(defn run-over [a]
(cond(dillo? a)(make-dillo false (dillo-weight a))
(rattlesnake? a)(make-rattlesnake 0 (rattlesnake-length a))
(mouse? a)(make-mouse 0 (mouse-size a))))
Erweitern auf Listen(defn run-over-all [a]
(map run-over a))
Adressbuch(define-record-type Person(make-person first last addresses)person?[first person-get-firstlast person-get-lastaddresses person-get-addresses])
Beispiele(def mike-sperber
(make-person "Mike" "Sperber"{:home "Pappelweg":work "Hechinger Straße"}))
(def sabine-ferber(make-person "Sabine" "Ferber"
{:home "Pappelweg":work "Schwanenstraße"}))
Maps(def addresses
{:home "Pappelweg":work "Hechinger Straße"})
(assoc addresses :home "Lerchenweg")=> {:home "Lerchenweg",
:work "Hechinger Straße"}addresses=> {:home "Pappelweg",
:work "Hechinger Straße"}
Maps in Clojure
Sharing
Hochzeit(defn marry
[person-1 person-2](make-person
(person-get-first person-1)(person-get-last person-2)(person-get-addresses person-1)))
Hochzeit(marry mike-sperber
sabine-ferber)=>#crud.address_book.Person{:first "Mike",:last "Ferber",:addresses{:home "Pappelweg“:work "Hechinger Straße"}}
Umzug(defn move[person key to](make-person(person-get-first person)(person-get-last person)(assoc(person-get-addresses person) key to)))
Umzug(move mike-sperber
:home "Lerchenweg")=>#crud.address_book.Person{:first "Mike", :last "Sperber",:addresses{:home "Lerchenweg“:work "Hechinger Straße"}}
Linsen(ns crud.address-book(:require[active.clojure.record :refer :all][active.clojure.lens :as lens]))
Linse
Getter+
Setter
Personen mit Linsen(define-record-type Person(make-person first last addresses)person?[(first person-get-first
person-first)(last person-get-last
person-last)(addresses person-get-addresses
person-addresses)])
Linsen(lens/yank mike-sperber person-first)=> "Mike“(lens/shove mike-sperber person-last
“Sperber") =>#crud.address_book.Person{:first "Mike",:last “Sperber",:addresses {:home "Pappelweg“
:work "Hechinger Straße"}}
Hochzeit(defn marry
[person-1 person-2](lens/shove person-1 person-last
(lens/yank person-2 person-last)))
Umzug(defn move [person key to]
(lens/overhaul personperson-addresses(fn [old]
(lens/shove old key to))))
Linsen auf Maps(def addresses{:home "Pappelweg":work "Hechinger Straße"})
(lens/yank addresses :home)=> "Pappelweg"
(lens/shove addresses :home "Lerchenweg")=> {:home "Lerchenweg",
:work "Hechinger Straße"}
Linsen komponieren(defn move [person key to]
(lens/shove person(lens/>> person-addresses key)to))
Komposition(defn comb-yank [data l1 l2]
(yank (yank data l1) l2))(defn comb-shove [data v l1 l2]
(shove data l1(shove (yank data l1) l2 v)))
(defn >>[l1 l2](lens comb-yank comb-shove l1 l2))
Unveränderliche Daten in Java
„Machste Daten, veränderste einfach nicht.“(Dierk König)
Linsen in Javapublic class Lens<A, B> {
private final Function<A, B> getter;private final BiFunction<A, B, A> setter;
public Lens(Function<A, B> getter,BiFunction<A, B, A> setter) {
this.getter = getter;this.setter = setter;
}...
}
Linsen in Javapublic B yank(A target) {
return getter.apply(target);}
public A shove(A target, B value) {return setter.apply(target, value);
}
Linsen in Javapublic A overhaul(A oldValue,
Function<B, B> mapper) {B extracted = getter.apply(oldValue);B transformed = mapper.apply(extracted);return setter.apply(oldValue,
transformed);}
Kompositionpublic <C> Lens<A, C> compose(Lens<B, C> other) {
return new Lens<>((A a) ->
other.getter.apply(getter.apply(a)),(A a, C c) -> {
B b = getter.apply(a);B newB = other.set(b, c);return setter.apply(a, newB);
});
}
„Datenbanken“
Quelle: tma.vn
Fakten vs. Zustand
27.1.2015: Eva -> Hans, 100€28.1.2015: Eva -> Fritz, 200€
29.1.2015: Hans -> Fritz, 100€...
Hans27.1.2015: 100€28.1.2015: 100€
29.1.2015: 0€...
Eva27.1.2015: -100€28.1.2015: -300€29.1.2015: -300€
...
Fritz27.1.2015: 0€
28.1.2015: 200€29.1.2015: 300€
...
Rohdaten abgeleiteteDaten
Hans
0€
Eva
-300€
Fritz
300€
Zustand/Cache
Datenbasis als Faktenspeicher
Fakten Veränderung = mehr Fakten
„Vera Mustermann hat Hans Exemplar am 21.5. 100€ überwiesen.“
„Vera Mustermanns Kontostand ist 1000€.“
Datomichttp://www.datomic.com/rationale.html
Unveränderliche Datenbanken• transaktionssicher• immer konsistent• Caching überall• Verteilung einfach• Synchronisation
Zusammenfassung• unveränderlich ist besser• einfach mit Funktionen höherer Ordnung• z.B. mit Linsen• geht auch in Java
top related