Философский этюд
Jocker 2022
CTO at Health Samurai @niquola - github, telegram, twitter
- HL7 FHIR
- Clojure
- Postgres
- DevOps
Не люблю доклады! Люблю общаться! Спрашивайте походу!
Основная польза от конференций в разговорах с инетересными людьми в кулуарах!
Почему не вся конференция не кулуары? Выступайте - попадете в кулуары!
- Academy - PHD in radio-pharmacy
- Late in IT at 25y
- Теория? :(
- Как правильно программировать?
Рефлексия последних 10 лет
Гипотеза лингвистической относительности: Язык определяет мышление!
My hx:
- PHP, VB ! fun
- Java/C# ! hard
- SQL ! declarative
- Ruby ! dsls
- Clojure ! data & fp
Чему меня научила clojure? Может будет полезно и вам!
two_plus_two() {
return 2 + 2;
}
three_plus_three () {}
four_plus_four () {}
// parameterize, generalize!
dobule(x) { return x + x; }
// even more general
op(op, x) { return x op x; }
class Patient {
private string id;
private string name;
private Date birthDate;
//other fields
}
save_patient(Patient pt){
validate(pt);
String cmd = "INSERT INTO patient(name,birthdate) VALUES(?,?) returning *";
PreparedStatement stmt = conn.prepareStatement(cmd, ...)
stmt.setString(1, pt.getName());
stmt.setString(2, pt.getBirthDate());
ResultSet rs = stmt.executeQuery();
// get generated id
pt.setId(rs.getString(0));
return pt;
}
class Practitioner { ... }
save_practitioner(Practitioner pt){
// SAME S....
// BOILERPLATE?
}
- Reflection?
- Annotations?
- So complicated! WTF?
OOP was invented because hash-map was late!
{:type "Patient"
:name "Nikilai"
:birth_date "1980-03-05"}
(assoc map :key "value")
(get map :key)
(dissoc map :key)
(for [[k v] map] ...)
- keys -> column names
- type attribute -> table name
- concat insert command
- populate values
- execute
(defn save [conn res]
(let [tbl (:type res)]
(loop [cols [] vals [] params []
[[k v] & kvs] res]
(if (nil? k)
(let [st (jdbc/prepared conn (str "insert into " tbl " (" cols ") VALUES (" vals ")"))
res (.executeQuery st)]
(assoc res :id ()))
(recur (conj cols (nake k))
(conj vals "?")
(conj params v)
kvs)))))
(save conn
{:type "Patient"
:name "Nikilai"
:birth_date "1980-03-05"})
(save {:type "Practitioner"
:name "Nikilai"
:birth_date "1980-03-05"})
> Is it better to have 100 functions operate on one data structure > than 10 functions on 10 data structures?
Alan Perlis’ Epigrams on Programming (1982)
Write more generic code!
- Program with data & functions!
- 100 functions
class Repo {
private Logger logger;
private Connection conn;
//other fields
String doTheJob() { ... }
}
Repo r = new Repo();
r.setLogger(..)
r.setConnection(..)
// or constructor?
r.doTheJob(..)
Accidental complexity
- lifecycle
- deps
- extra design choices (constructor, setters, …)
=> coupling (спутаность) => less reuse
Pass it as params!
doTheJob (Logger logger, Connection connection) { ... }
(defn do-the-job [ctx params] ...)
;; decomplected! less choices
(do-the-job {:connection conn-1 :logger logger-config-1})
(do-the-job {:connection conn-2 :logger logger-config-2})
Была ломка - но прошла
- easy reuse
- easy test
(def ctx
{:db db-pool
:http {:server server}})
(do-the-job {:connection conn-1 :logger logger-config-1} params)
(do-the-job {:connection conn-2 :logger logger-config-2} params)
Let’s get back to Patient CRUD
- create table
- serialize/deserialize json
- CRUD methods
- Search
- Docs
Repeat our self!
- Oh, we can define annotations.
- Then extract it and generate something!
create_table :patient do |t|
t.string :name
t.date :birthdate
t.reference :organization
t.timestamps
end
class Patient < ApplicationRecord
belongs_to :organization
end
Why not just encode it as data
models
{Patient
{:fields {:name {:type string :required true}
:birthDate {:type dateTiem}}}
Practitioner
{:fields {:name {:type string :required true}
:birthDate {:type dateTiem}}}}
- generate tables, classes, docs
- use as params in generic functions
- validation
- persistence
- etc
- pass over the network!
- what ever interpretation!
- json
- yaml
- edn!
;;primitives
1 100.0 "string" true
;; special strings
:keyword ;; special string to be the key in a map
symbol ;; sepcial string to name the functions
;; map
{:key "value"
:vector [1 2 3]
:set #{}
:symbol Patient
:date #inst"2022-01-01"}
This is magic that you can extract part of algorithm into data structure.
Everything can be expressed as a data!
- http server routing
- access policies
- transformations
- workflows
(def routes
{:GET 'root
"files" {:path* {:GET 'file}}
"users" {:GET 'list
:POST 'create
[:uid] {:GET 'show
:PUT 'udpate
:DELETE 'destroy}}})
(assoc
{:select :*
:from :Patient
:where [:= :id "pt-1"]}
:limit 100)
(plus 1 1) => [“plus”, 1, 1]
#macro system
data -> f -> data
You program is just a set of functions transforming data!
- parse http request into map
- find route in a map
- build sql as map
- execute
- transform
- build http request string
system = meta-data (models) + generic interpreters
To refer one model from another we need to **names**! Use symbol for names!
{ns myapp
import #{lib}
patient
{:zen/tags #{zen/schema}
:keys {}}
create-pt
{:zen/tags #{lib/http-op}
:schema patient
:route [:GET "/patient"]}}
- Introspection
- Configurable
- Extensible
- Reusable
zen-lang (https://github.com/zen-lang/zen)
- Define your Models/DSLs
- Compose DSLs with refs
- Create model modules (aka libs)
- Reuse generic LSP
- Hard-coded (specific use case)
- Customizable (group of use cases)
- Platform (domain of use cases)
Allow the user to program your system at the end!
@niquola tg, tw, gh