Upcoming SlideShare
×

# Introduction to Functional Programming with Scheme

6,632 views

Published on

Slide deck from my session at CodeMash 2.0.1.0

Published in: Technology
8 Likes
Statistics
Notes
• Full Name
Comment goes here.

Are you sure you want to Yes No
• Be the first to comment

Views
Total views
6,632
On SlideShare
0
From Embeds
0
Number of Embeds
175
Actions
Shares
0
141
0
Likes
8
Embeds 0
No embeds

No notes for slide
• Michael Norton - DocOnDev
ThoughtWorks for 6 months - currently out of Chicago Office
LeanDog out of Cleveland before that
Years of Corporate Living Hell before that; Director/CTO in legal, banking, manufacturing

Primary Language (for the moment) is Java, some delivery in Ruby, Scheme for fun

I am uniquely qualified to give this presentation based on two criteria; I wrote it and I have the microphone
• SICP Study Groups -
twitter conversation -&gt; Jim Weirich, Dave Hoover, others; Software Craftsmanship North America; August 2009

Filled up fast; I started the Gamma Group

Who has started this book?
Who has completed this book?
• Had to put a cap on the size of the group
Capped at 15; ended up with 18

Week one - we were all excited and ready to go; did our reading.
Introduction - read first section of first chapter; Jim Weirich did a kick-off phone call.

Week two - Introduced to atoms and pairs. Reading. Exercises were primarily discussion-based.
• Fairly significant drop-off rate

Week 3 - Iterative versus Recursive; reading; actual problem sets
Week 4 - Pairs, car, cdr, anonymous functions (lambda), linear geometry
• Group size leveled out at about, well... 3

Week 5/6; lists, recursion over lists, trees, dotted-tail notation
Week 7; sets; union, append, add, subtract, compare, etc.
• Week 8 was all about differential equations and derivatives

I got lost in the math; 20+ years since I studied calculus

There were no more than two of us on the weekly phone calls and I was falling way behind the other two guys.

Alpha and Beta merged into a single group of about 5 active people; Gamma continues with the two remaining
• May be great for some.
I found it relatively unapproachable. Even simple problems were presented in a difficult to consume manner.

Given the tremendous dropout rate, I suspect many others felt as I did.
• I found these texts to be more approachable

Little / Seasoned / Reasoned almost too approachable. Can be considered condescending.

50 pages into Little Schemer, I understood the Scheme list structures and their processing.
50 pages into SICP, I had atoms, pairs, and a nagging sense of self-doubt.
• Main Program is a function which receives input and provides output

Main program is defined in terms of other functions, which are defined in terms of other functions, and so on.

At the lowest level, functions are language primitives
Functions are first class
• Which means it supports Higher-order functions

What are higher order functions?
Functions which take functions as parameters and functions that return functions
• There are no assignment statements; variables are immutable - purely functional

A function call takes input and calculates a result; altering no state in the environment

As a result, there are no (or at least considerably fewer) side-effects

Technically a purely functional application is impossible; with no side-effects, there is no means of providing input or receiving output. If it did exist, nobody would ever know.
• A call to a function can be replaced with its value without changing the application behavior
• Recursion is a primary control structure for iteration

Some functional languages allow no other loop structure

In Scheme, recursion is idiomatic, but there are do and for constructs
• Symbolic Expression - convention representing semi-structured data in human-readable form

a.k.a - s-expressions

Originally intended for data only. M-expressions were supposed to be for methods.
Formalization came too late and use of s-expressions was idiomatic for data and methods
• Type checking at run-time

Perl, JavaScript, Ruby, Python, Objective-C
• Computer representation of a mathematical finite sequence or tuple

We&amp;#x2019;re all familiar with this concept; List Objects, Arrays, Linked Lists

Functional Languages favor immutability
Static list structures - only allows inspection and enumeration
• Static Scope - variable always refers to its top-level environment

Bindings are able to be understood in isolation; no concern about context of invocation
• Static Scope - variable always refers to its top-level environment

Bindings are able to be understood in isolation; no concern about context of invocation
• Idiomatic to Scheme
Scheme does have a do construct, but tail recursion is preferred

Last call of function (tail call) is a recursive call - easily transformed to iterations
Optimization of stack space allowing for deeper (infinite?) recursion

