;;; typed-compose.rkt ;;; ;;; Copyright 2020 Sergiu Ivanov ;;; ;;; This program is free software: you can redistribute it and/or ;;; modify it under the terms of the GNU General Public License as ;;; published by the Free Software Foundation, either version 3 of the ;;; License, or (at your option) any later version. ;;; ;;; This program is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;;; General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program. If not, see ;;; . #lang typed/racket/base (require syntax/parse/define (for-syntax racket/base racket/match racket/list racket/syntax syntax/parse)) (provide make-compose compose-n compose-3 compose-4 compose-5 compose-6 compose-7 compose-8 compose-9 multi-compose multi-chain) (module+ test (require typed/rackunit)) (: compose-n (All (a) (-> (-> a a) * (-> a a)))) (define (compose-n . funcs) (λ (x) (for/foldr ([x x]) ([f funcs]) (f x)))) (module+ test (test-case "compose-n" (check-equal? ((compose-n add1 add1 add1) 3) 6))) (define-for-syntax (make-compose-type n) (with-syntax* ([(t ...) (generate-temporaries (make-list n 't))] [(_ ... t₀) #'(a t ...)] [(F ...) (let step ([u #'a] [ts (syntax->list #'(t ...))]) (match ts ['() '()] [(cons t ts*) (cons #`(#,t → #,u) (step t ts*))]))]) #'(∀ (a t ...) (F ... → t₀ → a)))) (define-syntax-parser make-compose [(_ n:nat) (with-syntax* ([(f ...) (generate-temporaries (make-list (syntax-e #'n) 'f))] [T (make-compose-type (syntax-e #'n))] [body (foldr (λ (fᵢ res) #`(#,fᵢ #,res)) #'x (syntax->list #'(f ...)))]) #'(ann (λ (f ...) (λ (x) body)) T))]) (define compose-3 (make-compose 3)) (define compose-4 (make-compose 4)) (define compose-5 (make-compose 5)) (define compose-6 (make-compose 6)) (define compose-7 (make-compose 7)) (define compose-8 (make-compose 8)) (define compose-9 (make-compose 9)) (module+ test (test-case "compose-3 to compose-9" (define (s->n [s : String]) (cast (string->number s) Number)) (define (n->s [n : Number]) (number->string n)) (check-equal? ((compose-3 n->s add1 s->n) "3") "4") (check-equal? ((compose-4 n->s add1 add1 s->n) "3") "5") (check-equal? ((compose-5 n->s add1 add1 add1 s->n) "3") "6") (check-equal? ((compose-6 n->s add1 add1 add1 add1 s->n) "3") "7") (check-equal? ((compose-7 n->s add1 add1 add1 add1 add1 s->n) "3") "8") (check-equal? ((compose-8 n->s add1 add1 add1 add1 add1 add1 s->n) "3") "9") (check-equal? ((compose-9 n->s add1 add1 add1 add1 add1 add1 add1 s->n) "3") "10"))) (define-syntax (multi-compose stx) ;; Implementation by Sorawee Porncharoenwase. (syntax-parse stx [(_ f:expr g:expr) #'(compose f g)] [(_ f:expr funcs:expr ...) #'(compose f (multi-compose funcs ...))])) (module+ test (test-case "multi-compose" (check-equal? ((multi-compose add1 (λ ([x : Number]) (* x 3)) add1 (λ ([x : Number]) (+ x 2))) 3) 19))) (define-syntax (multi-chain stx) (syntax-parse stx [(_ funcs:expr ...) (define rev-funcs (reverse (syntax->list #'(funcs ...)))) #`(multi-compose #,@rev-funcs)])) (module+ test (require racket/port) (test-case "multi-chain and multi-compose" (define f1 (λ ([x : Number]) (displayln "f1") (+ x 1))) (define f2 (λ ([x : Number]) (displayln "f2") (+ x 1))) (define f3 (λ ([x : Number]) (displayln "f3") (+ x 1))) (check-equal? (with-output-to-string (λ () (displayln ((multi-chain f1 f2 f3) 3)))) "f1\nf2\nf3\n6\n") (check-equal? (with-output-to-string (λ () (displayln ((multi-compose f1 f2 f3) 3)))) "f3\nf2\nf1\n6\n")))