Skip to content

Instantly share code, notes, and snippets.

@gfredericks
Created March 19, 2018 17:07
Show Gist options
  • Save gfredericks/358c39478f104281a6364f446f5b3c6b to your computer and use it in GitHub Desktop.
Save gfredericks/358c39478f104281a6364f446f5b3c6b to your computer and use it in GitHub Desktop.
Half a draft of bijections in clojure
(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