2. Declarative Programming Techniques

"The nice thing about declarative programming is that you can write a specification and run it as a program. The nasty thing about declarative programming is that some clear specifications make incredibly bad programs. The hope of declarative programming is that you can move from a specification to a reasonable program without leaving the language."
- The Craft of Prolog, Richard O’Keefe

1. Iterative computation

General schema

Newton's method

fun {Sqrt X}
    Guess = 1.0
in
    {SqrtIter Guess X}
end

fun {SqrtIter Guess X}
    if {GoodEnough Guess X} then Guess
    else
        {SqrtIter {Improve Guess X} X}
    end
end

fun {Improve Guess X}
    (Guess + X/Guess) / 2.0
end

fun {GoodEnough Guess X}
    {Abs X-Guess*Guess}/X < 0.00001
end

fun {Abs X} if X<0.0 then ~X else X end end

Assignment 1

declare
fun {Abs X}
    if X<0 then ~X
    else X
    end
end

Assignment 2

Using local procedures

Way 1

local
    fun {Improve Guess X} ... end
    fun {GoodEnough Guess X} ... end
    fun {SqrtIter Guess X}
        if {GoodEnough Guess X} then Guess
        else
           {SqrtIter {Improve Guess X} X}
        end
    end
end
in
    fun {Sqrt X}
        Guess=1.0
    in
        {SqrtIter Guess X}
    end
end

Way 2

fun {Sqrt X}
    fun {SqrtIter Guess X}
        fun {Improve Guess X} ... end
        fun {GoodEnough Guess X} ... end
    in
        if {GoodEnough Guess X} then Guess
        else
            {SqrtIter {Improve Guess X} X}
        end
    end
    Guess=1.0
in
    {SqrtIter Guess X}
end

Way 2, simplified

fun {Sqrt X}
    fun {SqrtIter Guess}
        fun {Improve} ... end
        fun {GoodEnough} ... end
    in
        if {GoodEnough} then Guess
        else
            {SqrtIter {Improve}}
        end
    end
    Guess=1.0
in
    {SqrtIter Guess}
end

Final definition

fun {Sqrt X}
    fun {Improve Guess} ... end
    fun {GoodEnough Guess} ... end
    fun {SqrtIter Guess}
        if {GoodEnough Guess} then Guess
        else
            {SqrtIter {Improve Guess}}
        end
    end
    Guess=1.0
in
    {SqrtIter Guess}
end

Control abstraction

fun {Iterate S IsDone Transform}
    if {IsDone S} then S
    else S1 in
        S1 = {Transform S}
        {Iterate S1 IsDone Transform}
    end
end
fun {Sqrt X}
    { Iterate
        1.0
        fun {$ G} {Abs X-G*G}/X < 0.00001 end
        fun {$ G} (G+X/G)/2.0 end }
end

Assignment 3

2. Recursive computation


Print Gallery (M. C. Escher)

fun {Fact N}
    if N==0 then 1
    elseif N>0 then N*{Fact N-1}
    else raise domainError end
    end
end

Growing stack size

Converting a recursive to an iterative computation

fun {Fact N}
    fun {FactIter N A}
        if N==0 then A
        elseif N>0 then {FactIter N-1 A*N}
        else raise domainError end
        end
    end
in
    {FactIter N 1}
end

3. Programming with recursion

Programming with lists

Thinking recursively

fun {Length Ls}
    case Ls
    of nil then 0
    [] _|Lr then 1+{Length Lr}
    end
end
{Browse {Length [a b c]}}
fun {Append Ls Ms}
    case Ls
    of nil then Ms
    [] X|Lr then X|{Append Lr Ms}
    end
end

Exercise 1

Exercise 2

Naive recursive definition

fun {Reverse Xs}
    case Xs
    of nil then nil
    [] X|Xr then
        {Append {Reverse Xr} [X]}
    end
end

Converting recursive to iterative computations

