Created
March 19, 2018 17:07
-
-
Save gfredericks/358c39478f104281a6364f446f5b3c6b to your computer and use it in GitHub Desktop.
Half a draft of bijections in clojure
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns user.bijections | |
(:refer-clojure :exclude [-> comp update]) | |
(:require [clojure.core :as core] | |
[clojure.set :as set]) | |
(:import (java.util UUID))) | |
;; | |
;; Motivation: | |
;; | |
;; - avoid the edge cases inherent in allowing multiple | |
;; representations of things | |
;; - push that to the edge when it's unavoidable | |
;; - avoid the downsides of having global type-based extension | |
;; points for libraries (e.g., print-method, clojure.java.jdbc | |
;; reads and writes, json libraries...) | |
;; - avoid the downsides of blind coercions, e.g. | |
;; - key respellings | |
;; - | |
;; | |
;; Design questions: | |
;; | |
;; - is the directionality metaphor the most natural? | |
;; - handling coercions | |
;; - surjections vs projections | |
;; - force them to the edge? | |
;; - order of comp args | |
;; - how much data validation to do, | |
;; how to let users configure it | |
;; | |
(defprotocol IBijection | |
(-> [b x]) | |
(<- [b x])) | |
(defrecord Bijection [-> <-] | |
IBijection | |
(-> [_ x] (-> x)) | |
(<- [_ x] (<- x))) | |
(defn comp | |
;; ordering should be the same as clojure.core/comp for ->, | |
;; | |
;; which is unfortunate because then foo<->bar names compose the | |
;; opposite of what you'd want. might want to reverse this. | |
([b] b) | |
([b1 b2] | |
(Bijection. | |
(core/comp (:-> b1) (:-> b2)) | |
(core/comp (:<- b2) (:<- b1)))) | |
([b1 b2 & more] | |
(reduce comp b1 (cons b2 more)))) | |
(defn update | |
[k b] | |
(->Bijection | |
(fn [m] (core/update m k #(-> b %))) | |
(fn [m] (core/update m k #(<- b %))))) | |
(defn biject-keys | |
"Given a collection of keys and a function that transforms those keys, | |
returns a bijection on maps that replaces the given keys according to | |
the function." | |
[ks func] | |
(let [ks' (map func ks) | |
km (zipmap ks ks') | |
km' (zipmap ks' ks)] | |
(assert (= (count km) (count km') (count ks))) | |
;; TODO: handle when the keys are missing | |
(->Bijection | |
(fn [m] | |
(set/rename-keys m km)) | |
(fn [m] | |
(set/rename-keys m km'))))) | |
(def uuid<->str | |
(->Bijection str #(UUID/fromString %))) | |
(defn nilfurcate | |
"Given a bijection between two sets that do not contain nil, | |
returns an augmented bijection that maps nil to nil" | |
[b] | |
(->Bijection #(some->> % (-> b)) #(some->> % (<- b)))) | |
(defn explicit | |
[m] | |
(let [m' (into {} (for [[k v] m] [v k]))] | |
(assert (= (count m) (count m'))) | |
(->Bijection m m'))) | |
(defn merge | |
[b1 b2] | |
;; the user would also have to supply a function for identifying | |
;; which bijection an object belongs to, on both ends | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment