commit 99a1aa94c5f5ea898356a9c6e42769cf05eb5d66 Author: tA Date: Fri Jul 24 17:40:40 2020 +1200 first commit diff --git a/.fizzbuzz.rkt.swp b/.fizzbuzz.rkt.swp new file mode 100644 index 0000000..2053eec Binary files /dev/null and b/.fizzbuzz.rkt.swp differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..b272d74 --- /dev/null +++ b/README.md @@ -0,0 +1,203 @@ +# FizzBuzz Macro + +a small macro for defining FizzBuzz-like functions easily. +uses continuation passing to ensure each predicate is only checked once per number. + +## usage + +import the file into your `#lang racket` project + +``` +(require "fizzbuzz.rkt") +``` + +from there the functions `mult`, `fizzbuzz` and `define-fizzbuzz` will be avaliable to you. + +### mult + +`(mult x)` creates a predicate that answers whether a number is a multiple of `x` + +``` +((mult 5) 15) => #t +((mult 5) 16) => #f +``` + +### fizzbuzz + +`(fizzbuzz x)` is an implementation of fizzbuzz using this program. it returns a string which will be one of: + + * the string representation of `x` + * the string "Fizz" + * the string "Buzz" + * the string "FizzBuzz" + +it is defined using `define-fizzbuzz` and `mult` as follows: + +``` +(define-fizzbuzz fizzbuzz + [(mult 3) "Fizz"] + [(mult 5) "Buzz"]) +``` + +### define-fizzbuzz + +`define-fizzbuzz` takes a name, and a list of predicate, string pairs, and creates a function that acts as fizzbuzz would for any number of triggers. + +the order of the pairs will determine the ordering of the strings should more than one be true for a given number, ie. the difference between "FizzBuzz" and "BuzzFizz" is the ordering of the pairs. + +## example + +in order to define the function `bashforkfoobar` which has the following semantics: + + * if `x` is odd, "Bash" + * if `x` is a multiple of 7, "Fork" + * if `x - 2` is a multiple of 3, "Foo" + * if `x` is 69, "Bar" + +we can use the following; + +``` +(define-fizzbuzz bashforkfoobar + [odd? "Bash"] + [(mult 7) "Fork"] + [(lambda (n) ((mult 3) (- n 2))) "Foo"] + [(lambda (n) (= n 69) "Bar"]) +``` + +which when mapped over a range of numbers using `(map bashforfoobar (range 1 101)`; + +``` +'("Bash" + "Foo" + "Bash" + "4" + "BashFoo" + "6" + "BashFork" + "Foo" + "Bash" + "10" + "BashFoo" + "12" + "Bash" + "ForkFoo" + "Bash" + "16" + "BashFoo" + "18" + "Bash" + "Foo" + "BashFork" + "22" + "BashFoo" + "24" + "Bash" + "Foo" + "Bash" + "Fork" + "BashFoo" + "30" + "Bash" + "Foo" + "Bash" + "34" + "BashForkFoo" + "36" + "Bash" + "Foo" + "Bash" + "40" + "BashFoo" + "Fork" + "Bash" + "Foo" + "Bash" + "46" + "BashFoo" + "48" + "BashFork" + "Foo" + "Bash" + "52" + "BashFoo" + "54" + "Bash" + "ForkFoo" + "Bash" + "58" + "BashFoo" + "60" + "Bash" + "Foo" + "BashFork" + "64" + "BashFoo" + "66" + "Bash" + "Foo" + "BashBar" + "Fork" + "BashFoo" + "72" + "Bash" + "Foo" + "Bash" + "76" + "BashForkFoo" + "78" + "Bash" + "Foo" + "Bash" + "82" + "BashFoo" + "Fork" + "Bash" + "Foo" + "Bash" + "88" + "BashFoo" + "90" + "BashFork" + "Foo" + "Bash" + "94" + "BashFoo" + "96" + "Bash" + "ForkFoo" + "Bash" + "100") +``` + +## explanation + +the macro creates a composition of continuation style passing functions, which will build a string and halt production in order to prevent a number being printed if an identifier has been. + +the string building functions `myprint`, `skip` and `halt` are quite simple. + +`skip` and `halt` both take a string and return a string, with `skip` acting as the identity function, and `halt` intead throwing away its argument and returning the empty string (the unit value for this composition). + +`myprint` takes two strings and returns a string. the first is the message to add to the end of the string being built, and the second (curried) argument works as in `skip` and `halt`. + +`base` exists as a CPS function that creates (once passed a continuation) a function from string to string, simply applying its continuation to the result of printing the number passed to `base`. it is the first rule in any fizzbuzz function to facilitate the default behaviour. + +the macro then constructs an additional trigger for each predicate / string pair, which when given a number, returns a CPS function that when given a continuation will take a string, and either pass it unchanged to the continuation, or sandwich the continuation between a `myprint` and a `halt`, such that the halt will prevent the default behaviour. + +after macro transformations, a trigger with the message `message` and the predicate `predicate` will look as follows: + +``` +(lambda (n) + (lambda (k) + (lambda (s) + (if (p n) + (myprint m (k (halt s))) + (k s))))) +``` + +here, `n` is the number passed to the fizzbuzz function, `k` is the continuation function, and `s` is the string for the final string builder. + +these are composed starting with `base`, and then each trigger in order to create the final fizzbuzz function. + +## author + +* tA diff --git a/fizzbuzz.rkt b/fizzbuzz.rkt new file mode 100644 index 0000000..634a34e --- /dev/null +++ b/fizzbuzz.rkt @@ -0,0 +1,49 @@ +#lang racket + +(define (skip s) + s) + +(define (halt s) + "") + +(define (myprint s x) + (string-append s x)) + +(define (base n) + (lambda (k) + (lambda (s) + (k (myprint (number->string n) s))))) + +(define (mult n) + (lambda (k) (= 0 (modulo k n)))) + +(require (for-syntax syntax/parse racket/base)) + +(begin-for-syntax + (define-syntax-class fbline + #:description "predicate string pair" + (pattern (p:expr m:string)))) + +(define-syntax (create-trigger stx) + (syntax-parse stx + [(_ p:expr m:string) + #'(lambda (n) + (lambda (k) + (lambda (s) + (if (p n) + (myprint m (k (halt s))) + (k s)))))])) + +(define-syntax (define-fizzbuzz stx) + (syntax-parse stx + [(_ name:identifier l:fbline ...) + #'(define (name n) + (((compose (base n) + ((create-trigger l.p l.m) n) ...) + skip) ""))])) + +(define-fizzbuzz fizzbuzz + [(mult 3) "Fizz"] + [(mult 5) "Buzz"]) + +(provide mult define-fizzbuzz fizzbuzz)