fun {Length Xs}
    case Xs of
    nil then 0
    [] _|Xr then
        1+{Length Xr}
    end
end
local
    fun {IterLength I Ys}
        case Ys
        of nil then I
        [] _|Yr then
            {IterLength I+1 Yr}
        end
    end
in
    fun {Length Xs}
        {IterLength 0 Xs}
    end
end
local
    fun {IterReverse Rs Ys}
        case Ys
        of nil then Rs
        [] Y|Yr then
            {IterReverse Y|Rs Yr}
        end
    end
in
    fun {Reverse Xs}
        {IterReverse nil Xs}
    end
end

Assignment 4

Assignment 5

fun {Append Ls Ms}
    case Ls
    of nil then Ms
    [] X|Lr then X|{Append Lr Ms}
    end
end

Sorting with mergesort

proc {Split Xs ?Ys ?Zs}
    case Xs
    of nil then Ys=nil Zs=nil
    [] [X] then Ys=[X] Zs=nil
    [] X1|X2|Xr then Yr Zr in
        Ys=X1|Yr
        Zs=X2|Zr
        {Split Xr Yr Zr}
    end
end

fun {Merge Xs Ys}
    case Xs # Ys
    of nil # Ys then Ys
    [] Xs # nil then Xs
    [] (X|Xr) # (Y|Yr) then
        if X<Y then X|{Merge Xr Ys}
        else Y|{Merge Xs Yr}
        end
    end
end

fun {MergeSort Xs}
    case Xs
    of nil then nil
    [] [X] then [X]
    else Ys Zs in
        {Split Xs Ys Zs}
        {Merge {MergeSort Ys} {MergeSort Zs}}
    end
end

Accumulators

Mergesort with an accumulator

fun {MergeSort Xs}
    fun {MergeSortAcc L1 N}
        if N==0 then
            nil # L1
        elseif N==1 then
            [L1.1] # L1.2
        elseif N>1 then
            NL = N div 2
            NR = N-NL
            Ys # L2 = {MergeSortAcc L1 NL}
            Zs # L3 = {MergeSortAcc L2 NR}
        in
            {Merge Ys Zs} # L3
        end
    end
in
    {MergeSortAcc Xs {Length Xs}}.1
end

Queues

A naive queue

proc {ButLast L ?X ?L1}
    case L
    of [Y] then X=Y L1=nil
    [] Y|L2 then L3 in
        L1 = Y|L3
        {ButLast L2 X L3}
    end
end

Amortized constant-time ephemeral queue

fun {NewQueue} q(nil nil) end

fun {Check Q}
    case Q of q(nil R) then q({Reverse R} nil) else Q end
end

fun {Insert Q X}
    case Q of q(F R) then {Check q(F X|R)} end
end

fun {Delete Q X}
    case Q of q(F R) then F1 in F=X|F1 {Check q(F1 R)} end
end

fun {IsEmpty Q}
    case Q of q(F R) then F==nil end
end
Q1 = {NewQueue}         % Q1 = q(nil nil)
Q2 = {Insert Q1 1}      % Q2 = q([1] nil)
Q3 = {Insert Q2 2}      % Q3 = q([1] [2])
Q4 = {Insert Q3 3}      % Q4 = q([1] [3 2])
Q5 = {Insert Q4 4}      % Q5 = q([1] [4 3 2])
Q6 = {Insert Q5 5}      % Q6 = q([1] [5 4 3 2])

Q7 = {Delete Q6 X}      % Q7 = q([2 3 4 5] nil)  X=1
Q8 = {Delete Q7 X}      % Q8 = q([3 4 5] nil)    X=2

Q9  = {Insert Q8 6}     % Q9  = q([3 4 5] [6])
Q10 = {Insert Q9 7}     % Q10 = q([3 4 5] [7 6])

Q11 = {Delete Q10 X}    % Q11 = q([4 5] [7 6])   X=3

Assignment 6

Trees

Ordered binary tree

Storing information in trees

