Ruby plays to many programming paradigms. It's an object-oriented language that can be used in a functional or an imperative/procedural way. But Ruby does not often get used as a logic programming language. In this talk I'll explore logic programming using Ruby. What is it, and is it a tool you want to add to your toolbox? We'll touch on several libraries, we'll primary look at an implementation of minikanren (http://minikanren.org/) for Ruby.
3. Ruby is many things
Ruby is a dynamic, reflective, object-oriented, general-
purpose programming language. [...] Ruby was influenced
by Perl, Smalltalk, Eiffel, Ada, and Lisp. It supports multiple
programming paradigms, including functional, object-
oriented, and imperative. It also has a dynamic type system
and automatic memory management.
~Wikipedia
6. This may not be very pragmatiC
I'm going to talk about something Ruby isn't good at...
I'm going to show you some libraries that are half baked...
But hopefully, I'll encourage you to explore logic programming more....
7. real world Logic programming
ThreatGRID uses logic
programming (core.logic in Clojure)
to process observations of malware
execution looking for behavioral
indicators of compromise.
10. (defobs process-modified-path
[pid path]
:doc "A pathname modified by a process,
associated by the PID."
:tags ["process" "file" "directory" "path"])
assert observations
Malware analysis generates
analysis data, which in turn
generates observation data that
can be queried by core.logic.
Some observations are exported
to the database.
11. (defioc autoexec-bat-modified
:title "Process Modified AUTOEXEC.BAT"
:description "A process modified the AUTOEXEC.BAT file. ..."
:category ["persistence" "weakening"]
:tags ["process" "autorun" "removal"]
:severity 80
:confidence 70
:variables [Path Process_Name Process_ID]
:query [(process-modified-path Process_ID Path)
(matches "(?i).*AUTOEXEC.BAT" Path)
(process-name Process_ID Process_Name)])
Logic programs are queries
Security researchers write
core.logic queries over the
observations.
Declarative nature combined with
abstraction make queries small
and high level.
12. (defioc sinkholed-domain-detected
:title "Domain Resolves to a Known DNS Sinkhole"
:description "..."
:category ["research" "defending"]
:tags ["network" "dns" "sinkhole" "botnet"]
:severity 100
:confidence 100
:variables [Answer_Data Answer_Type Query_Data
Query_Type Network_Stream]
:query
[(fresh [qid]
(dns-query Network_Stream qid (lvar)
Query_Type Query_Data)
(dns-answer Network_Stream qid (lvar)
Answer_Type Answer_Data (lvar)))
(sinkhole-servers Answer_Data)])
Logic programs are queries
We combine rules with internal
knowledge bases.
Declarative queries combined
with abstraction make queries
small and high level.
13. Indicators produce data
{:ioc autoexec-bat-modified
:hits 1
:data ({Process_ID 1200
Process_Name "smss.exe"
Path "AUTOEXEC.BAT"})
:confidence 70
:truncated false
:title "Process Modified AUTOEXEC.BAT"
:description "A process modified the AUTOEXEC.BAT ..."
:severity 80
:category ["persistence" "weakening"]
:tags ["process" "autorun" "removal"]}
Queries generate data that is
used in reports.
14. Sample reports
Reports show observations and
matched indicators and their
data.
We also correlate this data and
mine the relationships between
samples to create data feeds that
customers can take action based
on
15. minikanren
Minikanren is a relational programming
environment, originally written in Scheme
but ported to many other languages. It is
described in the book The Reasoned Schemer.
The language is powerful, but deceptively
simple, with only a few core language
concepts.
http://minikanren.org/
16. ruby minikanren
One of two implementations, neither of
which are currently being developed. (I
would love to help someone fix this)
Doesn't have any advanced features
you need for real world use, but it can
be used for most of the examples in
The Reasoned Schemer.
https://github.com/spariev/mini_kanren
require 'mini_kanren'
include MiniKanren::Extras
result = MiniKanren.exec do
# your logic program goes here
end
17. run
run([], succeed)
This is the simplest possible
minikanren. There are no query
variables, and the query always
succeeds
run says "give me all the results"
and in ruby minikanren is an
array. This query returns one
result, which matches the empty
query.
[[]]
19. FRESH
fresh introduces logic variables.
Logic variables are the things we
want to find the values of.
Minikanren programs often use q
to represent the query.
_.0 represents an unbound logic
variable in the results. We are
saying, the query succeeded and
the result is anything.
["_.0"]
q = fresh
run(q, succeed)
20. FRESH
This query has two logic
variables, and we find one
results, where both logic
variables are unbound and
different. (or at least not
constrained to be the same) [["_.0", "_.0"]]
a, b = fresh 2
run([a, b], eq(a, b))
21. unification
run(q, eq(q, :hello))
The most fundamental operation
on a logic variable is to unify it.
unification is eq.
There is only one value of q that
satisfies the relation. [:hello]
22. unification
run(q, eq(q, [:hello, :world]))
Logic variables can also be
unified over non-primitive values
There is still only one value of q
that satisfies the relation.
[[:hello, :world]]
23. all
run(q, all(eq(q, :helloworld),
eq(:helloworld, q)))
All expresses that all conditions
must be true.
A logic variable can unify with the
same value multiple times. But
the overall goal only succeeds
once, so there is only one value
of q that satisfies the relation.
[:helloworld]
24. all
run(q, all(eq(q, :hello),
eq(q, :world)))
A logic variable cannot unify with
two different values at the same
time.
There are no values of q that
satisfy the relation. []
25. conde
run(q,
conde(eq(q, :hello),
eq(q, :world)))
You can introduce alternative
values with conde. Every conde
clause that succeeds produces
possible alternative values.
There are 2 values of q that
satisfy the relation. [:hello, :world]
26. Ordering clauses
run(q,
fresh {|a,b|
all(eq([a, :and, b], q),
eq(a, :something),
eq(:somethingelse, b)})
fresh can be used inside of a
query.
Order does not matter for
unification nor does the order of
clauses matter. [[:something, :and, :somethingelse]]
27. rock paper scissors
def beats(move1, move2)
conde(all(eq(move1, :rock),
eq(move2, :scissors)),
all(eq(move1, :scissors),
eq(move2, :paper)),
all(eq(move1, :paper),
eq(move2, :rock)))
end
beats is a custom relation
between two terms. It succeeds
when the first players move
beats the second players move.
More advanced implementations
might have a prolog-style fact
database, but we'll do this the
hard way.
28. rock paper scissors
run(q, beats(:rock, :paper))
beats fails because :rock does
not beat :paper. No value of q
makes this succeed.
[]
29. rock paper scissors
run(q, beats(:paper, :rock))
beats succeeds because :paper
beats :rock. q remains fresh
because no questions were
asked of it.
["_.0"]
31. rock paper scissors
core.logic
winner, loser = fresh 2
run([winner, loser],
beats(winner, loser))This query asks for all the pairs
where winner beats loser.
[[:rock, :scissors],
[:scissors, :paper],
[:paper, :rock]]
33. SPOCK CHAINS
core.logiccore.logic
run(q,
fresh{|m1, m2|
all(eq(q, [:spock, m1, m2, :spock]),
rpsls_beats(:spock, m1),
rpsls_beats(m1, m2),
rpsls_beats(m2, :spock))})
We can ask questions like: give
me a 4-chain of dominated
moves starting and ending
with :spock. There are three
solutions.
[[:spock, :rock, :lizard, :spock],
[:spock, :scissors, :paper, :spock],
[:spock, :scissors, :lizard, :spock]]
34. spock chains
def chain(moves)
fresh {|first, rest|
all(caro(moves, first),
cdro(moves, rest),
rpsls(first),
conde(nullo(rest),
fresh {|second|
all(caro(rest, first),
rpsls_beats(first, second),
defer(method(:chain), rest))}))}
end
A winning chain is a single rpsls
move either by itself or followed
by a winning chain whose first
move is beaten by the original
move.
This example uses LISP-style list
conventions. caro (first element)
and cdro (the rest of the times)
are relations on those lists.
35. how many chains?
run(q,
all(eq(q, build_list([:spock] + fresh(10) +[:spock])),
chain(q))).length
How many winning chains are
there from :spock to :spock with
10 steps?
385
36. def edge(x,y)
edgefact = -> (x1, y1) {
all(eq(x,x1),eq(y,y1))
}
conde(edgefact[:g, :d],
edgefact[:g, :h],
edgefact[:e, :d],
edgefact[:h, :f],
edgefact[:e, :f],
edgefact[:a, :e],
edgefact[:a, :b],
edgefact[:b, :f],
edgefact[:b, :c],
edgefact[:f, :c])
end
Path finding
D
A
E
B
G
H
F
C
37. def path(x, y)
z = fresh
conde(eq(x, y),
all(edge(x, z),
defer(method(:path), z, y)))
end
def ispath(nodes)
fresh {|first, second, rest|
all(caro(nodes, first),
cdro(nodes, rest),
conde(nullo(rest),
all(edge(first, second),
caro(rest, second),
defer(method(:ispath), rest))))}
end
Path finding
D
A
E
B
G
H
F
C
42. Map coloring
core.logiccore.logic
http://pragprog.com/book/btlang/seven-languages-in-seven-weeks
(run 1 [q]
(fresh [tn ms al ga fl]
(everyg #(membero % [:red :blue :green])
[tn ms al ga fl])
(!= ms tn) (!= ms al) (!= al tn)
(!= al ga) (!= al fl) (!= ga fl) (!= ga tn)
(== q {:tennesse tn
:mississipi ms
:alabama al
:georgia ga
:florida fl})))
({:tennesse :blue,
:mississipi :red,
:alabama :green,
:georgia :red,
:florida :blue})
43. FINITE DOMAINS
core.logiccore.logic
fd/interval declares a finite
integer interval and fd/in
contrains logic variables to a
domain.
(defn two-plus-two-is-four [q]
(fresh [t w o f u r TWO FOUR]
(fd/in t w o f u r (fd/interval 0 9))
(fd/distinct [t w o f u r])
(fd/in TWO (fd/interval 100 999))
(fd/in FOUR (fd/interval 1000 9999))
...
(== q [TWO TWO FOUR])))
T W O
+ T W O
-------
F O U R
http://www.amazon.com/Crypt-arithmetic-Puzzles-in-PROLOG-ebook/dp/B006X9LY8O
44. FINITE DOMAINS
core.logiccore.logic
fd/eq translates simple math to
constraints over finite domain
logic variables.
(fd/eq (= TWO
(+ (* 100 t)
(* 10 w)
o)))
(fd/eq (= FOUR
(+ (* 1000 f)
(* 100 o)
(* 10 u)
r)))
(fd/eq (= (+ TWO TWO) FOUR))
T W O
+ T W O
-------
F O U R
45. FINITE DOMAINS
core.logiccore.logic
There are 7 unique solutions to
the problem.
(run* [q]
(two-plus-two-is-four q))
T W O
+ T W O
-------
F O U R
([734 734 1468]
[765 765 1530]
[836 836 1672]
[846 846 1692]
[867 867 1734]
[928 928 1856]
[938 938 1876])
46. USEless logic puzzle
core.logiccore.logic
‣ petey pig did not hand out the popcorn
‣ pippin pig does not live in the wood house
‣ the pig that lives in the straw house handed out
popcorn
‣ Petunia pig handed out apples
‣ The pig who handed out chocolate does not live in
the brick house.
Three little pigs, who each
lived in a different type of
house, handed out treats for
Halloween. Use the clues to
figure out which pig lived in
each house, and what type of
treat each pig handed out.
http://holidays.hobbyloco.com/halloween/logic1.html
47. USEless logic puzzle
core.logiccore.logic
(defn pigso [q]
(fresh [h1 h2 h3 t1 t2 t3]
(== q [[:petey h1 t1]
[:pippin h2 t2]
[:petunia h3 t3]])
(permuteo [t1 t2 t3]
[:chocolate :popcorn :apple])
(permuteo [h1 h2 h3]
[:wood :straw :brick])
... ))
pigso starts by defining the
solution space.
permuteo succeeds when the
first list is permutation of the
second.
48. USEless logic puzzle
core.logiccore.logic
(fresh [notpopcorn _]
(!= notpopcorn :popcorn)
(membero [:petey _ notpopcorn] q))
(fresh [notwood _]
(!= notwood :wood)
(membero [:pippin notwood _] q))
(fresh [_]
(membero [_ :straw :popcorn] q))
(fresh [_]
(membero [:petunia _ :apple] q))
(fresh [notbrick _]
(!= notbrick :brick)
(membero [_ notbrick :chocolate] q))
The clues translate cleanly to
goals constraining the solution
space.
membero has a solution when
the first item is a member of the
second.