Skip to main content
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
DocOnDev




SICP Study
  Gamma Group




                CodeMash 2.0.1.0
DocOnDev




        Yes!




Gamma Group Week 1
               CodeMash 2.0.1.0
DocOnDev




   umm...




Gamma Group Week 3
               CodeMash 2.0.1.0
DocOnDev




     Hello?




Gamma Group Week 5
               CodeMash 2.0.1.0
DocOnDev




                        Maths are
                          hard
Second order nonlinear ordinary differential equation




                   Third Order Nonlinear Partial Differential Equation




                                                                         CodeMash 2.0.1.0
DocOnDev




         SICP
I don’t recommend this text to
           start with




                                 CodeMash 2.0.1.0
DocOnDev




Texts I do recommend




                  CodeMash 2.0.1.0
DocOnDev




Functional Programming
                    CodeMash 2.0.1.0
DocOnDev




Consists Entirely of Functions



                         CodeMash 2.0.1.0
DocOnDev




Consists Entirely of Functions
        Higher-order Functions




                                 CodeMash 2.0.1.0
DocOnDev




Favors Immutability



                      CodeMash 2.0.1.0
DocOnDev




Referentially Transparent



                       CodeMash 2.0.1.0
DocOnDev




Limited Loop Structures



                     CodeMash 2.0.1.0
DocOnDev




Functional Programming

• Consists Entirely of Functions
• Favors Immutability
• Referentially Transparent
• Limited Loop Structures

                                   CodeMash 2.0.1.0
DocOnDev




LISP
       CodeMash 2.0.1.0
DocOnDev




Symbolic Expressions



                   CodeMash 2.0.1.0
DocOnDev




Dynamically Typed



                    CodeMash 2.0.1.0
DocOnDev




List Processing
   LISt Processing




                     CodeMash 2.0.1.0
DocOnDev




                 Lisp

• Symbolic Expressions
• Dynamically Typed
• List Processing

                         CodeMash 2.0.1.0
DocOnDev




Scheme
         CodeMash 2.0.1.0
DocOnDev




Static (Lexical) Scope



                     CodeMash 2.0.1.0
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
DocOnDev




Tail Recursion



                 CodeMash 2.0.1.0
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
DocOnDev




Shared Namespace



                   CodeMash 2.0.1.0
DocOnDev




            Scheme

• Static Scope
• Tail Recursion
• Shared Namespace

                     CodeMash 2.0.1.0
DocOnDev




String Calculator Kata
                    CodeMash 2.0.1.0
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
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
DocOnDev




                      FAIL!


. expand: unbound identifier in module in: str-calc




                                                CodeMash 2.0.1.0
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
DocOnDev




     PASS!


#t




             CodeMash 2.0.1.0
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
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
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
DocOnDev




     PASS!

#t
#t




             CodeMash 2.0.1.0
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
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
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
DocOnDev




     PASS!

#t
#t
#t




             CodeMash 2.0.1.0
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
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
DocOnDev




     PASS!

#t
#t
#t
#t




             CodeMash 2.0.1.0
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
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
DocOnDev




                        FAIL!


. procedure str-calc: expects 1 argument, given 2: "1*2" #*




                                                   CodeMash 2.0.1.0
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
DocOnDev




     PASS!
#t
#t
#t
#t
#t




             CodeMash 2.0.1.0
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
DocOnDev




     PASS!
#t
#t
#t
#t
#t
#t



             CodeMash 2.0.1.0
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
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
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
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
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
DocOnDev




     PASS!
#t
#t
#t
#t
#t
#t



             CodeMash 2.0.1.0
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
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
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
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
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
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
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
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
DocOnDev




        Thank You!
          Michael Norton

    michael@docondev.com : mail
    docondev.blogspot.com/ : blog
twitter.com/DocOnDev : the twitters

                                      CodeMash 2.0.1.0
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