Genetic
Programmingand
Beyondwith
clojure.specCarin Meier(@gigasquid)- Cognitect
I'm so excitedto be
here!
Perspective
Seeingthings ina
differentway
JavaDeveloper
manyyears
Everything is
an object
Differentwayofthinking
Modelingtheworld ina functional
way
FourTribes of
MathematiciansDavid Mumford Mathand Beautyand
BrainAreas
FourTribes ofProgrammers
» Explorers
» Alchemists
» Wrestlers
» Detectives
FourTribes ofProgrammers
Explorers
Those who get excited about discovering a new
algorithm or tool.
FourTribes ofProgrammers
Explorers
Gem Collectors
Bring beautiful things back from their discoveries
FourTribes ofProgrammers
Explorers
Mappers
Describe and chart the new lands
FourTribes ofProgrammers
Alchemists
Those who get excited about finding connections
between different areas of programming or other
fields that no one had seen before.
Pour one into another and explode!
FourTribes ofProgrammers
Wrestlers
Those who enjoy the scale of programming.
They thrive on true big data, speed, and handling
things at scale that would make other people faint
FourTribes ofProgrammers
Detectives
Those who find enjoyment in diving into the deep,
detailed aspects of programming.
Understanding things at a 200X magnification.
Belongto One or Many
» Explorers
» Alchemists
» Wrestlers
» Detectives
Alchemist
Seek Integration
Iamabig “integrationalways
proponent"
Integration
“Take risks at the edge of scholarship and seek
integration.
Most forays won’t yield anything, but some certainly
will.”
M Gazzaniga (Split Brain) leader of cognitive
neuroscience
Combine clojure.spec
withthings I'm
interested in
Clojure.spec
Genetic
Programming
WhyClojure.spec?
» Allows you to describe data and create a spec
» Allows you to validate data against the spec
» Allows you to generate data from the spec
Describe Dataand Create Spec
foo is a integer
(require '[clojure.spec :as s])
;; => nil
(s/def ::foo int?)
;; => :user/foo
Validate DataagainstaSpec
Is this data a valid foo ?
(s/valid? ::foo 1)
;; => true
(s/valid? ::foo "hi")
;; => false
Validate DataagainstaSpec
Is this data a valid foo ?
Why Not?
(s/explain ::foo "hi")
;;=> val: "hi" fails spec: :user/foo predicate: int?
Generate DatafromaSpec
Can you give me some example data?
(s/exercise ::foo 1)
;; => ([-1 -1])
Whatis Genetic
Programming?
Evolving creaturesthrough breeding
and mutating
Whatis Genetic
Programming?
The creaturesare PROGRAMS!
Our Experiment
Given some data
Use genetic programmingto
evolveaspec programto
describe it
Spec Creatures
Eat Data
["hi" true 5 10 "boo"]
Spec Creatures
» program = spec
» score
{:program (s/cat :0 int? :1 string?)
:score 0}
Howto Scorea
Creature
Howmuch ofthe datacanthe
spec consumewithouterrors?
PerfectScore
(s/explain-data (s/cat :0 int? :1 string?) [1 "hi"])
;=> nil
NotPerfectScore
Use explain-datato figure itout
(s/explain-data (s/cat :0 int? :1 string?) [1 true])
;=> #:clojure.spec{:problems
[{:path [:1]
:pred string?
:val true
:via []
:in [1]}]}
Howto ScoreaCreature
» Perfect match = 100
» Anything less is how far in the sequence it was
valid
CreatingaCreature
CreatingaCreatureUsethe power ofClojure!
Code is Data
Creature
Creation
Controls
» All start with s/cat
» Allowed preds
» integer?
» string?
» boolean?
» even?
» odd?
Creature
Creation
Controls
» Allow composition
» s/+
» s/*
» s/and
» s/or
Creature
Creation
Controls
probabilityknobs
(def seq-prob 0.3)
(def nest-prob 0.00)
(def max-depth 4)
(def and-or-prob 0.85)
Creature Create!
(make-random-cat 3)
;=> (clojure.spec/cat :0 (s/and integer? odd?) :1 integer? :2 boolean?)
Creature Create!
Make an initial population
(defn initial-population [popsize max-cat-length]
(for [i (range popsize)]
{:program (make-random-cat (inc (rand-int max-cat-length)))}))
So NowWhat?
Howdotheyevolve?
Mutate
MutateaCreature
» Walk the creatures program
» Choose a program segment with a probability
» Swap out the piece for newly generated segment
Mutate Creature!
(mutate {:program '(clojure.spec/cat :0 (s/and integer? odd?) :1 integer?)})
;=> {:program (clojure.spec/cat :0 (s/or (s/and integer? even?)) :1 integer?)}
Whatabout
Breeding?
CrossoverTakestwo creaturesand swapsanode
from one creaturetoanother
Crossover
» Use the walk function to select at a random
probability the crossover node from the first
creature
» Insert it into the second creature's program at
another random spot
Crosover!
(crossover {:program '(clojure.spec/cat :0 (s/and integer? odd?) :1 integer?)}
{:program '(clojure.spec/cat :0 string? :1 boolean?)})
;=> {:program (clojure.spec/cat :0 (s/and integer? odd?) :1 boolean?)}
We can now
» Create Creatures
» Score Creatures
» Change Creatures with Mutation
» Breed Creatures with other Creatures
Evolution Process
Evolution Process
» Create initial population
» Rank them
» Take the top two best ones and carry them over
(this is known as elitism)
» Create the next generation from selecting
creatures for crossover and mutation
» Repeat!
Howdoyou select
them?
Howdoyou select
them?Good Question
Howdoyou select
them?
Tournamentselection
buttherearemanyotherwaystoo
TournamentSelection
» Pick n creatures from the whole population
» Among those, pick the best scored one
This will allow diversity in our population that is
needed for proper evolution
Readyto Evolve
Makeafunctionthathas inputs
population size
how many generations
tournament size
test data
Ends with creature that is perfect fit or max
generations
(defn perfect-fit [creatures]
(first (filter #(= 100 (:score %)) creatures)))
Momentof
Truth
Evolve
(def creature-specs (evolve 100 100 7 ["hi" true 5 10 "boo"]))
Evolve
(def creature-specs (evolve 100 100 7 ["hi" true 5 10 "boo"]))
(perfect-fit creature-specs)
Evolve
(def creature-specs (evolve 100 100 7 ["hi" true 5 10 "boo"]))
(perfect-fit creature-specs)
;=>{:program (clojure.spec/cat :0 string?
; :1 boolean?
; :2 (s/and integer? odd?)
; :3 integer?
; :4 string?)
; :score 100}
Yay!
We Did it- butwait...
Our generated
creature isaspec
Specs can generate
data
Specs can generate data
(s/exercise (eval (:program (perfect-fit creature-specs))) 5)
;; ([("" true -1 -1 "") {:0 "", :1 true, :2 -1, :3 -1, :4 ""}]
;; [("D" false -1 -1 "G") {:0 "D", :1 false, :2 -1, :3 -1, :4 "G"}]
;; [("12" false -1 0 "l0") {:0 "12", :1 false, :2 -1, :3 0, :4 "l0"}]
;; [("" false -1 -2 "") {:0 "", :1 false, :2 -1, :3 -2, :4 ""}]
;; [("2" false 1 0 "Jro") {:0 "2", :1 false, :2 1, :3 0, :4 "Jro"}])
We can usethat
generated datato
generate more specs!
Genetic
Programming
Summary
Genetic Programming Summary
» Start with a way to generate random creatures
» Have a way to evaluate their fitness
» Create a way to change them for the next
generations using
» Mutation
» Crossover
Genetic Programming Summary
» Have an evolution process
» Create an initial population
» Rank them
» Create the next generation using selection
techniques and mutation/ crossovers
» Don’t forget about diversity
Clojure.spec is
reallycool
Whatotherthings can it
do?
CouldWe Use itto Make
Code Smarter?
Wouldn'titbe greatifa
program could recover
froman errorand heal
itself
This codewould beableto
rise above the mistakes
ofits humble programmer
and make itself better
SelfHealing Code
Clojure.spec
SelfHealing
Code
SelfHealing Code
Ingredients
The paperTowards Design for Self-
healing outlinesafewmain
ingredientsthatwewillneed
SelfHealing Code Ingredients
» Failure Detection - This one is pretty straight
forward. We need to detect the problem in order to
fix it.
» Fault Diagnosis - Once the failure has been
detected, we need to be able to figure out exactly
what the problem was so that we can find a
solution.
SelfHealing Code Ingredients
» Fault Healing - This involves the act of finding a
solution and fixing the problem.
» Validation - Some sort of testing that the
solution does indeed solve the problem.
Nowwe have ingredients
Howdowe selfheal?
HorizontalDonor
CodeTransfer
MITdevelopedasystem
called CodePhage
CodePhage
» Inspired from the biological world with horizontal
gene transfer of genetic material between
different organisms.
» In it they use a horizontal code transfer system
that fixes software errors by transferring correct
code from a set of donor application
Cool! Canwe dothatin
Clojure?
» Clojure - has macros that let code modify itself
» clojure.spec - gives code the ability to describe
itself
» clojure.spec - let's code share these specs with
other code with its global registry
» clojure.spec - gives us the ability to generate
example data from the specs
SelfHealing Clojure
Experiment
» write a small report
program
» function called report
» made up of three helper
functions.
» takes in a list of earnings
and outputs a string
summary of the average.
SelfHealing Clojure Experiment
(defn report [earnings]
(-> earnings
(clean-bad-data)
(calc-average)
(display-report)))
SelfHealing Clojure Experiment
(defn report [earnings]
(-> earnings
(clean-bad-data)
(calc-average)
(display-report)))
Divide by zero error waiting in calc-average!!!
Our Goal
» (should we choose to accept the mission)
Onthe error, use clojure.specto finda
matching replacementfunction fromasetof
donor candidates.
The Setup
reportfunction - dive in
(defn report [earnings]
(-> earnings
(clean-bad-data)
(calc-average)
(display-report)))
clean-bad-data
It takes in a vector of anything and filters out only
those elements that are numbers.
(defn clean-bad-data [earnings]
(filter number? earnings))
clean-bad-dataspecs
specthe input
earnings will be a vector, (for the params) with
another vector of anything.
(s/def ::earnings (s/cat :elements (s/coll-of any?)))
clean-bad-dataspecs
specthe output
Custom generator - which will constrain the generator
to just returning the value [[1 2 3 4 5]] as its
example data* will explain why later
(s/def ::cleaned-earnings (s/with-gen
(s/cat :clean-elements (s/coll-of number?))
#(gen/return [[1 2 3 4 5]]))
clean-bad-dataspecs
example ofrunningthe functionwith specs
(clean-bad-data [1 2 "cat" 3])
;=>(1 2 3)
clean-bad-dataspecs
example ofrunning exercise on it
(s/exercise ::cleaned-earnings 1)
;=> ([[[1 2 3 4 5]] {:clean-elements [1 2 3 4 5]}])
spec calc-average functionwith vital/fatal flaw
(defn calc-average [earnings]
(/ (apply + earnings) (count earnings)))
(s/def ::average number?)
(s/fdef calc-average
:args ::cleaned-earnings
:ret ::average)
display-reportfunction
(s/def ::report-format string?)
(defn display-report [avg]
(str "The average is " avg))
(s/fdef display-report
:args ::average
:ret ::report-format)
reportfunction
(defn report [earnings]
(-> earnings
(clean-bad-data)
(calc-average)
(display-report)))
(s/fdef report
:args ::earnings
:ret ::report-format)
ReadyforTestDrive
ReadyforTestDrive
(report [1 2 3 4 5])
;=> "The average is 3"
And the fatal flaw:
(report [])
;=> EXCEPTION! Divide by zero
We have our problem setup
Now...the Donor
Candidates
Donor CandidatesInanother namespacetherewillbea
number offunctionsallspeced out
Donor CandidatesSomewillmatchthe spec for our
failing function, somewillnot
Donor Candidates
Bad Matches
» bad-calc-average - matches spec but wrong answer
value
» bad-calc-average2 - good value but returns string
» adder - not spec match
Donor Candidates
Good Match better calc average
(s/def ::numbers (s/cat :elements (s/coll-of number?)))
(s/def ::result number?)
(defn better-calc-average [earnings]
(if (empty? earnings)
0
(/ (apply + earnings) (count earnings))))
We havethe setup
We havethe donor
candidates
Nowwhatisthe process?
The SelfHealing
Process
The SelfHealing Process
» Try the report function and catch any exceptions.
» If we get an exception, look through the stack
trace and find the failing function name.
» Retrieve the failing function's spec from the spec
registry
The SelfHealing Process
» Look for potential replacement matches in the
donor candidates
» Check the orig function's and the donor's :args
spec and make sure that they are both valid for
the failing input
The SelfHealing Process
» Check the orig function's and the donor's :ret
spec and make sure that they are both valid for
the failing input
» Call spec exercise for the original function and
get a seed value. Check that the candidate
function's result when called with the seed value
is the same result when called with the original
function.
The SelfHealing Process
» If a donor match is found, then redefine the
failing function as new function. Then call the
top level report form again, this time using the
healed good function.
» Return the result!
Code itupinafunction
with-healing
Tryitout!!
First we call the report function with a non-empty
vector.
(healing/with-healing (report [1 2 3 4 5 "a" "b"]))
;=>"The average is 3"
Nowthe bigtest...
(healing/with-healing (report []))
; ERROR in function self-healing.core/calc-average
; Divide by zero -- looking for replacement
; Retreiving spec information for function
; {:args :self-healing.core/cleaned-earnings
; :ret :self-healing.core/average, :fn nil}
; Checking candidates better-calc-average
; adder bad-calc-average bad-calc-average2)
; ----------
; **Looking at candidate better-calc-average
; ****Comparing args :numbers :cleaned-earnings with input [[]]
; ****Comparing seed [[1 2 3 4 5]] with new function
; ****Result: old 3 new 3
; Found a matching candidate replacement;
; Replacing with candidate match better-calc-average
; ----------
; Calling function again
; Healed function result is: The average is 0
;=>"The average is 0"
Sincethe function is nowhealedwe can call
itagainand itwon'thavethe same issue.
(healing/with-healing (report []))
;=>"The average is 0"
Success!
SelfHealing Summary
» Very simple - didn't do validation on fn of spec
» Only checked one seed value from exercise but
could have done much more - 10 or 100
» We neglected to use the built in feature of spec
to check functions that would have found the
divide by zero before it happened
SelfHealing Summary
» clojure.spec rocks
» clojure.spec adds another dimension to how we can
solve problems in self-healing
Code is outon Github
» https://github.com/gigasquid/self-healing
» https://github.com/gigasquid/genetic-programming-
spec
TakingaStepBack
We'vejustscratched
the surface on how
clojure.spec can be
used
ToAllMyFellow
Explorers,
Alchemists,
Wrestlers,and
Detectives
Clojure.spec has
openedthe doors
wideto newand
excitingareas
Challengetoyou
Clojure.spec + ? =win
Thankyou!
Pic Credits
Cardboard Box City
Cat in box
Forest
heart
tribe
evolution
mad scientist
gold
dna
programmer
test drive
cooking

Genetic programming with clojure.spec and Beyond