6.2.1 Studies and Steps
| (require congame/components/study) | package: congame-core |
procedure
(make-study name steps [ #:requires requires #:provides provides #:transitions transitions #:view-handler view-handler #:failure-handler failure-handler]) → study? name : string? steps : (listof step?) requires : (listof symbol?) = null provides : (listof symbol?) = null transitions : (or/c #f (hash/c symbol? any/c)) = #f view-handler : (or/c #f (-> request? response?)) = #f failure-handler : (or/c #f (-> step? any/c step-id/c)) = #f
The #:transitions argument has to be a transition-graph that provides all the transitions between the steps. See transition-graph for details.
procedure
s : study? req : request? = (current-request) bindings : (hash/c symbol? any/c) = (hasheq)
procedure
6.2.1.1 Study variables
A study variable is a variable whose value is recorded in the study server database.
A variable with participant scope will store/reference a separate value for each participant in each separate instance.
A variable with instance scope will store/reference a separate value for each study instance, but the value is shared by all participants in a given study instance.
The value of the study variable will be stored in the Congame server database under the current study → instance → participant.
Study variables created with defvar* will additionally be visible to any child studies (see defstep/study).
Important: when using defvar*, you must provide a second identifier global-id and manually ensure it is distinct from any identifiers that may be used in child studies. This prevents child studies that may be using the same identifier names from accidentally overwriting your parent study’s variable.
syntax
(defvar/instance ...)
syntax
(defvar*/instance ...)
procedure
(undefined? v) → boolean?
v : any/c
syntax
(if-undefined study-var alt)
procedure
(call-with-study-transaction proc) → any/c
proc : (-> any/c)
syntax
(with-study-transaction expr ...)
Important: You should use one of these forms when you’re doing multiple operations that depend on each other and which involve instance-scoped variables (that is, variables with instance scope, created with defvar/instance or defvar*/instance). Variables with instance scope can be updated by other participants at any time, so it is possible that the variable could change between the time when its value is read and when it is updated.
Example:
(defvar/instance group-score) ;; ... (define (bad-update) (when (> score 100) ;; Bad: another participant could change the score before the next line runs! (set! score (+ score 50)))) (define (good-update) (with-study-transaction ; Prevents changes by anyone else during this code (when (> score 100) (set! score (+ score 50)))))
Note that each highlighted expression above is a separate reference to the score variable; without the use of a transaction, other study participants could change its value at any point between those expressions.
You don’t need with-study-transaction for single operations; a single read, or a single write of a literal value, is safe. But the moment you have a sequence of operations where later steps depend on earlier ones, and those steps touch instance variables, use with-study-transaction to keep everything atomic and consistent.
In concrete technical terms, call-with-study-transaction enters a database transaction with an isolation level of 'serializable.
6.2.1.2 Steps
procedure
(make-step id handler [ transition #:view-handler view-handler #:for-bot bot-handler]) → step? id : symbol? handler : (-> xexpr?) transition : transition/c = (lambda () next) view-handler : (or/c #f (-> request? response?)) = #f bot-handler : (or/c #f procedure?) = #f
procedure
(make-step/study id s [ transition #:require-bindings require-bindings #:provide-bindings provide-bindings]) → step? id : symbol? s : study? transition : transition/c = (lambda () next) require-bindings : (listof binding/c) = null provide-bindings : (listof binding/c) = null
The #:require-bindings argument maps identifiers required by s to identifiers available in the current study context if the name is different – otherwise it assumes that required identifers share names and attempts to set them accordingly.
The #:provide-bindings argument maps identifiers in the current study that should be mapped to some subset of the identifiers provided by s upon completion. When #:provide-bindings is null?, no values are assigned.
For example:
(make-step/study 'required-tasks task-study #:require-bindings '([n task-treatment]) #:provide-bindings '([root-success? success?]))
Here, n in task-study will take on the value of task-treatment, and after running, root-success? will be assigned the value of success? in the parent.
The predicate next? returns #t if v is identical to next, #f otherwise.
The predicate done? returns #t if v is identical to done, #f otherwise.
6.2.1.3 Step Widgets
procedure
(button action label [ #:id id #:to-step-id to-step-id]) → xexpr? action : (-> void?) label : xexpr? id : string? = "" to-step-id : (or/c #f symbol?) = #f
The #:id argument is useful for identifying the button within bot handlers.
procedure
(form f action render [ #:id id #:enctype enctype #:combine combine-proc #:defaults defaults]) → xexpr? f : form? action : (-> void?) render : (-> (widget-renderer/c) xexpr?) id : string? = "" enctype : string? = "multipart/form-data" combine-proc : (-> any/c any/c any/c any/c) = (λ (k v1 v2) v2) defaults : hash? = (hash)
The #:id argument is useful for identifying the button within bot handlers.
syntax
(when-bot expr)
6.2.1.4 Study loops
| (require congame/components/for-study) | |
| package: congame-core | |
syntax
(for/study [#:substudies] [#:requires requires] [#:provides provides] (for-clause ...) body-or-break ... body)
requires : (listof symbol?)
provides : (listof symbol?)
Use for/study for quickly building studies with many steps that differ in only a few places.
(for/study ([phase (in-list '("Setup" "Activation" "Tear-down"))]) (page (haml (:h1 phase " Phase") (:p "...") (button void "Next"))))