Skip to content

Instantly share code, notes, and snippets.

@tatut
Created April 11, 2017 05:27
Show Gist options
  • Save tatut/00b7d9a08550ef25d651906ecdcedc88 to your computer and use it in GitHub Desktop.
Save tatut/00b7d9a08550ef25d651906ecdcedc88 to your computer and use it in GitHub Desktop.
(ns m
(:require [clojure.java.io :as io]
[clojure.pprint :refer [*print-suppress-namespaces* pprint]]
[clojure.repl :refer [doc source]]))
;; Utility ennen/jälkeen tulosteille
(defmacro m [form]
(binding [*print-suppress-namespaces* true]
(println "\n----ENNEN:" )
(pprint form)
(println "----JÄLKEEN:")
(pprint (macroexpand-1 form))))
;; Mikä on makro?
;; Ohjelma, joka ajetaan käännösaikana.
;; Ottaa sisään parametrit (joita ei ole evaluoitu)
;; Tuottaa lähdekoodin, jolla kääntäjä korvaa makrokutsun
;; Voidaan käyttää esim. source transformaatioon
;; Yksinkertainen esimerkki
(defmacro lisaa-yksi [x]
`(do
;; risuaita tekee uuden nimen, jota voidaan käyttää turvallisesti
;; ~x on unquote, joka korvaa kohdan tuloksessa x:llä
(let [x# ~x]
(println "Hei, lisää yhden arvoon " x#)
(let [tulos# (+ x# 1)]
(println "Saatiin arvo " tulos#)
tulos#))))
(lisaa-yksi 1)
(def x 665)
(lisaa-yksi x)
;; nimen x käyttö on turvallista eikä sekoitu
(m (lisaa-yksi x))
;; Mitä jos halutaan tarkoituksella kaapata sama nimi?
;; Mahdollista, mutta kääntäjä estää sen vahingossa tekemisen
(defmacro ei-toimi [foo]
`(let [tulos (* ~foo 2)]
tulos))
(ei-toimi 123)
(m (ei-toimi 123))
;; unquote/quote yhdistelmällä se onnistuu
(defmacro toimii [foo]
`(let [~'tulos (* ~foo)]
~'tulos))
(toimii 123)
(m (toimii 123))
;; with- makrot
;; yleensä tekevät jotain ympäristön alustusta, ajavat käyttäjän antaman koodin ja
;; lopuksi tekevät ympäristön siivoamisen
(with-open [f (io/reader "https://google.com")]
(println (take 1 (line-seq f))))
(m (with-open [foo bar] baz))
;; def- makrot
;; yleensä ottavat nimen ja määrittelyn ja määrittelevät annetun nimisen
;; nimen nimiavaruuteen
(defmulti foo :type)
(m (defmulti foo :type))
(m (defmethod foo :bar [_] (println "Baz!")))
;; Defrecord luo uuden tyypin sekä konstruktoreita
(defrecord Quux [zoo])
(->Quux 123)
(map->Quux {:zoo 666})
(m (defrecord Quux [zoo]))
;; Makrot voivat tehdä mitä vaan, joten ne eivät rajoitu annetun lähdekoodin
;; muokkaamiseen, vaan voivat lukea mitä vain.
;; esim Harjassa, "luetaan lähdekoodia"
;; - excel tiedostosta
;; - json skeematiedostoista
;; - tietokannasta
;; esimerkki 1: excel tiedosto
(require '[harja.domain.oikeudet.makrot :refer [maarittele-oikeudet!]])
(m (maarittele-oikeudet!))
;; esimerkki 2: json skeematiedostoista
(require '[webjure.json-schema.validator.macro :refer [make-validator]])
(require '[cheshire.core :as cheshire])
(def skeema (cheshire/parse-string (slurp "skeema.json")))
(def v (make-validator skeema {}))
(pprint (v (cheshire/parse-string
"{\"address\": {\"city\": \"Oulu\", \"streetAddress\": \"Torikatu 18\"}, \"phoneNumber\": [-1]}")))
(pprint (v 123))
;; Generoitu koodi on pitkä
(m (make-validator skeema {}))
;; esimerkki 3: tietokannasta
(require '[specql.core :refer [define-tables]])
(require '[harja.domain.tierekisteri :as tr])
(require '[harja.domain.urakka :as urakka])
(m (define-tables {:connection-uri "jdbc:postgresql://localhost/harjatest_template?user=postgres"}
["tr_osoite" ::tr/osoite]
["urakkatyyppi" ::urakka/urakkatyyppi]))
;; lisää hassuja makroja
(def *sarcasm* true)
;; ärsyttääkö, että let vaatii "turhat" sulut, ei hätää! korjaa se itse
(defmacro lets [& bindings-ja-form]
`(let [~@(butlast bindings-ja-form)]
~(last bindings-ja-form)))
(lets
x 1 y 2
(+ x y))
(m (lets
x 1 y 2
(+ x y)))
;; onko rajapinta epävakaa, ei se mitään yritä uudestaan vaan!
(defn tryhard* [how-hard form on-failure]
(let [ex (gensym "EX")]
`(try
~form
(catch Throwable ~ex
~(if (zero? how-hard)
`(~on-failure ~ex)
(tryhard* (dec how-hard) form on-failure))))))
(defmacro tryhard [how-hard form on-failure]
(tryhard* how-hard form on-failure))
(m (tryhard 4 (/ 10 0) (fn [ex]
(println "ei onnistu: " ex))))
(tryhard 5
(let [luku (Math/random)]
(if (> luku 0.2)
(throw (RuntimeException. "ei kelepaa"))
luku))
(fn [ex]
(println "ei saatu sopivaa lukua!")))
;; huom: ylempi olisi parempi kirjoittaa siten, että "yritys" on
;; funktio, eikä expandoida samaa koodia n kertaa
;; core.async
(require '[clojure.core.async :as async :refer [go <! >! chan timeout]])
;; go on massiivisen monimutkaisen makro, joka on käytännössä clojure compiler
;; muuntaa normaalin clojure koodin tilakoneeksi, joka ei ole ajossa kun se
;; odottaa kanavaoperaatioita
(async/<!! (go
(println "odotellaan sekunti")
(<! (timeout 1000))
(println "odoteltiin")
(System/currentTimeMillis)))
(m (go
(println "odotellaan sekunti")
(<! (timeout 1000))
(println "odoteltiin")
(System/currentTimeMillis)))
(defn printtaa [x]
(println "X ON " x))
(with-out-str
(println "FOO")
(future (printtaa 42))
(println "BAR"))
(m (and 1 2))
(defn ja [& arvot]
(every? identity arvot))
(def x 42)
(m (cond
(even? x) :on-se
(odd? x) :ei-se-oo
:default :jotain-outoa))
(defmacro foo [& args]
(let [{:keys [nimi maarittely]} (s/conform ::foo args)]
(assert nimi "virheilmoitus")
`(do ...koodi...)))
(deftest mun-makro
(is (thrown-with-msg?
AssertionError #"virhe"
(= '(foo) (macroexpand '(mun-makro-kutsu 1 2 3))))))
(defmacro mittaa-aika [& body]
`(let [alku# (System/currentTimeMillis)]
(try
(if (< (Math/random) 0.1)
(throw (RuntimeException. "ei onnistu")))
~@body
(finally
(println "KESTI: " (- (System/currentTimeMillis) alku#))))))
(defmacro oma+ [x y]
(list '+ x y))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment