Logical Student Language
#lang lsl | package: lsl-lib |
The Logical Student Language (LSL) is a teaching language that extends the Intermediate Student Language (ISL) from How to Design Programs with features that support formally reasoning about programs. In particular, LSL tightly integrates contracts and property-based randomized testing.
1 Inherited from ISL
2 Extended from ISL
syntax
(define-struct structure-name (field-name ...))
make-structure-name : takes a number of arguments equal to the number of fields in the structure, and creates a new instance of that structure.
structure-name-field-name : takes an instance of the structure and returns the value in the field named by field-name.
set-structure-name-field-name! : takes an instance of the structure and a value, and sets the field named by field-name in the given instance of the structure to the given value.
structure-name? : takes any value, and returns #t if the value is an instance of the structure.
The name of the new functions introduced by define-struct must not be the same as that of other functions or variables, otherwise define-struct reports an error.
(define-struct posn [x y]) (define a-posn (make-posn 0 0)) (posn-x a-posn) 0
syntax
(define-mutable-struct structure-name (field-name ...))
(define-mutable-struct box [value]) (define a-box (make-box 0)) (set-box-value! a-box 1) (box-value a-box) 1
syntax
(begin expr0 ... expr)
syntax
(set! id expr)
syntax
(raise (make-struct-name ...))
> (define-struct foo ()) > (raise (make-foo)) exception raised: (make-foo)
syntax
(check-raises expression)
(check-raises expression structure-name)
> (define-struct foo ()) > (define (always-raise) (raise (make-foo))) > (check-raises (always-raise)) > (check-raises (always-raise) foo) > (define-struct bar ()) > (check-raises (always-raise) bar)
--------------------
FAILURE
name: check-exn
location: eval:15:0
params:
'(#<procedure:...te/library/test.rkt:214:9>
#<procedure:...te/library/test.rkt:217:9>)
message: "Wrong exception raised"
exn-message: "exception raised: (make-foo)"
exn:
#(struct:exn:fail:lsl:user "exception raised: (make-foo)" #<continuation-mark-set> ...)
--------------------
procedure
(remove-duplicates l) → list?
l : list?
> (remove-duplicates '(0 1 1 2)) '(0 1 2)
3 Testing
syntax
(test-suite "name" ...)
4 Contracts
syntax
(define-contract id contract)
(define-contract IntToInt (-> Integer Integer))
syntax
(: id contract)
(: on-two-and-three (-> (-> Integer Integer) Integer))
(define (on-two-and-three g) (+ (g 2) (g 3)))
Uses of on-two-and-three are protected:
> (on-two-and-three (lambda (x) (* x x))) 13
> (on-two-and-three (lambda (x) (/ x 2))) on-two-and-three: contract violation
expected: Integer
given: 3/2
blaming: program (as client)
4.1 Primitives
syntax
(Immediate clause ...)
(define-contract Even (Immediate (check (lambda (x) (and (integer? x) (even? x)))) (generate (lambda (fuel) (* 2 (contract-generate Integer fuel)))) (feature "positive?" positive?) (shrink (lambda (fuel x) (let ([y (/ x 2)]) (if (even? y) y (sub1 y)))))))
syntax
(check predicate-expr)
The predicate-expr is expected to produce a predicate that determines if a value satisfies the contract.
syntax
(generate gen-expr)
The gen-expr should evaluate to a function that takes a single natural number, the fuel, and produces a value that satisfies the contract. Fuel provides a rough measure of how hard the generator is willing to work to produce a value.
syntax
(feature name-string feature-expr)
syntax
(shrink shrink-expr)
The shrink-expr function takes two arguments, fuel and a value to shrink. The value to shrink is guaranteed to satisfy the contract.
syntax
(Function clause ...)
(: double (Function (arguments [x Integer]) (result (lambda (y) (= x y)))))
(define (double x) (* 2 x))
> (double 0) 0
> (double 1) double: contract violation
expected: (lambda (y) (= x y))
given: 2
blaming: program (as server)
syntax
(arguments [id contract] ...)
Describes the contracts on arguments. Any id can be used in any contract and will be bound to the concrete argument when the function is applied, so long as there is no cyclic dependency.
syntax
(result contract)
Describes the result contract. All id values are available in contract.
syntax
(raises exn-id)
Permits the exn-id struct to be raised in the function. Such exceptions are not considered failures during property-based testing.
syntax
(List contract)
> (contract-generate (List Integer)) '(27 -18 19 -17 -36 -11)
syntax
(Tuple contract ...)
> (contract-generate (Tuple Boolean Integer)) '(#f -49)
syntax
(OneOf contract ...)
> (contract-generate (List (OneOf Boolean Integer)))
'(#t
39
-21
#f
-11
-26
-35
#f
31
-5
#f
-43
-12
-46
25
#f
44
#t
11
-24
-11
-47
27
#t
-26
-24)
syntax
(AllOf contract ...)
> (define-contract UnitInterval (AllOf Real (lambda (x) (< 0 x)) (lambda (x) (< x 1)))) > (: reciprocal (-> UnitInterval Real)) > (define (reciprocal x) (/ 1 x)) > (reciprocal 1/2) 2
> (reciprocal -2) reciprocal: contract violation
expected: (lambda (x) (< 0 x))
given: -2
blaming: program (as client)
> (reciprocal 2) reciprocal: contract violation
expected: (lambda (x) (< x 1))
given: 2
blaming: program (as client)
syntax
(Struct struct-id (field-contract ...))
> (define-struct my-point (x y)) > (contract-generate (Struct my-point [Integer Integer])) (make-my-point 34 25)
syntax
(All (id ...) contract)
syntax
(Exists (id ...) contract)
> (: counter-pkg (Exists (A) (Tuple (-> A) (-> A A) (-> A Integer))))
> (define counter-pkg (list (λ () 0) (λ (x) (+ x 1)) (λ (x) x))) > (define make-counter (first counter-pkg)) > (define counter-incr (second counter-pkg)) > (define counter-get (third counter-pkg)) > (counter-get (counter-incr (make-counter))) 1
4.2 Derived
syntax
syntax
syntax
syntax
syntax
syntax
syntax
syntax
syntax
syntax
(Constant expr)
syntax
(Maybe contract)
To use a Maybe value, you must check if the output is a Boolean with false?, or by using the contract for the associated type.
> (: bigger-than-three (-> Integer (Maybe Integer)))
> (define (bigger-than-three n) (if (> n 3) n #f))
> (let ([x (bigger-than-three 1)]) (if (false? x) "small" x)) "small"
> (filter integer? (map bigger-than-three (list 1 2 3 4 5))) '(4 5)
syntax
(-> arg-contract ... result-contract)
syntax
(Record maybe-folding trace-id)
maybe-folder =
| folding-expr
The name of this contract rhymes with sword since the contract records values in a trace.
> (: down-prev (Maybe Natural)) > (define down-prev #f) > (: down-folder (-> (Maybe Natural) Natural Natural))
> (define (down-folder prev cur) (cond [(equal? prev cur) #f] [else cur])) > (: down (-> (AllOf Natural (Record down-folder down-prev))))
> (define down (let ([x 3]) (lambda () (begin (set! x (if (< x 1) 0 (sub1 x))) x)))) > (down) 2
> (down) 1
> (down) 0
> (down) down-folder: contract violation
expected: Natural
given: #f
blaming: program (as server)
> (define-contract Increasing (AllOf (List Real) (lambda (xs) (equal? (sort xs <) xs)))) > (: up-results Increasing) > (define up-results '()) > (: up (-> (AllOf Natural (Record up-results))))
> (define up (let ([x 0]) (lambda () (begin (set! x (modulo (add1 x) 4)) x)))) > (up) 1
> (up) 2
> (up) 3
> (up) contract violation
expected: (lambda (xs) (equal? (sort xs <) xs))
given: '(1 2 3 0)
5 Property-Based Randomized Testing
syntax
(check-contract id)
(check-contract id attempts) (check-contract id attempts size-expr)
> (: bigger-even (-> Integer Even))
> (define (bigger-even x) (* 3 x)) > (check-contract bigger-even 20)
--------------------
FAILURE
params: '(#<procedure:.../syntax/compile.rkt:194:7> 20)
name: check-contract
location: eval:72:0
discovered a counterexample
counterexample: (bigger-even 1)
error:
bigger-even: contract violation
expected: Even
given: 3
blaming: program (as server)
--------------------
syntax
(contract-generate contract maybe-fuel)
maybe-attempts =
| nat-expr
> (contract-generate Even) -54
syntax
(with-traces body ...)
> (define-contract AtMostOne (AllOf Natural (lambda (x) (<= x 1)))) > (: num-calls AtMostOne) > (define num-calls 0) > (: say-hi (-> (Record (lambda (old _) (add1 old)) num-calls))) > (define (say-hi) "hi")
> (with-traces (say-hi)) "hi"
> (with-traces (say-hi)) "hi"
> (with-traces (say-hi) (say-hi)) contract violation
expected: (lambda (x) (<= x 1))
given: 2
6 State Machines
procedure
(machine-accepting? x) → boolean?
x : any/c
procedure
(machine-accepts? m l) → boolean?
m : machine? l : list?
procedure
(machine-next m x) → machine?
m : machine? x : any/c
6.1 DFAs
syntax
(dfa start (end ...) [state ([in next-state] ...)] ...)
> (define M (dfa s1 (s1) [s1 ([0 s2] [2 s1])] [s2 ([0 s1] [2 s2])])) > (machine-accepts? M (list 2 0 2 0 2)) #t
> (machine-accepts? M (list 0 2 0 2 0)) #f
6.2 Regular Expressions
syntax
(re re-form)
> (define R (re (star (seq 'a 'b)))) > (machine-accepts? R (list 'a 'b 'a 'b)) #t
> (machine-accepts? R (list 'a 'b 'a)) #f
A literal value recognizes itself.
syntax
(complement re)
Recognizes anything that is not recognized not re.
syntax
(seq re ...)
Recognizes a sequence of regular expressions.
syntax
(seq-prefix re ...)
Recognizes prefixes of the given regular expression. In other words, (seq-prefix x y z) is the same as (union (epsilon) x (seq x y) (seq x y z)).
syntax
(union re ...)
Recognizes any of the given re.
syntax
(star re)
Recognizes zero or more repetitions of re.
syntax
(epsilon)
Recognizes the empty list.
7 Resources
Outputs a visualization graphing how much time f takes for each of the given args.
8 Concurrency
Concurrency is not the same as parallelism. In parallelism, computations occur simultaneously, not just in an arbitrary order.
An actor system consists of a number of actors (or processes) that are identified by a unique name. Actors send immutable values (or messages) to one another. These messages are then processed by the receiving actor. Messages are guaranteed to be delivered in FIFO (first-in-first-out) between two actors. However, the order in which messages are delivered among all actors is unspecified. When starting an actor system, a scheduler can be provided to fix some delivery policy.
See start for an example of a concurrent system.
syntax
(Packet contract)
procedure
(packet-from p) → String
p : (Packet Any)
procedure
(packet-msg sp) → Any
sp : (Packet Any)
syntax
(SendPacket contract)
(: sp (SendPacket Natural)) (define sp (send-packet "process-1" 42))
procedure
(send-packet to msg) → (SendPacket Any)
to : string? msg : any/c
procedure
(send-packet-to sp) → String
sp : (SendPacket Any)
procedure
(send-packet-msg sp) → Any
sp : (SendPacket Any)
procedure
(send-packet? x) → Boolean
x : Any
syntax
(ReceivePacket contract)
(: rp (ReceivePacket Natural)) (define rp (receive-packet "process-1" 42))
procedure
(receive-packet from msg) → (ReceivePacket Any)
from : string? msg : any/c
procedure
(receive-packet-from rp) → String
rp : (ReceivePacket Any)
procedure
(receive-packet-msg rp) → Any
rp : (ReceivePacket Any)
procedure
(receive-packet? x) → Boolean
x : Any
syntax
(Action contract)
(: a (Action Natural))
(define a (action 5 (list (send-packet "process-1" 42) (send-packet "process-1" 43) (send-packet "process-2" "Hello!"))))
syntax
syntax
(process clause ...)
clause = (name s) | (on-start handler) | (on-receive handler)
syntax
s : String Names a process. This needs to be unique across all processes in a system.
The handler function is applied to the list of all the other processes (as strings) when the process starts. The result of the call is an action, containing the new state of the process and a list of packets to send to other processes.
syntax
handler : (-> contract ReceivePacket (Action contract)) The handler function is applied to the process’s state and a packet when the process receives a new packet. The result of the call is an action, containing the new state of the process and a list of packets to send to other processes.
The example below demonstrates a concurrent program with two processes, a and b, which send greetings back and forth. The program starts with a greeting b. Each process has its own state, remembering how many greetings it has sent so far. Once each process has sent three greetings, they stop greeting, and the concurrent program terminates.
(define MAX-GREETINGS 3) (: a-receive (-> Natural (ReceivePacket String) (Action Natural))) (define (a-receive state pkt) (cond [(>= state MAX-GREETINGS) (action state (list))] [else (let ([new-state (add1 state)] [messages-to-send (list (send-packet "b" "Hi b!"))]) (action new-state messages-to-send))])) (: b-receive (-> Natural (ReceivePacket String) (Action Natural))) (define (b-receive state pkt) (cond [(>= state MAX-GREETINGS) (action state (list))] [else (let ([new-state (add1 state)] [messages-to-send (list (send-packet "a" "Hi a!"))]) (action new-state messages-to-send))])) (start first (list (process (name "a") (on-start (λ (others) (action 1 (list (send-packet "b" "Hi b!"))))) (on-receive a-receive)) (process (name "b") (on-start (λ (others) (action 0 (list)))) (on-receive b-receive)))) '(("b" 3) ("a" 3))
procedure
(start-debug scheduler processes) → (List (Tuple String Any))
scheduler : (-> (List (Packet Any)) (Packet Any)) processes : (List Process)
procedure
(start-gui scheduler processes summarize-state) → (List (Tuple String Any)) scheduler : (-> (List (Packet Any)) (Packet Any)) processes : (List Process) summarize-state : (-> Any Any)
The extra argument, summarize-state, is a function that is applied to the process state before it is displayed, since large process states can make the interface harder to use. The identity function can be provided if you want the entire process state visible.