Improves efficiency for complex calls
• Idiomatic to Scheme
Scheme does have a do construct, but tail recursion is preferred

Last call of function (tail call) is a recursive call - easily transformed to iterations
Optimization of stack space allowing for deeper (infinite?) recursion

Improves efficiency for complex calls
• Common Lisp has different namespaces for functions and data

Common Lisp allows function and variable to have the same name - requires special notation to refer to a function as a value

Scheme data and procedures can be bound and manipulated by same primitives

Scheme does support creation/installation of namespaces; out of scope for presentation
• What is a Kata?

Relatively simple problem domains; repeat them over and over again, learning and improving as you go

Relatively simple Kata put together by Roy Osherove
• This is a simple description of the requirements

There are additional requirements to fail for negatives, improve error messages, exclude numbers greater than a given size, allow multiple delimiters, allow complex delimiters
• schemeunit is a unit testing framework for Scheme. I am running it in DrScheme, available in the PLT Scheme sight. All made available in the &amp;#x201C;show notes&amp;#x201D;

Provides capability for test suites. Will show simple tests.
• This is exactly what we expected!

Let&amp;#x2019;s make it pass.
• schemeunit is a unit testing framework for Scheme. I am running it in DrScheme, available in the PLT Scheme sight. All made available in the &amp;#x201C;show notes&amp;#x201D;

Provides capability for test suites. Will show simple tests.
• Yahoo!

Added a second test - String with single number should return number
• Again; expected.

Let&amp;#x2019;s make it pass.
• cond =&gt; scheme conditional Like a case or switch statement where the else is our otherwise

string-length? =&gt; scheme function returns boolean
string-&gt;number =&gt; scheme function
• Yahoo!

Added a second test - String with single number should return number
• Again; expected.

Let&amp;#x2019;s make it pass.
• cond =&gt; scheme conditional Like a case or switch statement where the else is our otherwise

string-length? =&gt; scheme function returns boolean
string-&gt;number =&gt; scheme function
• Yahoo!
• This is a simple description of the requirements

There are additional requirements to fail for negatives, improve error messages, exclude numbers greater than a given size, allow multiple delimiters, allow complex delimiters
• Let&amp;#x2019;s see if it can do strings with more numbers

What do you think?
• Got one for free - I don&amp;#x2019;t necessarily like when that happens
• This is a simple description of the requirements

There are additional requirements to fail for negatives, improve error messages, exclude numbers greater than a given size, allow multiple delimiters, allow complex delimiters
• Now we&amp;#x2019;ve added the Custom Delimiter
• This is exactly what we expected!

Let&amp;#x2019;s make it pass.
• We&amp;#x2019;ve added a parameter to the str-calc function, but this paramater is optional. When not provided, it defaults to a comma as indicated

Hash-Whack Notation - escape sequence
• So now we have custom delimiter
• Now we&amp;#x2019;ve added the Custom Delimiter
• So now we have custom delimiter
• Ok - now we have to handle the custom delimiter as a prefix to the string as opposed to as a separate parameter.
• Now we&amp;#x2019;ve added the Custom Delimiter as a prefix

I&amp;#x2019;m going to ask you to take a leap of faith with me in the interest of time.
• We&amp;#x2019;ve added a parameter to the str-calc function, but this paramater is optional. When not provided, it defaults to a comma as indicated

Hash-Whack Notation - escape sequence
• Delimiter parser was also written as part of this kata

Test drove it just as we did all the other code.
• I told you....
• Yahoo!!
• All set.
• All set.
• All set.
• All set.
• All set.
• All set.
• All set.
• All set.
• Michael Norton - DocOnDev
ThoughtWorks for 6 months - currently out of Chicago Office
LeanDog out of Cleveland before that
Years of Corporate Living Hell before that; Director/CTO in legal, banking, manufacturing

Primary Language (for the moment) is Java, some delivery in Ruby, Scheme for fun

I am uniquely qualified to give this presentation based on two criteria; I wrote it and I have the microphone
• ### Introduction to Functional Programming with Scheme

