"I mean, what is it you want to achieve or what problem are you specifically aiming to solve?"
Basically fluid-let in Scheme and I think maybe defvar in Common Lisp.
I think I got it working ok now in a somewhat reasonable way. It changes how T* works but I wanted some of these features for other reasons in the past.
*************
Problem 1. T* only accepts basic type signatures.
*************
Stock T* starts type checking inside the rule body and you can't control anything before that. The modified T* now starts at the top of the define and is restructured to allow you to inject things into the hypothesis context.
For example this function:
(define test4
{ context (*test2* : r number) (number --> number) }
N -> (+ N (fluid-value *test2*)) )
Type checking starts here:
(spy +)
typechecking (fn test4)
____________________________________________________________ 16 inferences
?- (shen.intro test4 (shen.rules test4 (shen.rule (N) (+ N (fluid-value *test2*))))) : Var10
1. test4 : (context (*test2* : r number) (number --> number))
The user can add rules like so:
(datatype ctx
(dynamic (env | P)), F : Sig >> (shen.intro F Rules) : A;
___________________________________________________________________________
F : (context P Sig) >> (shen.intro F Rules) : A;
______________________________________________________________________
(dynamic (env | P)), F : (context P (A --> B)) >> F : (A --> B);
Rules : (A --> B);
______________________________________________
F : (A --> B) >> (shen.intro F Rules) : C; )
F gets converted to (A --> B) and some assumptions are added to the context.
***********
Problem 2. Dynamic Scope vs Lexical Scope contexts
***********
Freeze and Lambda introduce new dynamic scopes, but the hypothesis list mostly just reflects the lexical state of things. Assumptions related to the current dynamic environment need to be reset inside a lambda because this environment won't be valid when the lambda is applied (they aren't allowed to capture any dynamic scope from the current lexical context).
Sequent calculus allows multiple context lists like gamma delta etc. We've talked about this before, but it would come in useful here to have a separate list for assumptions related to the dynamic scope.
That is a larger change though and I'll save it for later. For now, the second list is being simulated by tagging certain list entries with (dynamic).
The T* rule for freeze and lambda are also now changed to strip all (dynamic X) entries from the hypothesis list like so:
(defprolog system-S-h
(- [freeze Y]) [lazy B] Hyp <-- (bind Hyp2 (filter-dynamic Hyp)) (system-S-h Y B Hyp2);)
***********
Problem 3. Extend the types for freeze/lambda
***********
Any usage of 'fluid-value' inside an expression requires there to be an active (dynamic (env | A)) in the assumption list.
Therefore the default version of freeze/lambda in T* should fail (because all (dynamic A) entries are stripped from the list).
So we need to add a new version that creates an empty dynamic context and tags the result type with it, as well as a new version of thaw (called 'inject' below):
(datatype freeze2
(dynamic (env | New)) >> X : A;
_______________________________________________
(dynamic (env | _)) >> \\ TODO: needs to strip all dynamic
(freeze X) : (context New (lazy A));
(dynamic (env | New)) >> X : A;
_______________________________________________
(freeze X) : (context New (lazy A));
X : (context Want (lazy A));
(check-all Want Have);
__________________________________________
(dynamic (env | Have)) >> (inject X) : A;)
***********
Problem 4. Type fluid-let
***********
Note that it has to reach into the outer 'freeze' inside the body (if it exists) to inject the context below it, because the implementation of fluid-let needs to use lazy evaluation.
(datatype fluid-let
(update Key r A Have Have*);
(dynamic (env | Have*)) >> Body : C;
_____________________________________________
(- (let-body Have Key A C (freeze Body)));
!;
(dynamic (env | Have)) >> Value : A;
(let-body Have Key A C Body);
______________________________________________________________________
(dynamic (env | Have)) >> (fluid-let Key Value Body) : C;
Value : A;
(let-body () Key A C Body);
______________________________________________________________________
(fluid-let Key Value Body) : C;
(env? Key r A Have);
_________________________________________________________________
(dynamic (env | Have)) >> (fluid-value Key) : A;)
***********
Notes
***********
You could avoid the modifications in 'problem 1' if you were to wrap the body of function 'test4' in a 'freeze', and thereby obtain the (context A B) type as a result, but it isn't really that great of an option.
The modifications in problem 1 are useful for other things anyway.
***********
Tests
***********
\\ these all work (the 'let!' macro just expands out fluid-let and fluid-value as needed):
(define test3
{ (context (*test2* : r number) (lazy number)) --> number }
Thunk -> (let!
*test* 1
*test2* (+ 1 *test*)
(inject Thunk)) )
(define test4
{ context (*test2* : r number) (number --> number) }
N -> (+ N (fluid-value *test2*)) )
(fluid-let *test2* 10
(freeze (test4 1)))
(let X (type (freeze (fluid-value *test2*))
(context (*test2* : r number) (lazy number)))
(fluid-let *test2* 0
(freeze (test3 X))))
\\ And this one fails as intended
(let X (type (freeze (fluid-value *test2*))
(context (*test2* : r string) (lazy string)))
(fluid-let *test2* 0
(freeze (test3 X))))