test/test-function.ss
#lang scheme

(require "checks.ss"
         "../function.ss")

(provide test-function)

(define list/kw (make-keyword-procedure list))

(define test-function
  (test-suite "function.ss"

    (test-suite "Simple Functions"

      (test-suite "identity"
        (test-case "unique symbol"
          (let* ([sym (gensym)])
            (check-eq? (identity sym) sym))))

      (test-suite "const"
        (test-case "unique symbol"
          (let* ([sym (gensym)])
            (check-eq? ((const sym) 'x #:y 'z) sym))))

      (test-suite "thunk"
        (test-case "unique symbol"
          (let* ([count 0]
                 [f (thunk (set! count (+ count 1)) count)])
            (check = count 0)
            (check = (f) 1)
            (check = count 1)))))

    (test-suite "Higher Order Predicates"

      (test-suite "conjoin"
        (test-case "no functions"
          (check-true ((conjoin) 'x #:y 'z)))
        (test-case "true"
          (check-true ((conjoin integer? exact?) 1)))
        (test-case "false"
          (check-false ((conjoin integer? exact?) 1.0)))
        (test-case "false"
          (check-false ((conjoin integer? exact?) 0.5))))

      (test-suite "disjoin"
        (test-case "no functions"
          (check-false ((disjoin) 'x #:y 'z)))
        (test-case "true"
          (check-true ((disjoin integer? exact?) 1)))
        (test-case "true"
          (check-true ((disjoin integer? exact?) 1/2)))
        (test-case "false"
          (check-false ((disjoin integer? exact?) 0.5)))))

    (test-suite "Currying and (Partial) Application"

      (test-suite "call"
        (test-case "string-append"
          (check-equal? (call string-append "a" "b" "c") "abc")))

      (test-suite "papply"
        (test-case "list"
          (check-equal? ((papply list 1 2) 3 4) (list 1 2 3 4)))
        (test-case "sort"
          (check-equal?
           ((papply sort '((1 a) (4 d) (2 b) (3 c)) #:cache-keys? #f)
            < #:key car)
           '((1 a) (2 b) (3 c) (4 d)))))

      (test-suite "papplyr"
        (test-case "list"
          (check-equal? ((papplyr list 1 2) 3 4) (list 3 4 1 2)))
        (test-case "sort"
          (check-equal?
           ((papplyr sort < #:key car)
            '((1 a) (4 d) (2 b) (3 c)) #:cache-keys? #f)
           '((1 a) (2 b) (3 c) (4 d)))))

      (test-suite "curryn"
        (test-case "1"
          (check-equal? (curryn 0 list/kw 1) '(() () 1)))
        (test-case "1 / 2"
          (check-equal? ((curryn 1 list/kw 1) 2) '(() () 1 2)))
        (test-case "1 / 2 / 3"
          (check-equal? (((curryn 2 list/kw 1) 2) 3) '(() () 1 2 3)))
        (test-case "1 a"
          (check-equal? (curryn 0 list/kw 1 #:a "a")
                        '((#:a) ("a") 1)))
        (test-case "1 a / 2 b"
          (check-equal? ((curryn 1 list/kw 1 #:a "a") 2 #:b "b")
                        '((#:a #:b) ("a" "b") 1 2)))
        (test-case "1 a / 2 b / 3 c"
          (check-equal? (((curryn 2 list/kw 1 #:a "a") 2 #:b "b") 3 #:c "c")
                        '((#:a #:b #:c) ("a" "b" "c") 1 2 3))))

      (test-suite "currynr"
        (test-case "1"
          (check-equal? (currynr 0 list/kw 1) '(() () 1)))
        (test-case "1 / 2"
          (check-equal? ((currynr 1 list/kw 1) 2) '(() () 2 1)))
        (test-case "1 / 2 / 3"
          (check-equal? (((currynr 2 list/kw 1) 2) 3) '(() () 3 2 1)))
        (test-case "1 a"
          (check-equal? (currynr 0 list/kw 1 #:a "a")
                        '((#:a) ("a") 1)))
        (test-case "1 a / 2 b"
          (check-equal? ((currynr 1 list/kw 1 #:a "a") 2 #:b "b")
                        '((#:a #:b) ("a" "b") 2 1)))
        (test-case "1 a / 2 b / 3 c"
          (check-equal? (((currynr 2 list/kw 1 #:a "a") 2 #:b "b") 3 #:c "c")
                        '((#:a #:b #:c) ("a" "b" "c") 3 2 1)))))

    (test-suite "Eta Expansion"
      (test-suite "eta"
        (test-ok (define f (eta g))
                 (define g add1)
                 (check-equal? (f 1) 2)))
      (test-suite "eta*"
        (test-ok (define f (eta* g x))
                 (define g add1)
                 (check-equal? (f 1) 2))
        (test-bad (define f (eta* g x))
                  (define g list)
                  (f 1 2))))

    (test-suite "Parameter Arguments"

      (test-suite "lambda/parameter"
        (test-case "provided"
          (let* ([p (make-parameter 0)])
            (check = ((lambda/parameter ([x #:param p]) x) 1) 1)))
        (test-case "not provided"
          (let* ([p (make-parameter 0)])
            (check = ((lambda/parameter ([x #:param p]) x)) 0)))
        (test-case "argument order / provided"
          (let* ([p (make-parameter 3)])
            (check-equal? ((lambda/parameter (x [y 2] [z #:param p])
                                             (list x y z))
                           4 5 6)
                          (list 4 5 6))))
        (test-case "argument order / not provided"
          (let* ([p (make-parameter 3)])
            (check-equal? ((lambda/parameter (x [y 2] [z #:param p])
                                             (list x y z))
                           1)
                          (list 1 2 3))))))))