1. DocOnDev An Introduction to Functional Programming with Scheme Michael Norton michael@docondev.com : mail docondev.blogspot.com/ : blog twitter.com/DocOnDev : the twitters CodeMash 2.0.1.0
2. DocOnDev SICP Study Gamma Group CodeMash 2.0.1.0
3. DocOnDev Yes! Gamma Group Week 1 CodeMash 2.0.1.0
4. DocOnDev umm... Gamma Group Week 3 CodeMash 2.0.1.0
5. DocOnDev Hello? Gamma Group Week 5 CodeMash 2.0.1.0
6. DocOnDev Maths are hard Second order nonlinear ordinary differential equation Third Order Nonlinear Partial Differential Equation CodeMash 2.0.1.0
7. DocOnDev SICP I don’t recommend this text to start with CodeMash 2.0.1.0
8. DocOnDev Texts I do recommend CodeMash 2.0.1.0
9. DocOnDev Functional Programming CodeMash 2.0.1.0
10. DocOnDev Consists Entirely of Functions CodeMash 2.0.1.0
11. DocOnDev Consists Entirely of Functions Higher-order Functions CodeMash 2.0.1.0
12. DocOnDev Favors Immutability CodeMash 2.0.1.0
13. DocOnDev Referentially Transparent CodeMash 2.0.1.0
14. DocOnDev Limited Loop Structures CodeMash 2.0.1.0
15. DocOnDev Functional Programming • Consists Entirely of Functions • Favors Immutability • Referentially Transparent • Limited Loop Structures CodeMash 2.0.1.0
16. DocOnDev LISP CodeMash 2.0.1.0
17. DocOnDev Symbolic Expressions CodeMash 2.0.1.0
18. DocOnDev Dynamically Typed CodeMash 2.0.1.0
19. DocOnDev List Processing LISt Processing CodeMash 2.0.1.0
20. DocOnDev Lisp • Symbolic Expressions • Dynamically Typed • List Processing CodeMash 2.0.1.0
21. DocOnDev Scheme CodeMash 2.0.1.0
22. DocOnDev Static (Lexical) Scope CodeMash 2.0.1.0
23. DocOnDev Static Scope (define x 9) (define (show-x) (display x)) (define test (lambda (x) (show-x) (display ‘,)(display x)) (test 3) => 9,3 CodeMash 2.0.1.0
24. DocOnDev Tail Recursion CodeMash 2.0.1.0
25. DocOnDev Tail Recursion (define (factorial n) (define (fact-tail n m) (if (= n 0) m (fact-tail (- n 1) (* n m)))) (fact-tail n 1)) (factorial 4) => 24 CodeMash 2.0.1.0
26. DocOnDev Shared Namespace CodeMash 2.0.1.0
27. DocOnDev Scheme • Static Scope • Tail Recursion • Shared Namespace CodeMash 2.0.1.0
28. DocOnDev String Calculator Kata CodeMash 2.0.1.0
29. DocOnDev Requirements Takes a String of integers and returns their sum Can handle an unknown amount of integers Can handle a custom delimiter Delimiter notation “//[delimiter]n[numbers]” CodeMash 2.0.1.0
30. DocOnDev First Test string-calculator-tests.ss #lang scheme (require (planet schematics/schemeunit:3) (planet schematics/schemeunit:3/text-ui) "string-calculator.ss") (check-equal? (str-calc "") 0 "Empty string should return 0") CodeMash 2.0.1.0
31. DocOnDev FAIL! . expand: unbound identiﬁer in module in: str-calc CodeMash 2.0.1.0
32. DocOnDev String Calculator Code string-calculator.ss #lang scheme ;; String Calculator Kata (define (str-calc input) 0) ;; Expose these functions (provide str-calc) CodeMash 2.0.1.0
33. DocOnDev PASS! #t CodeMash 2.0.1.0
34. DocOnDev Second Test string-calculator-tests.ss #lang scheme (require ... "string-calculator.ss") (check-equal? (str-calc "") 0 "Should return 0") (check-equal? (str-calc "1") 1 "Should return 1") CodeMash 2.0.1.0
35. DocOnDev FAIL! #t -------------------- FAILURE name: check-equal? location: (#<path:/string-calculator-tests.ss> 11 0 219 63) expression: (check-equal? (str-calc "1") 1) params: (0 1) message: "Should return 1" actual: 0 expected: 1 -------------------- CodeMash 2.0.1.0
36. DocOnDev String Calculator Code string-calculator.ss #lang scheme (define (str-calc input) (cond ((= (string-length input) 0) 0) (else (string->number input)))) (provide str-calc) CodeMash 2.0.1.0
37. DocOnDev PASS! #t #t CodeMash 2.0.1.0
38. DocOnDev Third Test string-calculator-tests.ss #lang scheme (require ... "string-calculator.ss") (check-equal? (str-calc "") 0 "Should return 0") (check-equal? (str-calc "1") 1 "Should return 1") (check-equal? (str-calc "1,2") 3 "Should sum items") CodeMash 2.0.1.0
39. DocOnDev FAIL! #t #t -------------------- FAILURE name: check-equal? location: (#<path:/string-calculator-tests.ss> 10 0 241 52) expression: (check-equal? (str-calc "1,2") 3) params: (#f 3) message: "Should sum items" actual: #f expected: 3 -------------------- CodeMash 2.0.1.0
40. DocOnDev String Calculator Code string-calculator.ss (define (str-calc input) (cond ((list? input) (cond ((null? input) 0) (else (+ (string->number (car input)) (str-calc (cdr input)))))) ((= (string-length input) 0) 0) (else (str-calc (str-split input #,))))) CodeMash 2.0.1.0
41. DocOnDev PASS! #t #t #t CodeMash 2.0.1.0
42. DocOnDev Requirements Takes a String of integers and returns their sum Can handle an unknown amount of integers Can handle a custom delimiter Delimiter notation “//[delimiter]n[numbers]” CodeMash 2.0.1.0
43. DocOnDev Fourth Test string-calculator-tests.ss #lang scheme (require ... "string-calculator.ss") (check-equal? (str-calc "") 0 "Should return 0") (check-equal? (str-calc "1") 1 "Should return 1") (check-equal? (str-calc "1,2") 3 "Should sum items") (check-equal? (str-calc "1,2,3,4") 10 "Should sum all items") CodeMash 2.0.1.0
44. DocOnDev PASS! #t #t #t #t CodeMash 2.0.1.0
45. DocOnDev Requirements Takes a String of integers and returns their sum Can handle an unknown amount of integers Can handle a custom delimiter Delimiter notation “//[delimiter]n[numbers]” CodeMash 2.0.1.0
46. DocOnDev Fifth Test string-calculator-tests.ss #lang scheme (require ... "string-calculator.ss") (check-equal? (str-calc "") 0 "Should return 0") (check-equal? (str-calc "1") 1 "Should return 1") (check-equal? (str-calc "1,2") 3 "Should sum items") (check-equal? (str-calc "1,2,3,4") 10 "Should sum all items") (check-equal? (str-calc "1*2" #*) 3 "Should delimit on custom") CodeMash 2.0.1.0
47. DocOnDev FAIL! . procedure str-calc: expects 1 argument, given 2: "1*2" #* CodeMash 2.0.1.0
48. DocOnDev String Calculator Code string-calculator.ss (define (str-calc input [delim #,]) (cond ((list? input) (cond ((null? input) 0) (else (+ (string->number (car input)) (str-calc (cdr input)))))) ((= (string-length input) 0) 0) (else (str-calc (str-split input delim))))) CodeMash 2.0.1.0
49. DocOnDev PASS! #t #t #t #t #t CodeMash 2.0.1.0
50. DocOnDev Sixth Test string-calculator-tests.ss (require ... "string-calculator.ss") (check-equal? (str-calc "") 0 "Should return 0") (check-equal? (str-calc "1") 1 "Should return 1") (check-equal? (str-calc "1,2") 3 "Should sum items") (check-equal? (str-calc "1,2,3,4") 10 "Should sum all items") (check-equal? (str-calc "1*2" #*) 3 "Should delimit on custom") (check-equal? (str-calc "1*2*3*4" #*) 10 "Should delimit on custom") CodeMash 2.0.1.0
51. DocOnDev PASS! #t #t #t #t #t #t CodeMash 2.0.1.0
52. DocOnDev Requirements Takes a String of integers and returns their sum Can handle an unknown amount of integers Can handle a custom delimiter Delimiter notation “//[delimiter]n[numbers]” CodeMash 2.0.1.0
53. DocOnDev Change Tests string-calculator-tests.ss (require ... "string-calculator.ss") (check-equal? (str-calc "") 0 "Should return 0") (check-equal? (str-calc "1") 1 "Should return 1") (check-equal? (str-calc "1,2") 3 "Should sum items") (check-equal? (str-calc "1,2,3,4") 10 "Should sum all items") (check-equal? (str-calc "//*n1*2") 3 "Should delimit on custom") (check-equal? (str-calc "//*n1*2*3*4") 10 "Should delimit on custom") CodeMash 2.0.1.0
54. DocOnDev String Calculator Code string-calculator.ss (define (str-calc input) (cond ((list? input) (cond ((null? input) 0) (else (+ (string->number (car input)) (str-calc (cdr input)))))) ((= (string-length input) 0) 0) (else (str-calc (str-split (rm-delim input) (get-delim input))))) CodeMash 2.0.1.0
55. DocOnDev Delimiter Parser delim-parse.ss (define (rm-delim input) (cadr (split-delim input))) (define (get-delim input) (car (split-delim input))) (define (split-delim input) (cond ((and (> (string-length input) 2) (string=? (substring input 0 2) "//")) (regexp-split #rx"n" (substring input 2))) (else (cons "," (list input))))) CodeMash 2.0.1.0
56. DocOnDev Delimiter Tests delim-parse-tests.ss (require (planet schematics/schemeunit:3) (planet schematics/schemeunit:3/text-ui) "delimiter-parse.ss") (check-equal? (get-delim "1") #, "Default delimiter") (check-equal? (remove-delim "2,1") "2,1" "String") (check-equal? (get-delim "//*n2*1") #* "Delimiter") (check-equal? (remove-delim "//*n2*1") "2*1" "-Delimiter") CodeMash 2.0.1.0
57. DocOnDev PASS! #t #t #t #t #t #t CodeMash 2.0.1.0
58. DocOnDev Requirements Takes a String of integers and returns their sum Can handle an unknown amount of integers Can handle a custom delimiter Delimiter notation “//[delimiter]n[numbers]” CodeMash 2.0.1.0
59. DocOnDev Ruby - Mario Aquino module Calculator def self.calculate(expression) raise ArgumentError, 'Must be a string' unless expression.is_a? String raise ArgumentError, 'Must be numeric' unless expression =~ pattern value = nil expression.scan(pattern) do |left, operator, right| value ||= left value = eval(value + operator + right).to_s end value end private def self.pattern /(d+)s?([+-*/])s*(?=(d+))/ end end CodeMash 2.0.1.0
60. DocOnDev Python - Gary Bernhardt def add(string): string = _normalize_delimiters(string) if string: return _add_numbers_in_string(string) else: return 0 def _normalize_delimiters(string): string = _normalize_custom_delimiter(string) string = string.replace('n', ',') return string def _normalize_custom_delimiter(string): if string.startswith('//'): delimiter_spec, string = string.split('n', 1) delimiter = delimiter_spec[2:] string = string.replace(delimiter, ',') return string def _add_numbers_in_string(string): numbers = map(int, string.split(',')) _validate_numbers(numbers) return sum(numbers) def _validate_numbers(numbers): if any(number < 0 for number in numbers): raise ValueError CodeMash 2.0.1.0
61. DocOnDev F# - Mark Needham module FSharpCalculator open System open System.Text.RegularExpressions   let split (delimeter:array<string>) (value:string) = value.Split (delimeter, StringSplitOptions.None) let toDecimal value = Decimal.Parse value   let (|CustomDelimeter|NoCustomDelimeter|) (value:string) = let delimeters (value:string) = Regex.Matches(value, "[([^]]*)]") |> Seq.cast |> Seq.map (fun (x:Match) -> x.Groups) |> Seq.map (fun x -> x |> Seq.cast<Group> |> Seq.nth 1) |> Seq.map (fun x -> x.Value) |> Seq.to_array   if (value.Length > 2 && "//".Equals(value.Substring(0, 2))) then if ("[".Equals(value.Substring(2,1))) then CustomDelimeter(delimeters value) else CustomDelimeter([| value.Substring(2, value.IndexOf("n") - 2) |]) else NoCustomDelimeter(",")   let digits value = match value with | CustomDelimeter(delimeters) -> value.Substring(value.IndexOf("n")) |> split delimeters |> Array.map toDecimal | NoCustomDelimeter(delimeter) -> value.Replace("n", delimeter) |> split [|delimeter |] |> Array.map toDecimal   let buildExceptionMessage negatives = sprintf "No negative numbers allowed. You provided %s" (String.Join(",", negatives |> Array.map (fun x -> x.ToString())))   let (|ContainsNegatives|NoNegatives|) digits = if (digits |> Array.exists (fun x -> x < 0.0m)) then ContainsNegatives(digits |> Array.filter (fun x -> x < 0.0m)) else NoNegatives(digits)   let add value = if ("".Equals(value) or "n".Equals(value)) then 0.0m else match digits value |> Array.filter (fun x -> x < 1000m) with | ContainsNegatives(negatives) -> raise (ArgumentException (buildExceptionMessage negatives)) | NoNegatives(digits) -> digits |> Array.sum CodeMash 2.0.1.0
62. DocOnDev C# - Anonymous using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; namespace TDDFun { public class StringCalculator { public double Add(string numbers) { Double sum = 0; if (string.IsNullOrEmpty(numbers)) { sum = 0; } else { const string regex1 = “(//)”; const string regex2 = “(.*?)”; const string regex3 = “(n)”; const string regex4 = “(.*?d)”; var delimiters = new List<string>(); var r = new Regex(regex1 + regex2 + regex3 + regex4, RegexOptions.IgnoreCase | RegexOptions.Singleline); Match m = r.Match(numbers); if (m.Success) { string delimiterCollection = m.Groups[2].ToString(); int numberStartIndex = m.Groups[3].Index; const string re5 = “([.*?])”; var r2 = new Regex(re5, RegexOptions.IgnoreCase | RegexOptions.Singleline); CodeMash 2.0.1.0
63. DocOnDev C# - Anonymous (cont.) MatchCollection m2 = r2.Matches(delimiterCollection); if (m2.Count > 0) { foreach (Match x in m2) { delimiters.Add(x.ToString().Replace(“[", "").Replace("]“, “”)); } } else { delimiters.Add(delimiterCollection); } numbers = numbers.Remove(0, numberStartIndex + 1); } else { delimiters.Add(“n”); delimiters.Add(“,”); } string[] splittedNumbers = numbers.Split(delimiters.ToArray(), StringSplitOptions.None); ValiateNumbers(splittedNumbers); foreach (string s in splittedNumbers) { double ss = Double.Parse(s); sum += ss <= 1000 ? ss : 0; } } return sum; } CodeMash 2.0.1.0
64. DocOnDev C# - Anonymous (cont.) private static void ValiateNumbers(IEnumerable<string> numbers) { double x; var negativeNumbers = new List<string>(); foreach (string s in numbers) { Validator.IsRequiredField(Double.TryParse(s, out x), “Validation Error”); if (Double.Parse(s) < 0) { negativeNumbers.Add(s); } } Validator.IsRequiredField(negativeNumbers.Count <= 0, “Negatives not allowed “ + ShowAllNegatives(negativeNumbers)); } private static string ShowAllNegatives(List<string> negativeNumbers) { var sb = new StringBuilder(); int counter = 0; negativeNumbers.ForEach(k => { if (counter == 0) { sb.Append(k); counter++; } else { sb.Append(“;” + k); } CodeMash 2.0.1.0
65. DocOnDev C# - Anonymous (cont.) }); return sb.ToString(); } } public static class Validator { public static void IsRequiredField(bool criteria, string message) { if (!criteria) { throw new ValidationException(message); } } } public class ValidationException : ApplicationException { public ValidationException(string message) : base(message) { } } } CodeMash 2.0.1.0
66. DocOnDev Thank You! Michael Norton michael@docondev.com : mail docondev.blogspot.com/ : blog twitter.com/DocOnDev : the twitters CodeMash 2.0.1.0
67. DocOnDev References • Wikipedia http://en.wikipedia.org/wiki/Scheme • PLT Scheme http://plt-scheme.org/ • MIT Press SICP http://mitpress.mit.edu/sicp/ • MIT Press HTDP http://www.htdp.org/ • U of CA, Berkley http://www.cs.berkeley.edu/~bh/ss-toc2.html CodeMash 2.0.1.0