\( % Latex Preamble definitions here (mostly usepackage) \usepackage% %[dvipsnames] {xcolor} % make sure this is before the loading font packages \newcommand\hmmax{0} \newcommand\bmmax{0} \usepackage{amssymb} \usepackage{amsmath} \usepackage{mathtools} \usepackage %[dvipsnames] {graphicx} \usepackage{float} %\usepackage[numbers]{natbib} \usepackage[document]{ragged2e} % % enumitem doesn't seem to work with beamer %\usepackage[inline]{enumitem} \usepackage{wrapfig} \usepackage{stackrel} % extensible arrows \usepackage{extpfeil} % \usepackage{trfrac} \usepackage{amsthm} \usepackage{tikz} \usepackage{tikz-cd} \usepackage{tikz} \usepackage{tikz-qtree} \usetikzlibrary{automata, positioning, arrows, shapes.geometric} \usepackage{turnstile} \usepackage{comment} %https://tex.stackexchange.com/questions/21334/is-there-a-package-that-has-the-clockwise-gapped-circle-arrow-in-it % \usepackage{mathbx} \usepackage{datetime} \usepackage{datetime2} %% Also See %% http://u.cs.biu.ac.il/~tsaban/Pdf/LaTeXCommonErrs.pdf %% for general tips \usepackage{listings} \usepackage{subfigure} \usepackage{bm} \usepackage{amsfonts} %% - also included by amssymb \usepackage{mathpazo} %% - because the OP uses mathpazo, optional %\usepackage{tufte-latex} \usepackage{comment} \usepackage{mathtools} \usepackage{bussproofs} \usepackage{hyperref} %\usepackage{cleveref} \)
\( %% Your math definitions here % \newcommand{\alphaequiv}{{\underset{\raise 0.7em\alpha}{=}}} \newcommand{\yields}{\Rightarrow} \newcommand{\derives}{\overset{*}{\yields}} \newcommand{\alphaequiv}{=_{\alpha}} \newcommand{\tto}[2]{{\overset{#1}{\underset{#2}{\longrightarrow}}}} \newcommand{\transitsto}[2]{{\overset{#1}{\underset{#2}{\longrightarrow}}}} \newcommand{\xtransitsto}[2]{{\underset{#2}{\xrightarrow{#1}}}} \newcommand{\xtransitsfrom}[2]{{\underset{#2}{\xleftarrow{#1}}}} \newcommand{\xto}[2]{{\xtransitsto{#1}{#2}}} \newcommand{\xfrom}[2]{{\xtransitsfrom{#1}{#2}}} \newcommand{\xreaches}[2]{{\underset{#2}{\xtwoheadrightarrow{#1}}}} \newcommand{\reaches}[2]{{\underset{#2}{\xtwoheadrightarrow{#1}}}} %\newcommand{\reaches}[2]{{\overset{#1}{\underset{#2}{\twoheadrightarrow}}}} %\newcommand{\goesto}[2]{\transitsto{#1}{#2}} %\newcommand{\betareducesto}{{\underset{\beta}{\rightarrow}}} \newcommand{\betareducesto}{\rightarrow_{\beta}} %\newcommand{\etareducesto}{{\underset{\eta}{\rightarrow}}} \newcommand{\etareducesto}{\rightarrow_{\eta}} %\newcommand{\betaetareducesto}{{\underset{\beta\ \eta}{\rightarrow}}} \newcommand{\betaetareducesto}{\rightarrow_{\beta\eta}} \newcommand{\preducesto}{\rhd} \newcommand{\psimplifiesto}{\stackrel{\scriptstyle{*}}{\rhd}} \newcommand{\lreducesto}{\rightsquigarrow} \newcommand{\lsimplifiesto}{\stackrel{\scriptstyle{*}}{\lreducesto}} \newcommand{\rewritesto}{\hookrightarrow} \newcommand{\goesto}[1]{\stackrel{#1}{\rightarrow}} \newcommand{\xgoesto}[1]{\xrightarrow{#1}} \newcommand{\reducesto}{\stackrel{}{\rightarrow}} \newcommand{\simplifiesto}{\stackrel{\scriptstyle{*}}{\rightarrow}} \newcommand{\connected}[1]{\stackrel{#1}{\leftrightarrow}} \newcommand{\joins}{\downarrow} \newcommand{\evaluatesto}{\Longrightarrow} %\newcommand{\lit}[1]{\hbox{\sf{#1}}} \newcommand{\lit}[1]{{\sf{#1}}} \newcommand{\true}{\lit{true}} \newcommand{\false}{\lit{false}} \def\Z{\mbox{${\mathbb Z}$}} \def\N{\mbox{${\mathbb N}$}} \def\P{\mbox{${\mathbb P}$}} \def\R{\mbox{${\mathbb R}$}} \def\T{\mbox{${\mathbb T}$}} \newcommand{\Rp}{{\mathbb{R}}^+} \def\Bool{\mbox{${\mathbb B}$}} \def\Q{\mbox{${\mathbb Q}$}} \def\sA{\mbox{${\cal A}$}} \def\sB{\mbox{${\cal B}$}} \def\sC{\mbox{${\cal C}$}} \def\sD{\mbox{${\cal D}$}} \def\sF{\mbox{${\cal F}$}} \def\sG{\mbox{${\cal G}$}} \def\sL{\mbox{${\cal L}$}} \def\sP{\mbox{${\cal P}$}} \def\sM{\mbox{${\cal M}$}} \def\sN{\mbox{${\cal N}$}} \def\sR{\mbox{${\cal R}$}} \def\sS{\mbox{${\cal S}$}} \def\sO{\mbox{${\cal O}$}} \def\sT{\mbox{${\cal T}$}} \def\sU{\mbox{${\cal U}$}} \def\th{\mbox{$\widetilde{h}$}} \def\tg{\mbox{$\widetilde{g}$}} \def\tP{\mbox{$\widetilde{P}$}} \def\norm{\mbox{$\parallel$}} \def\osum{${{\bigcirc}}\!\!\!\!{\rm s}~$} \def\pf{\noindent {\bf Proof}~~} \def\exec{\mathit{exec}} \def\Act{\mathit{A\!ct}} \def\Traces{\mathit{Traces}} \def\Spec{\mathit{Spec}} \def\uns{\mathit{unless}} \def\ens{\mathit{ensures}} \def\lto{\mathit{leads\!\!-\!\!to}} \def\a{\alpha} \def\b{\beta} \def\c{\gamma} \def\d{\delta} \def\sP{\mbox{${\cal P}$}} \def\sM{\mbox{${\cal M}$}} \def\sA{\mbox{${\cal A}$}} \def\sB{\mbox{${\cal B}$}} \def\sC{\mbox{${\cal C}$}} \def\sI{\mbox{${\cal I}$}} \def\sS{\mbox{${\cal S}$}} \def\sD{\mbox{${\cal D}$}} \def\sF{\mbox{${\cal F}$}} \def\sG{\mbox{${\cal G}$}} \def\sR{\mbox{${\cal R}$}} \def\tg{\mbox{$\widetilde{g}$}} \def\ta{\mbox{$\widetilde{a}$}} \def\tb{\mbox{$\widetilde{b}$}} \def\tc{\mbox{$\widetilde{c}$}} \def\tx{\mbox{$\widetilde{x}$}} \def\ty{\mbox{$\widetilde{y}$}} \def\tz{\mbox{$\widetilde{z}$}} \def\tI{\mbox{$\widetilde{I}$}} \def\norm{\mbox{$\parallel$}} \def\sL{\mbox{${\cal L}$}} \def\sM{\mbox{${\cal M}$}} \def\sN{\mbox{${\cal N}$}} \def\th{\mbox{$\widetilde{h}$}} \def\tg{\mbox{$\widetilde{g}$}} \def\tP{\mbox{$\widetilde{P}$}} \def\norm{\mbox{$\parallel$}} \def\to{\rightarrow} \def\ov{\overline} \def\gets{\leftarrow} \def\too{\longrightarrow} \def\To{\Rightarrow} %\def\points{\mapsto} %\def\yields{\mapsto^{*}} \def\un{\underline} \def\vep{$\varepsilon$} \def\ep{$\epsilon$} \def\tri{$\bigtriangleup$} \def\Fi{$F^{\infty}$} \def\Di{\Delta^{\infty}} \def\ebox\Box \def\emp{\emptyset} \def\leadsto{\rightharpoondown^{*}} \newcommand{\benum}{\begin{enumerate}} \newcommand{\eenum}{\end{enumerate}} \newcommand{\bdes}{\begin{description}} \newcommand{\edes}{\end{description}} \newcommand{\bt}{\begin{theorem}} \newcommand{\et}{\end{theorem}} \newcommand{\bl}{\begin{lemma}} \newcommand{\el}{\end{lemma}} % \newcommand{\bp}{\begin{prop}} % \newcommand{\ep}{\end{prop}} \newcommand{\bd}{\begin{defn}} \newcommand{\ed}{\end{defn}} \newcommand{\brem}{\begin{remark}} \newcommand{\erem}{\end{remark}} \newcommand{\bxr}{\begin{exercise}} \newcommand{\exr}{\end{exercise}} \newcommand{\bxm}{\begin{example}} \newcommand{\exm}{\end{example}} \newcommand{\beqa}{\begin{eqnarray*}} \newcommand{\eeqa}{\end{eqnarray*}} \newcommand{\bc}{\begin{center}} \newcommand{\ec}{\end{center}} \newcommand{\bcent}{\begin{center}} \newcommand{\ecent}{\end{center}} % \newcommand{\la}{\langle} % \newcommand{\ra}{\rangle} \newcommand{\bcor}{\begin{corollary}} \newcommand{\ecor}{\end{corollary}} \newcommand{\bds}{\begin{defns}} \newcommand{\eds}{\end{defns}} \newcommand{\brems}{\begin{remarks}} \newcommand{\erems}{\end{remarks}} \newcommand{\bxrs}{\begin{exercises}} \newcommand{\exrs}{\end{exercises}} \newcommand{\bxms}{\begin{examples}} \newcommand{\exms}{\end{examples}} \newcommand{\bfig}{\begin{figure}} \newcommand{\efig}{\end{figure}} \newcommand{\set}[1]{\{#1\}} \newcommand{\pair}[1]{\langle #1\rangle} \newcommand{\tuple}[1]{\langle #1\rangle} \newcommand{\size}[1]{| #1 |} \newcommand{\union}{\cup} \newcommand{\Union}{\bigcup} \newcommand{\intersection}{\cap} \newcommand{\Intersection}{\bigcap} \newcommand{\B}{\textbf{B}} %\newcommand{\be}[2]{\begin{equation} \label{#1} \tag{#2} \end{equation}} \newcommand{\abs}[1]{{\lvert}#1{\rvert}} \newcommand{\id}[1]{\mathit{#1}} \newcommand{\pfun}{\rightharpoonup} \newcommand{\ra}[1]{\kern-1.5ex\xrightarrow{\ \ #1\ \ }\phantom{}\kern-1.5ex} \newcommand{\ras}[1]{\kern-1.5ex\xrightarrow{\ \ \smash{#1}\ \ }\phantom{}\kern-1.5ex} \newcommand{\da}[1]{\bigg\downarrow\raise.5ex\rlap{\scriptstyle#1}} \newcommand{\ua}[1]{\bigg\uparrow\raise.5ex\rlap{\scriptstyle#1}} % \newcommand{\lift}[1]{#1_{\bot}} \newcommand{\signal}[1]{\tilde{#1}} \newcommand{\ida}{\stackrel{{\sf def}}{=}} \newcommand{\eqn}{\doteq} \newcommand{\deduce}[1]{\sststile{#1}{}} %% These don't sit very well with MathJax %% so we don't plan to use theorem like environments %% in org documents. %% instead we plan to use headings with %% 1. property drawers with a CLASS property identifying %% the environment %% 2. A tag with the same name as the CLASS property %% In LaTeX export, these turn into (sub)sections. %% See http://u.cs.biu.ac.il/~tsaban/Pdf/LaTeXCommonErrs.pdf %% \newtheorem{prop}[thm]{Proposition} %% \theoremstyle{plain}%default %% \newtheorem{theorem}{Theorem}[section] %% \newtheorem{lemma}{Lemma}[section] %% \newtheorem{corollary}{Corollary}[section] %% \newtheorem{definition}{Definition}[section] %% \newtheorem{remark}{Remark}[section] %% \newtheorem{example}{Example}[section] %% \newtheorem{exercise}{Exercise}[section] \newcommand{\less}[1]{#1_{<}} \newcommand{\pfn}{\rightharpoonup} \newcommand{\ffn}{\stackrel{{\sf fin}}{\rightharpoonup}} \newcommand{\stkout}[1]{\ifmmode\text{\sout{\ensuremath{#1}}}\else\sout{#1}\fi} % Caution: Not supported by MathJax! % ---------------------------------- % \DeclareMathSymbol{\shortminus}{\mathbin}{AMSa}{"39} % \usepackage{amsfonts} %% <- also included by amssymb % \DeclareMathSymbol{\shortminus}{\mathbin}{AMSa}{"39} \usepackage{mathpazo} %% <- because the OP uses mathpazo, optional \newcommand{\mbf}[1]{\mathbf{#1}} \newcommand{\floor}[1]{\left\lfloor #1 \right\rfloor} \newcommand{\ceil}[1]{\left\lceil #1 \right\rceil} \newcommand{\rel}{\twoheadrightarrow} \newcommand{\map}{\rightarrow} %\newcommand{\fixed}{\boldsymbol{\circlearrowleft}} \newcommand{\terminal}{\not\xto{}{}} \newcommand{\fixed}{\bm\circlearrowleft} \newcommand{\imp}{\rightarrow} \newcommand{\dimp}{\leftrightarrow} % double implication \newcommand{\lequiv}{\Longleftrightarrow} % logical equivalence \newcommand{\limplies}{\Rightarrow} \newcommand{\lxor}{\veebar} \)
UP | HOME

Functional Programming with Natural numbers and Lists

Table of Contents

1 Introduction

In this section, we look at functional programming, or programming with functions as values on their own right. A functional is a function that takes or returns functions. In mathematics, functionals are very common, although they are better known as operators. Examples: the compose operator, denoted \(f\circ g\), which takes two functions \(f\) and \(g\) and returns their composition \(\lambda x.\ f(g(x))\). Another example is the iterate operator denoted by superscripts: \(f^{i}\) is the function obtained by iterating \(f\) \(i\) times.

Functional Programming is a style of programming where one can write functions that take or return functions. Racket/Scheme is eminently suited for functional programming.

Functional Programming is useful because it lets one write programs (functions) that generate other functions. This allows one to write very general programs at a high level of abstraction and then specialise these high level programs to generate more specific programs. Several examples of this type of abstraction followed by specialisation are presented in this section.

2 Function Composition

Given two functions \(f:A\rightarrow B\) and \(g:B\rightarrow C\), the composition of \(g\) with \(f\), written \(g\circ f:\) is the function from \(A\) to \(C\) defined as \((g\circ f)(x)= g(f(x))\).

(define comp2
  (lambda (g f)
    (lambda (x)
      (g (f x)))))
(check-equal? ((comp2 (lambda (x) (* x 2))
		     (lambda (x) (+ 1 x)))
	       3)
	      8
	      "comp-1-test")
(check-equal? ((comp2 even? first) '(8 3 4))
	      #t
	      "comp-2-test")

Notice the generation of the new composite function by comp2. Thus comp2 denotes a higher-order function that takes two functions as arguments and returns a function as the result.

Thus we can write the conversion from Centigrade to Fahrenheit as a composition of two smaller functions:

(define add32
  (lambda (x) (+ 32 x)))

(define mul-with-9/5
   (lambda (x) (* 9/5 x)))

(define c->f1  ;; using comp2
  (comp2 add32 mul-with-9/5))

(check-equal?  (c->f1 0) 32 "c->f1-1")
(check-equal?  (c->f1 100) 212 "c->f1-2")
(check-equal?  (c->f1 -40) -40 "c->f1-3")

Note that although c->f1 is a function from (a number denoting) temperature to temperature, there is no reference to the temperature a formal parameter in the definition of c->f1. It has been abstracted away. Abstractions such as these result in programs that are compact, but which, without some practice, also seem inscrutable. The advantage of the compactness is, however, compelling.

3 Sequential Application of Functions

Another way to think about composition (and this is less confusing for some) is the operator $;$ often used in programming languages like C to express sequential operation.

\(f;g\) takes an \(x:A\) and returns \(g(f(x))\). Note that \(f;g = g\circ f\), but the notation \(f;g\) aligns better with the left to right reading: apply \(f\); then apply \(g\).

(define seq2
  (lambda (f g)
    (comp2 g f)))

(define c->f2 ;; using seq2
  (seq2 mul-with-9/5 add32))

(check-equal?  (c->f2 0) 32 "c->f2-1")
(check-equal?  (c->f2 100) 212 "c->f2-2")
(check-equal?  (c->f2 -40) -40 "c->f2-3")

4 Identity Function

The identity function \(\mathit{Id}\) is useful function to have around. Notice that \(f;\mathit{Id} = \mathit{Id};f = f\) for any function \(f\).

(define id
  (lambda (x) x))

5 Variable arity composition and sequentialisation: comp and seq

The composition and sequentilisation operators may be generalized to arbitrary arity:

;;; comp: [A_{n-1} -> A_n, A_{n-2} -> A_{n-1} ... A_{0} -> A_{1}] -> A_0 -> A_n
(define comp
  (lambda fs
    (cond
      [(null? fs) id]
      [else (comp2 (first fs) (apply comp (rest fs)))])))


(define sqr
  (lambda (x) (* x x)))

(check-equal? ((comp) 5) 5 "comp-1")
(check-equal? ((comp add1) 5) 6 "comp-2")
(check-equal? ((comp add1 sqr) 5) 26 "comp-3")
(check-equal? ((comp add1 sqr mul-with-9/5) 5) 82 "comp-4")

;;; seq: [A_0 -> A_1, A_1 -> A_2 ... A_{n-1} -> A_n] -> A_0 -> A_n
(define seq
  (lambda fs
    (cond
      [(null? fs) id]
      [else (seq2 (first fs) (apply seq (rest fs)))])))

(check-equal? ((seq) 5) 5 "seq-1")
(check-equal? ((seq add1) 5) 6 "seq-2")
(check-equal? ((seq add1 sqr) 5) 36 "seq-3")
(check-equal? ((seq add1 sqr mul-with-9/5) 4) 45 "seq-4")

6 Reduce

Recall the notion of a deterministic, non-blocking state machine.

\[ S = \pair{X, U, Y, f:X,U\rightarrow X, h:X\rightarrow Y} \]

Deterministic and non-blocking means that there is exactly one next state for each state \(x\) and each input \(u\) and that state is \(f(x,u)\).

Given a state \(x\in X\) and a sequence \(\overline{u}\) of inputs, we are interested in knowing what is the output of the machine after it consumes the input \(\overline{u}\). Such a function is easy to write in functional programming.

(define list-reduce1
  (lambda (f h) 
    (lambda (x us)
      (letrec ([loop 
		(lambda (us a)
		  (cond
		   [(null? us) (h a)]
		   [else (loop (rest us) (f a (first us)))]))])
	(loop us x)))))

;;; computes the length of a list
(define list-length1
  (list-reduce1 (lambda (a u)  (add1 a)) id))

(check-equal? (list-length1 0 '(a b c))
	      3 "reduce1-1")

;;; reverses a list
(define list-reverse1
  (list-reduce1 (lambda (a u) (cons u a))  id))

(check-equal? (list-reverse1 '() '(a b c))
	      '(c b a) "reduce1-2")

;;; sums the numbers in a list of numbers
(define list-sum1
  (list-reduce1 + id))

(check-equal? (list-sum1 0 '(1 5 2)) 8 "reduce1-3")

The function list-reduce1 takes the functions \(f\) and \(h\) above and returns a function that takes a state \(x\) and a sequence of inputs \(us\) and returns the output of the state reached after consuming the entire input us.

7 Linear Reduction

By abstracting the sequencing operators of the input sequence, we get the following generalized reduce operator that works on finite linear structures that includes lists and also natural numbers.

;;; [first: (seq U) -> U, rest: (seq U) -> (seq U), base?: (seq U) -> boolean?] 
;;;   -> [init: X, f: [X,U]->X, h: X->Y] -> (seq U) -> Y
(define linear-reduce
  (lambda (first rest base?)
    (lambda (f h) 
      (lambda:  (x us)
	(letrec ([loop (lambda (us x)
			 (cond
			   [(base? us) (h x)]
			   [else (loop (rest us) (f x (first us)))]))])
	  (loop us x))))))

8 List Reduction as a Special Case of Linear Reduction

From this we may easily define reduction on lists as

(define list-reduce
  (linear-reduce first rest null?))

;;; computes the length of a list
(define list-length
  (: (list-reduce (lambda (a u)  (add1 a)) id) 0))

(check-equal? (list-length '(a b c))
	      3 "list-length-1")

;;; sums the numbers in a list of numbers
(define list-sum
  (: (list-reduce + identity) 0))

(check-equal? (list-sum '(1 5 2)) 8 "list-sum-1")


;;; reverses a list
(define list-reverse
  (: (list-reduce (lambda (a u) (cons u a))  id) '()))

(check-equal? (list-reverse '(a b c))
	      '(c b a) "list-reverse-1")

;;; list-map: A->B -> list[A] -> list[B]
(define list-map
  (lambda (g)
    (: (list-reduce (lambda (ls u) (cons (g u) ls)) reverse) '())))

(check-equal? (: list-map add1 '(2 7 3)) '(3 8 4) "list-map-1")

9 Iteration as a Special Case of Linear Reduction

Natural numbers are also linear structures. A natural number \(n\) is isomorphic to a list of n objects.

(define nat-reduce
  (linear-reduce identity sub1 zero?))

;;; nat? -> nat?
(define nat-sum
  (: (nat-reduce + identity)
     0))

(check-equal?  (nat-sum 0) 0 "nat-sum-1")
(check-equal?  (nat-sum 4) 10 "nat-sum-2")

;;; nat? -> nat?
(define nat-prod ;; factorial
  (: (nat-reduce * identity)
     1))


(check-equal?  (nat-prod 0) 1 "nat-prod-1")
(check-equal?  (nat-prod 4) 24 "nat-prod-2")

;;; f:A->A -> n:nat? -> A->A
(define nat-iterate
  (lambda (f)
    (: (nat-reduce (lambda (g n) (comp2 f g)) identity)
       identity)))

(check-equal?  (: nat-iterate add1 4 3) 7 "nat-iterate-1")
(check-equal?  (: nat-iterate add1 0 3) 3 "nat-iterate-2")

10 Higher Order Reduction

Can the above programs be made more compact? The answer is yes! In the programs that follow, we will aggressively use currying, and will define functions in a curried way wherever possible and useful.

We curry the arguments f and h. Further, we also switch the arguments of f, so the signature of \(f\) is now \(f:U\rightarrow X\rightarrow X\). We also switch the order of arguments to loop.

;;; [first: (seq U) -> U, rest: (seq U) -> (seq U), base?: (seq U) -> boolean?] 
;;;   -> [f: U->X->X, h: X->Y] -> (seq U) -> X -> Y
(define linear-reduce-t1:  ;; take 1
  (lambda (first rest base?)
    (lambda: (f h) 
      (letrec ([loop (lambda: (us x)
		       (cond
			 [(base? us) (h x)]
			 [else (: loop (rest us)
				  (: f (first us) x))]))])
	       loop))))

Notice that the clause in the else part of loop's definition may be written using seq, and as a result, the x may be factored out of the definition:

;;; [first: (seq U) -> U, rest: (seq U) -> (seq U), base?: (seq U) -> boolean?] 
;;;   -> [f: U->X->X, h: X->Y] -> (seq U) -> X -> Y
(define linear-reduce: ;;; take2
  (lambda (first rest base?)
    (lambda: (f h) 
      (letrec ([loop (lambda (us)
		       (cond
			 [(base? us) h]
			 [else (seq (: (seq first f) us)
				    (: (seq rest loop) us))]))])
	loop))))

Exercise: derive the equivalence of linear-reduce and linear-reduce-t1 using the rules of eta reduction i.e., (lambda (x) (e x)) = e (provided x is not a free variable in e) and the definition of seq.

11 Curried version of basic functions

First, we defined curried versions of binary functions + and cons:

(define +:
  (lambda: (x y) (+ x y)))

(define cons:
  (lambda: (x ls) (cons x ls)))

The curried list-reduce: and various functions generated from it may now be defined as follows

(define list-reduce:
  (linear-reduce: first rest null?))

;;; computes the length of a list
(define list-length:
  (lambda (ls)
    (: list-reduce: (lambda (u) add1) id ls 0)))

(check-equal? (list-length: '(a b c))
	      3 "list-length:-1")

;;; sums the numbers in a list of numbers
(define list-sum:
  (lambda (ls)
    (: list-reduce: +: id ls 0)))

(check-equal? (list-sum: '(1 5 2)) 8 "list-sum:-1")


;;; reverses a list
(define list-reverse:
  (lambda (ls) 
    (: list-reduce: cons: id ls '())))

(check-equal? (list-reverse: '(a b c))
	      '(c b a) "list-reverse-1")


(define list-map:
  (lambda: (g ls)
    (: list-reduce: (seq g cons:) reverse ls '())))

(check-equal? (: list-map: add1 '(2 7 3)) '(3 8 4) "list-map:-1")

12 Non-determinism

Consider a non-deterministic state machine

\[S = \pair{X, U, f:U\rightarrow X\rightarrow 2^{X}, X, h:X\rightarrow X}\]

The transition relation in \(S\) is specified by the function \(f:U\rightarrow X\rightarrow 2^{X}\) and output function \(h\) is assumed identity. The corresponding reduce function nd-reduce: takes a list of inputs us and a set of states xs and returns the set of states reachable from xs via us.

(define set-mapf
  (lambda (f sets)
    (set-map sets f)))

;;; nd:reduce:-t1 : [f: U->X->set[X]] -> list[U] -> set[X] -> set[X]
(define nd-reduce:-t1
  (lambda (f)
    (: list-reduce:
       (lambda: (u xs)
	 (apply set-union (set-mapf (f u) xs)))
       id)))

The next few programs illustrate an exercise in code refactoring. Our goal is to write a version of nd-reduce that abstracts away the parameters xs and u (in that order).

We start by currying set-mapf and apply:

(define set-mapf:
  (lambda: (f sets)
    (set-map sets f)))

(define apply:
  (lambda: (fn args)
    (apply fn args)))

(define nd-reduce:-t2  ;; take 2
  (lambda (f)
    (: list-reduce:
       (lambda: (u xs)
	 (: apply: set-union (: set-mapf: (f u) xs)))
       id)))

Expanding the definition of :, we have

(define nd-reduce:-t3
  (lambda (f)
    (: list-reduce:
       (lambda: (u xs)
	 ((apply: set-union) ((set-mapf: (f u)) xs)))
       id)))

Expanding the definition of lambda:, we have

(define nd-reduce:-t4
  (lambda (f)
    (: list-reduce:
       (lambda (u)
	 (lambda (xs)
	   ((apply: set-union) ((set-mapf: (f u)) xs))))
       id)))

Introducing comp, we get

(define nd-reduce:-t5
  (lambda (f)
    (: list-reduce:
       (lambda (u)
	 (lambda (xs)
	   ((comp (apply: set-union) ((comp set-mapf: f) u)) xs)))
       id)))

Inverse eta reduction abstracts the xs parameter away:

(define nd-reduce:-t6
  (lambda (f)
    (: list-reduce:
       (lambda (u)
	 (comp (apply: set-union) ((comp set-mapf: f) u)))
       id)))

By currying comp, we get

(define comp2:
  (lambda: (f g)
    (comp2 f g)))

(define nd-reduce:-t7
  (lambda (f)
    (: list-reduce:
       (lambda (u)
	 ((comp2: (apply: set-union)) ((comp set-mapf: f) u)))
       id)))

This allows us to abstract away the u

(define nd-reduce:-t8
  (lambda (f)
    (: list-reduce:
       (comp (comp2: (apply: set-union)) (comp set-mapf: f))
       id)))

Exploiting the associativity of comp, i.e., (comp A (comp B C)) to (comp A B C) we get

(define nd-reduce:-t9
  (lambda (f)
    (: list-reduce:
       (comp (comp2: (apply: set-union)) set-mapf: f)
       id)))

The code below tests nd-reduce: with a non-deterministic state machine:

(define nd-reduce: nd-reduce:-t9)
(define nd-reduce-f:
  (let ([f (lambda: (u x)
	      (case u
		[(0) (case x
		     [(q0) (set 'q0 'q1)]
		     [(q1) (set 'q1 'q2)]
		     [(q2) (set 'q0)])]
		[else (error 'nd-reduce: "invalid arg ~a" u)]))])
      (nd-reduce: f)))

(check-equal? (: nd-reduce-f: '(0) (set 'q0)) (set 'q0 'q1) "nd-reduce-f:-1")
(check-equal? (: nd-reduce-f: '(0 0) (set 'q0)) (set 'q0 'q1 'q2) "nd-reduce-f:-2")
(check-equal? (: nd-reduce-f: '(0 0 0) (set 'q0)) (set 'q0 'q1 'q2) "nd-reduce-f:-3")
(check-equal? (: nd-reduce-f: '(0) (set 'q0 'q2)) (set 'q0 'q1) "nd-reduce-f:-4")

Author: Venkatesh Choppella

Created: 2023-11-25 Sat 09:14

Validate