$30 off During Our Annual Pro Sale. View Details »

Hello, declarative world

Hello, declarative world

We know that a computer is an imperative machine: a CPU reads one instruction after another, and performs one operation after another, with each operation modifying the state of its registers and memory. But that doesn’t mean we HAVE to write our computer programs as imperative step-by-step recipes; there are other interesting ways to specify a computation.

Ruby’s billed as a “multi-paradigm” language, but, let’s face it, we use it to write imperative object-oriented programs. I'm interested in different ways of programming computers, because I reckon imperative programs are far too low-level and overspecified, which is why as a species we're generally terrible at writing them.

One interesting alternative is declarative programming, where we tell the computer about the problem we want to solve instead of exactly what steps to take to solve it. That idea’s been around for a long time but has recently begun to reappear everywhere.

In this talk I want to encourage you to think outside of your normal programming habits; I’ll present a more abstract and declarative style called relational programming, and show you how to implement a minimal relational language in Ruby. (Dog whistle: this is μKanren.)

Given at Joy of Coding 2015 (http://lanyrd.com/2015/joyofcoding/) and the London Ruby User Group (http://lrug.org/meetings/2015/august/). A video and expanded transcript are available at https://tomstu.art/hello-declarative-world, and the code is available at http://github.com/tomstuart/kanren.

Tom Stuart

August 10, 2015
Tweet

More Decks by Tom Stuart

Other Decks in Programming

Transcript

  1. HELLO,
    DECLARATIVE
    WORLD
    http://codon.com/hello-declarative-world

    View Slide

  2. View Slide

  3. View Slide

  4. UNSTRUCTURED
    PROCEDURAL
    FUNCTIONAL
    DECLARATIVE
    IMPERATIVE
    MORE
    ABSTRACT

    View Slide

  5. PROGRAMMER
    (A HUMAN)
    SOLVER
    (A MACHINE)
    HOW
    WHAT

    View Slide

  6. UNSTRUCTURED
    PROCEDURAL
    FUNCTIONAL
    RELATIONAL
    DECLARATIVE
    IMPERATIVE
    MORE
    ABSTRACT

    View Slide

  7. “FUNCTION”
    19
    23
    42
    x
    y
    x + y

    View Slide

  8. “RELATION”
    y is 5
    x = y
    x is 5
    y is ?

    View Slide

  9. “RELATION”
    x is 4
    x < y
    y is 5
    x is ? x is 3
    x is 2
    x is 1
    x is 0

    View Slide

  10. LET’S BUILD
    OUR OWN!

    View Slide

  11. GOAL
    STATE STATE
    STATE
    STATE

    View Slide

  12. VARIABLES

    View Slide

  13. >> x, y = Variable.new(:x),
    Variable.new(:y)
    => [x, y]
    >> x == x
    => true
    >> x == y
    => false
    >> x == Variable.new(:x)
    => false

    View Slide

  14. class Variable
    def initialize(name)
    @name = name
    end
    def inspect
    @name
    end
    end

    View Slide

  15. STATES

    View Slide

  16. class State
    def initialize(variables = [], values = {})
    @variables, @values = variables, values
    end
    attr_reader :variables, :values
    end

    View Slide

  17. >> state = State.new
    => #
    >> state, (x, y, z) =
    state.create_variables [:x, :y, :z]
    => [#,
    [x, y, z]]
    >> state.variables
    => [x, y, z]
    >> state =
    state.assign_values x => y, y => z, z => 5
    => #@values={x=>y, y=>z, z=>5}>
    >> state.values
    => {x=>y, y=>z, z=>5}

    View Slide

  18. class State
    def create_variables(names)
    new_variables =
    names.map { |name| Variable.new(name) }
    [
    State.new(variables + new_variables, values),
    new_variables
    ]
    end
    def assign_values(new_values)
    State.new(variables, values.merge(new_values))
    end
    end

    View Slide

  19. >> state.values
    => {x=>y, y=>z, z=>5}
    >> state.value_of x
    => 5
    >> state.value_of 7
    => 7
    >> state, (a, b, c) =
    state.create_variables [:a, :b, :c]
    => [#, [a, b, c]]
    >> state.value_of a
    => a

    View Slide

  20. class State
    def value_of(key)
    if values.has_key?(key)
    value_of values.fetch(key)
    else
    key
    end
    end
    end

    View Slide

  21. UNIFICATION

    View Slide

  22. >> state, (x, y) =
    State.new.create_variables [:x, :y]
    => [#,
    [x, y]]
    >> state = state.unify(x, x)
    => #
    >> state.values
    => {}

    View Slide

  23. >> state = state.unify(x, y)
    => #y}>
    >> state.values
    => {x=>y}
    >> state = state.unify(x, 5)
    => #y, y=>5}>
    >> state.values
    => {x=>y, y=>5}
    >> state.value_of x
    => 5
    >> state.unify(y, 6)
    => nil

    View Slide

  24. class State
    def unify(a, b)
    a, b = value_of(a), value_of(b)
    if a == b
    self
    elsif a.is_a?(Variable)
    assign_values a => b
    elsif b.is_a?(Variable)
    assign_values b => a
    end
    end
    end

    View Slide

  25. GOALS

    View Slide

  26. class Goal
    def initialize(&block)
    @block = block
    end
    def pursue_in(state)
    @block.call state
    end
    end

    View Slide

  27. BASIC
    GOALS

    View Slide

  28. EQUAL
    VALUE VALUE

    View Slide

  29. >> state, (x, y, z) =
    State.new.create_variables [:x, :y, :z]
    => [#,
    [x, y, z]]
    >> goal = Goal.equal(x, 5)
    => #>
    >> states = goal.pursue_in(state)
    => #:each>
    >> states.next
    => #5}>
    >> _.values
    => {x=>5}
    >> states.next
    StopIteration: iteration reached an end

    View Slide

  30. def Goal.equal(a, b)
    Goal.new do |state|
    state = state.unify(a, b)
    Enumerator.new do |yielder|
    yielder.yield state if state
    end
    end
    end

    View Slide

  31. WITH VARIABLES
    GOAL
    X Y …

    View Slide

  32. >> goal = Goal.with_variables { |x| Goal.equal(x, 5) }
    => #>
    >> states = goal.pursue_in(State.new)
    => #:each>
    >> state = states.first
    => #5}>
    >> state.values
    => {x=>5}

    View Slide

  33. def Goal.with_variables(&block)
    names = block.parameters.
    map { |type, name| name }
    Goal.new do |state|
    state, variables =
    state.create_variables(names)
    goal = block.call(*variables)
    goal.pursue_in state
    end
    end

    View Slide

  34. COMBINING
    GOALS

    View Slide

  35. EITHER
    GOAL GOAL

    View Slide

  36. >> goal = Goal.with_variables { |x|
    Goal.either(Goal.equal(x, 5), Goal.equal(x, 6))
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => #:each>
    >> states.next.values
    => {x=>5}
    >> states.next.values
    => {x=>6}

    View Slide

  37. def Goal.either(first_goal, second_goal)
    Goal.new do |state|
    first_stream = first_goal.pursue_in(state)
    second_stream = second_goal.pursue_in(state)
    first_stream.interleave_with(second_stream)
    end
    end

    View Slide

  38. >> letters = 'a'.upto('z')
    => #
    >> numbers = 1.upto(10)
    => #
    >> letters.interleave_with(numbers).entries
    => ["a", 1, "b", 2, "c", 3, "d", 4, "e", 5,
    "f", 6, "g", 7, "h", 8, "i", 9, "j",
    10, "k", "l", "m", "n", "o", "p", "q",
    "r", "s", "t", "u", "v", "w", "x", "y",
    "z"]

    View Slide

  39. >> letters = ['a', 'b', 'c'].cycle
    => #
    >> numbers = 1.upto(Float::INFINITY)
    => #
    >> letters.interleave_with(numbers).take(50)
    => ["a", 1, "b", 2, "c", 3, "a", 4, "b", 5,
    "c", 6, "a", 7, "b", 8, "c", 9, "a", 10,
    "b", 11, "c", 12, "a", 13, "b", 14, "c",
    15, "a", 16, "b", 17, "c", 18, "a", 19,
    "b", 20, "c", 21, "a", 22, "b", 23, "c",
    24, "a", 25]

    View Slide

  40. class Enumerator
    def interleave_with(other)
    enumerators = self, other
    Enumerator.new do |yielder|
    until enumerators.empty?
    loop do
    enumerator = enumerators.shift
    yielder.yield enumerator.next
    enumerators.push enumerator
    end
    end
    end
    end
    end

    View Slide

  41. BOTH
    GOAL GOAL

    View Slide

  42. >> goal = Goal.with_variables { |x, y|
    Goal.both(
    Goal.equal(x, 5),
    Goal.equal(y, 7)
    )
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => ##:each>
    >> states.next.values
    => {x=>5, y=>7}

    View Slide

  43. >> goal = Goal.with_variables { |a, b|
    Goal.both(
    Goal.equal(a, 7),
    Goal.either(
    Goal.equal(b, 5),
    Goal.equal(b, 6)
    )
    )
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => #:each>
    >> states.next.values
    => {a=>7, b=>5}
    >> states.next.values
    => {a=>7, b=>6}

    View Slide

  44. >> goal = Goal.with_variables { |x|
    Goal.both(
    Goal.equal(1, x),
    Goal.equal(x, 2)
    )
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => ##:each>
    >> states.next.values
    StopIteration: iteration reached an end

    View Slide

  45. def Goal.both(first_goal, second_goal)
    Goal.new do |state|
    states = first_goal.pursue_in(state)
    second_goal.pursue_in_each(states)
    end
    end

    View Slide

  46. class Goal
    def pursue_in_each(states)
    Enumerator.new do |yielder|
    results = pursue_in(states.next)
    results =
    results.interleave_with(pursue_in_each(states))
    results.each do |state|
    yielder.yield state
    end
    end
    end
    end

    View Slide

  47. WITH
    VARIABLES
    G
    X Y …
    EQUAL
    V V
    STATE
    VARIABLE
    EITHER
    G G
    BOTH
    G G

    View Slide

  48. µKANREN

    View Slide

  49. WHAT CAN
    IT DO?

    View Slide

  50. >> pair = Pair.new(5, 9)
    => (5, 9)
    >> pair.left
    => 5
    >> pair.right
    => 9

    View Slide

  51. Pair = Struct.new(:left, :right) do
    def inspect
    "(#{left.inspect}, #{right.inspect})"
    end
    end

    View Slide

  52. >> goal = Goal.with_variables { |x, y|
    Goal.equal(
    Pair.new(3, x),
    Pair.new(y, Pair.new(5, y))
    )
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => #:each>
    >> state = states.first
    => #@values={y=>3, x=>(5, 3)}>
    >> state.values
    => {y=>3, x=>(5, 3)}

    View Slide

  53. class State
    def unify(a, b)
    a, b = value_of(a), value_of(b)
    if a == b
    self
    elsif a.is_a?(Variable)
    assign_values a => b
    elsif b.is_a?(Variable)
    assign_values b => a
    end
    end
    end

    View Slide

  54. end
    end
    end
    elsif a.is_a?(Pair) && b.is_a?(Pair)
    state = unify(a.left, b.left)
    state.unify(a.right, b.right) if state
    class State
    def unify(a, b)
    a, b = value_of(a), value_of(b)
    if a == b
    self
    elsif a.is_a?(Variable)
    assign_values a => b
    elsif b.is_a?(Variable)
    assign_values b => a

    View Slide

  55. else
    key
    end
    end
    end
    class State
    def value_of(key)
    if values.has_key?(key)
    value_of values.fetch(key)

    View Slide

  56. else
    key
    end
    end
    end
    class State
    def value_of(key)
    if values.has_key?(key)
    value_of values.fetch(key)
    elsif key.is_a?(Pair)
    Pair.new(
    value_of(key.left),
    value_of(key.right)
    )

    View Slide

  57. class State
    def results(n)
    variables.first(n).
    map { |variable| value_of(variable) }
    end
    def result
    results(1).first
    end
    end

    View Slide

  58. >> state.values
    => {y=>3, x=>(5, 3)}
    >> state.variables
    => [x, y]
    >> state.results 2
    => [(5, 3), 3]
    >> state.result
    => (5, 3)

    View Slide

  59. LISTS

    View Slide

  60. ['a', 'b', 'c']
    ('a', ('b', ('c', •)))

    View Slide

  61. EMPTY_LIST = :empty
    def to_list(array)
    if array.empty?
    EMPTY_LIST
    else
    first, *rest = array
    Pair.new(first, to_list(rest))
    end
    end

    View Slide

  62. >> to_list ['a', 'b', 'c']
    => ('a', ('b', ('c', :empty)))

    View Slide

  63. def from_list(list)
    if list == EMPTY_LIST
    []
    else
    first, rest = list.left, list.right
    [first, *from_list(rest)]
    end
    end

    View Slide

  64. >> from_list \
    Pair.new('a',
    Pair.new('b',
    Pair.new('c', EMPTY_LIST)
    )
    )
    => ['a', 'b', 'c']

    View Slide

  65. >> goal = Goal.with_variables { |x, y, z|
    Goal.equal(
    to_list([x, 2, z]),
    to_list([1, y, 3])
    )
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => ##:each>
    >> states.next.values
    => {x=>1, y=>2, z=>3}

    View Slide

  66. def append(a, b, c)
    Goal.either(
    Goal.both(
    Goal.equal(a, EMPTY_LIST),
    Goal.equal(b, c)
    ),
    Goal.with_variables { |first, rest_of_a, rest_of_c|
    Goal.both(
    Goal.both(
    Goal.equal(a, Pair.new(first, rest_of_a)),
    Goal.equal(c, Pair.new(first, rest_of_c))
    ),
    append(rest_of_a, b, rest_of_c)
    )
    }
    )
    end

    View Slide

  67. >> goal = Goal.with_variables { |x|
    append(
    to_list(['h', 'e']),
    to_list(['l', 'l', 'o']),
    x
    )
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => #:each>
    >> state.next.result
    => ("h", ("e", ("l", ("l", ("o", :empty)))))
    >> from_list _
    => ["h", "e", "l", "l", "o"]

    View Slide

  68. >> goal = Goal.with_variables { |x|
    append(
    x,
    to_list(['l', 'o']),
    to_list(['h', 'e', 'l', 'l', 'o'])
    )
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => #:each>
    >> from_list states.next.result
    => ["h", "e", "l"]

    View Slide

  69. >> goal = Goal.with_variables { |x, y|
    append(
    x,
    y,
    to_list(['h', 'e', 'l', 'l', 'o'])
    )
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => #:each>
    >> states.next.results(2)
    => [:empty,
    ("h", ("e", ("l", ("l", ("o", :empty)))))]
    >> _.map { |list| from_list(list) }
    => [[], ["h", "e", "l", "l", "o"]]

    View Slide

  70. >> states.next.results(2).
    map { |list| from_list(list) }
    => [["h", "e"], ["l", "l", "o"]]
    >> states.next.results(2).
    map { |list| from_list(list) }
    => [["h"], ["e", "l", "l", "o"]]
    >> states = goal.pursue_in(State.new)
    => #:each>
    >> states.next.results(2)
    => [:empty,
    ("h", ("e", ("l", ("l", ("o", :empty)))))]
    >> _.map { |list| from_list(list) }
    => [[], ["h", "e", "l", "l", "o"]]

    View Slide

  71. >> states = goal.pursue_in(State.new)
    => #:each>
    >> states.each do |state|
    p state.results(2).map { |list| from_list(list) }
    end
    [[], ["h", "e", "l", "l", "o"]]
    [["h"], ["e", "l", "l", "o"]]
    [["h", "e"], ["l", "l", "o"]]
    [["h", "e", "l"], ["l", "o"]]
    [["h", "e", "l", "l"], ["o"]]
    [["h", "e", "l", "l", "o"], []]
    => nil

    View Slide

  72. NUMBERS

    View Slide

  73. 3
    (+, (+, (+, •)))

    View Slide

  74. INC, ZERO = :+, :z
    def to_peano(number)
    if number.zero?
    ZERO
    else
    Pair.new(INC, to_peano(number - 1))
    end
    end

    View Slide

  75. >> to_peano 3
    => (:+, (:+, (:+, :z)))

    View Slide

  76. def from_peano(peano)
    if peano == ZERO
    0
    else
    from_peano(peano.right) + 1
    end
    end

    View Slide

  77. >> from_peano \
    Pair.new(INC,
    Pair.new(INC,
    Pair.new(INC, ZERO)
    )
    )
    => 3

    View Slide

  78. def add(x, y, z)
    Goal.either(
    Goal.both(
    Goal.equal(x, ZERO),
    Goal.equal(y, z)
    ),
    Goal.with_variables { |smaller_x, smaller_z|
    Goal.both(
    Goal.both(
    Goal.equal(x, Pair.new(INC, smaller_x)),
    Goal.equal(z, Pair.new(INC, smaller_z))
    ),
    add(smaller_x, y, smaller_z)
    )
    }
    )
    end

    View Slide

  79. >> goal = Goal.with_variables { |x|
    add(
    to_peano(5),
    to_peano(3),
    x
    )
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => #:each>
    >> states.next.result
    => (:+, (:+, (:+, (:+, (:+, (:+, (:+, (:+,
    :z))))))))
    >> from_peano _
    => 8

    View Slide

  80. >> goal = Goal.with_variables { |x|
    add(
    x,
    to_peano(3),
    to_peano(8)
    )
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => #:each>
    >> from_peano states.next.result
    => 5

    View Slide

  81. >> goal = Goal.with_variables { |x, y|
    add(
    x,
    y,
    to_peano(8)
    )
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => #:each>
    >> states.each do |state|
    p state.results(2).map { |peano|
    from_peano(peano)
    }
    end
    [0, 8]

    View Slide

  82. >> states = goal.pursue_in(State.new)
    => #:each>
    >> states.each do |state|
    p state.results(2).map { |peano|
    from_peano(peano)
    }
    end
    [0, 8]
    [1, 7]
    [2, 6]
    [3, 5]
    [4, 4]
    [5, 3]
    [6, 2]
    [7, 1]
    [8, 0]
    => nil

    View Slide

  83. def multiply(x, y, z)
    Goal.either(
    Goal.both(
    Goal.equal(x, ZERO),
    Goal.equal(z, ZERO)
    ),
    Goal.with_variables { |smaller_x, smaller_z|
    Goal.both(
    Goal.both(
    Goal.equal(x, Pair.new(INC, smaller_x)),
    add(smaller_z, y, z)
    ),
    multiply(smaller_x, y, smaller_z)
    )
    }
    )
    end

    View Slide

  84. >> goal = Goal.with_variables { |x|
    multiply(
    to_peano(3),
    to_peano(8),
    x
    )
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => #:each>
    >> states.next.result
    => (:+, (:+, (:+, (:+, (:+, (:+, (:+, (:+,
    (:+, (:+, (:+, (:+, (:+, (:+, (:+, (:+,
    (:+, (:+, (:+, (:+, (:+, (:+, (:+, (:+,
    :z))))))))))))))))))))))))
    >> from_peano _
    => 24

    View Slide

  85. >> goal = Goal.with_variables { |x, y|
    multiply(
    x,
    y,
    to_peano(24)
    )
    }
    => #>
    >> states = goal.pursue_in(State.new)
    => #:each>
    >> states.take(8).each do |state|
    p state.results(2).map { |peano|
    from_peano(peano)
    }
    end
    [1, 24]
    [2, 12]
    [3, 8]

    View Slide

  86. >> states = goal.pursue_in(State.new)
    => #:each>
    >> states.take(8).each do |state|
    p state.results(2).map { |peano|
    from_peano(peano)
    }
    end
    [1, 24]
    [2, 12]
    [3, 8]
    [4, 6]
    [6, 4]
    [8, 3]
    [12, 2]
    [24, 1]
    => nil

    View Slide

  87. BE
    DECLARATIVE

    View Slide

  88. PROGRAMMER
    (A HUMAN)
    SOLVER
    (A MACHINE)
    HOW
    WHAT

    View Slide

  89. THANKS.
    @tomstuart
    http://codon.com/hello-declarative-world

    View Slide