Before I show that, let me say some things about what this is not and what it is. First, this is not necessarily the best way to build an inference engine; it is not even necessarily the best way to build a toy inference engine. Second, it is not a compendium of F# design patterns or best practices. Third, it is not thoroughly checked for quality or errors, it is a bit of weekend fun – use it at your own risk. Here’s what it is: it was a fun exercise in learning some things about F#. In particular, it shows how F# can easily be used as a framework for constructing domain and application-specific languages.
The tiny inference engine is made up of three things:
Consider and ConsiderImmediate – functions which define and instantiate hyptheses. Note that these are functionally very simple; most of the code in them is for readability and reporting.
ConjoinPossible, ConjoinEval, and Conjoin – functions which combine hypotheses conjunctively (i.e. using “and”). The first two functions are helpers. ConjoinPossible determines whether a conjunction is still possible given the current state of the evidence. ConjoinEval performs an actual conjunction. Conjoin groups the previous functions into a neat package.
DisjoinPossible, DisjoinEval, and Disjoin – equivalents of the conjunctive functions which combine hypotheses disjunctively (i.e. using “or”).
There is also a small sample set of rules which reason about the color of a thing. Rather than query the user, I hard-coded the values for the root hypotheses. This simplifies testing. Also, please note that the code is specially formatted to fit the narrow blog window.
// Note: formatted for blog width.
// Lazy evaluation of an hypothesis.
// Everything except the return
// is just for information.
let Consider s (b:Lazy<bool>) = lazy (
printf "Considering: "
printfn s
match b.Value with
| false -> printf "Rejecting: "
| true -> printf "Accepting: "
printfn s
b.Value)
// Immediate evaluation of an hypothesis.
// Everything except the return
// is just for information.
let ConsiderImmediate s (b:Lazy<bool>) =
printf "Considering: "
printfn s
match b.Value with
| false -> printf "Rejecting: "
| true -> printf "Accepting: "
printfn s
b
// This set of functions handles conjunction.
// True on all true or unknown.
let rec ConjoinPossible (l:Lazy<bool> list) =
match l with
| [] -> true
| h::t ->
if (h.IsValueCreated)
then (h.Value && ConjoinPossible(t))
else ConjoinPossible(t)
// Conjoin with short-circuit on false.
let rec ConjoinEval (l:Lazy<bool> list) =
match l with
| [] -> true
| h::t -> h.Value && ConjoinEval(t)
// Conjoin if possible.
let rec Conjoin (l:Lazy<bool> list) = lazy (
ConjoinPossible(l) &&
ConjoinEval(l))
// This set of functions handles disjunction.
// True on at least one true or unknown.
let rec DisjoinPossible (l:Lazy<bool> list) =
match l with
| [] -> false
| h::t ->
if (h.IsValueCreated)
then (h.Value || DisjoinPossible(t))
else true
// Disjoin with short-circuit on true.
let rec DisjoinEval (l:Lazy<bool> list) =
match l with
| [] -> false
| h::t -> h.Value || DisjoinEval(t)
// Disjoin if possible.
let rec Disjoin (l:Lazy<bool> list) = lazy (
DisjoinPossible(l) &&
DisjoinEval(l))
// Here are some test hypotheses.
// This first block contains hard coded values.
// In real life, these would be input data.
// They are hard-coded here to simplify testing.
let black =
Consider "black" (lazy true)
let blue =
ConsiderImmediate "blue" (lazy true)
let orange =
Consider "orange" (lazy false)
let white =
Consider "white" (lazy true)
// This second block is the logic.
let blackAndOrange =
Consider
"blackAndOrange"
(Conjoin [
black;
orange ])
let blackAndOrangeOrBlue =
Consider
"blackAndOrangeOrBlue"
(Disjoin [
blackAndOrange;
blue ])
let whiteAndBlack =
Consider
"whiteAndBlack"
(Conjoin [
white;
black ])
let color =
ConsiderImmediate
"color"
(Disjoin [
blackAndOrange;
blackAndOrangeOrBlue;
whiteAndBlack ])
// Run the system.
open System
Console.ReadLine() |> ignore
So what’s next? I’m not sure. For one thing, I’d like to add certainty factors. This may entail a more complex record type. In the interests of exercise particular F# features, I will likely base this on records or discriminated unions rather than classes. Stay tuned.
No comments:
Post a Comment