fun {Lookup X T}
    case T
    of leaf then notfound
    [] tree(Y V T1 T2) andthen X==Y then found(V)
    [] tree(Y V T1 T2) andthen X<Y  then {Lookup X T1}
    [] tree(Y V T1 T2) andthen X>Y  then {Lookup X T2}
    end
end
fun {Insert X V T}
    case T
    of leaf then tree(X V leaf leaf)
    [] tree(Y W T1 T2) andthen X==Y then tree(X V T1 T2)
    [] tree(Y W T1 T2) andthen X<Y  then tree(Y W {Insert X V T1} T2)
    [] tree(Y W T1 T2) andthen X>Y  then tree(Y W T1 {Insert X V T2})
    end
end

Deletion and tree reorganizing

fun {Delete X T}
    case T
    of leaf then leaf
    [] tree(Y W T1 T2) andthen X==Y then leaf
    [] tree(Y W T1 T2) andthen X<Y  then tree(Y W {Delete X T1} T2)
    [] tree(Y W T1 T2) andthen X>Y  then tree(Y W T1 {Delete X T2})
    end
end


Deleting node Y when one subtree is a leaf (easy case)


Deleting node Y when neither subtree is a leaf (hard case)

fun {RemoveSmallest T}
    case T
    of leaf then none
    [] tree(Y V T1 T2) then
        case {RemoveSmallest T1}
        of none then Y#V#T2
        [] Yp#Vp#Tp then Yp#Vp#tree(Y V Tp T2)
        end
    end
end
fun {Delete X T}
    case T
    of leaf then leaf
    [] tree(Y W T1 T2) andthen X==Y then
        case {RemoveSmallest T2}
        of none then T1
        [] Yp#Vp#Tp then tree(Yp Vp T1 Tp)
        end
    [] tree(Y W T1 T2) andthen X<Y then
        tree(Y W {Delete X T1} T2)
    [] tree(Y W T1 T2) andthen X>Y then
        tree(Y W T1 {Delete X T2})
    end
end

Tree traversal

proc {DFS T}
    case T
    of leaf then skip
    [] tree(Key Val L R) then
        {DFS L}
        {Browse Key#Val}
        {DFS R}
    end
end
proc {DFSAcc T S1 ?Sn}
    case T
    of leaf then Sn=S1
    [] tree(Key Val L R) then S2 S3 in
        {DFSAcc L S1 S2}
        S3 = Key#Val|S2
        {DFSAcc R S3 Sn}
    end
end
proc {BFS T}
    fun {TreeInsert Q T}
        if T\=leaf then {Insert Q T} else Q end
    end
    
    proc {BFSQueue Q1}
        if {IsEmpty Q1} then skip
        else
            X Q2 = {Delete Q1 X}
            tree(Key Val L R) = X
        in
            {Browse Key#Val}
            {BFSQueue {TreeInsert {TreeInsert Q2 L} R}}
        end
    end
in
    {BFSQueue {TreeInsert {NewQueue} T}}
end

Exercise 3

Assignment 7

4. Higher-order programming

Basic operations

Procedural abstraction

Genericity

fun {SumList L}
    case L
    of nil then 0
    [] X|L1 then X+{SumList L1}
    end
end
fun {FoldR L F U}
    case L
    of nil then U
    [] X|L1 then {F X {FoldR L1 F U}}
    end
end
fun {SumList L}
    {FoldR L fun {$ X Y} X+Y end 0}
end

Exercise 4

Instantiation

fun {MakeSort F}
    fun {$ L}
        {Sort L F}
    end
end

Exercise 5

Embedding

5. Abstract data types

A declarative stack

fun {NewStack} nil end
fun {Push S E} E|S end
fun {Pop S E} case S of X|S1 then E=X S1 end end
fun {IsEmpty S} S==nil end

Exercise 6

A declarative dictionary

List-based implementation

Tree-based implementation

State-based implementation


< ^ >