One more time: strict and nonstrict functions
------------------------------------------------------------------

_|_  means "no value" or "not defined" or "looping"

A function, f,  is *strict* if  f(_|_) = _|_ 

For a set ("domain") D, let  D_ =  D union { _|_ }.

Figure 3: Say that 

  Loc = {1,2,3}
  Store = { <a,b,c> | where a,b,c: Int }
  
  lookup : Loc x Store -> Int
    lookup(i, s) = s[i]
  update : Loc x Int x Store -> Store
    update(1, n, <a,b,c>) = <n,b,c>    (similar for locs 2 and 3)

  The above operations make no sense with _|_ as an argument.
  This one does:
  
  check : (Store -> Store_) x Store_ -> Store_
    check(f, _|_) = _|_
    check(f, <a,b,c>) =  f(<a,b,c>)
  

Commands in a traditional programming language cannot proceed from _|_:

C : Command -> Env -> Store -> Store_

C[[ I = E ]] = lam e. lam s.  update(find(I, e), E[[ E ]]e s, s)
   (this one always computes a Store answer)

C[[ loop ]] = lam e. lam s. _|_
   (this one always computes no value)

C[[ C1 ; C2 ]] = lam e. lam s.  check((C[[ C2 ]]e), (C[[ C1]]e s) )
    (this one computes C1 and checks the
     value before allowing C2 to proceed.)
     
Example:
  C[[ loop; z = 2 ]] init_env (init_store 5) 
    = check( C[[ z = 2 ]]init_env, _|_ )
    = _|_

  
============================
  
Can an assignment language be nonstrict?  Well, yes, but we must change how the store works: 
Say that 

  Loc = {0,1,2,...}
  NStore = { _|_ } union { (m,n)::s  |  m: Loc, n: Int, s: NStore }

    an "nstore" is a sequence of updates that are patched onto  _|_ using ::
  
  init_store = lam n. (1,n)::(2,0)::(3,0)::_|_
 
  lookup : Loc x NStore -> Int_
    lookup(m, _|_) = _|_
    lookup(m, (m,n)::s') = n
    lookup(m, (m',n')::s') =  lookup(m, s'),  if m != m'
    
  update : Loc x Int x NStore -> NStore
    update(m, n, s) = (m,n)::s     // this one is important!
    
  (Note: if you are suspicious of the phrase,  (m,n)::_|_,  then
   please see an alternative coding at the end of this note.)


Here is the semantics of the "nonstrict" assignment language with NStore:

P : Program -> Int -> Int_

P[[ C . ]] = lam n. lookup(3, C[[ C ]]init_env (init_store(n)) )


C : Command -> Env -> NStore -> NStore

C[[ I = E ]] = lam e. lam s.  update(find(I, e), E[[ E ]]e s, s)

C[[ loop ]] = lam e. lam s. _|_

C[[ C1 ; C2 ]] = lam e. lam s.  C[[ C2 ]]e (C[[ C1]]e s)



We can calculate that

P[[ z = x + 1. ]](5) 

  = lookup(3, C[[ z = x + 1 ]]init_env (init_store(5)) )

  = lookup(3, C[[ z = x + 1 ]]init_env ((1,5)::(2,0)::(3,0)::_|_) )
  
  = lookup(3, (3,6)::(1,5)::(2,0)::(3,0)::_|_ )
  
  = 6
 
We can calculate that 

P[[ y = x + 1; loop. ]](5)

  = lookup(3, C[[ y = x + 1; loop ]]init_env (init_store(5)) )
  
  = lookup(3, C[[ loop ]]init_env ((2,6)::(1,5)::(2,0)::(3,0)::_|_) )
  
  = lookup(3, _|_) = _|_
  

And we can calculate that

P[[ loop; z = 2. ]](5)

  = lookup(3, C[[ loop; z = 2 ]]init_env (init_store(5)) )
  
  = lookup(3,  C[[ z = 2 ]]init_env (C[[ loop ]]init_env (init_store(5)))  )

  = lookup(3,  update(3, 2, (C[[ loop ]]init_env (init_store(5))))  )
  
  = lookup(3, (3,2)::(C[[ loop ]]init_env (init_store(5))) )  =  lookup(3, (3,2)::_|_)
  
  = 2
  
The last example shows that commands are not strict about propagating _|_.  
The reason is that the  update  operation is not strict about the nstore argument.

If we wanted to implement this calculation on a conventional machine,
we would be forced to calculate the output "backwards" from the "end
of the program". 

This example stimulated thinking in the 1970s on demand-driven or
"lazy" computation and led to the development of the "lazy functional
language" Haskell.


==================

FINAL NOTE:  Here is how nonstrict store update was originally defined:


NStore = Loc -> Nat_     (stores are truly functions here)

  lookup(loc, s) = s(loc)

  update(loc, val, s) = lam loc'. if loc' = loc then val
                                  else s(loc')
  empty = \lam loc. _|_    
  
  init_store = \lam n. 
                   update(1, n, update(2, 0, update(3, 0, empty)))