Standard ML - Update global variable within a let-in-end expression? - scope

Is there any way to update a variable binded in global scope within a let-in-end expression?
For example if I have a global variable:
val playerScore = 0;
and then the function:
fun hit (option:int) =
if option = 2 then
printStay(playerScore, dealerScore)
else
let
val cardDrawn = showCard(hd deck)
val playerScore = playerScore + getValue(hd deck)
in
print ("You chose to hit\n" ^ cardDrawn ^ "Dealer Score: " ^ Int.toString(dealerScore) ^ "\nPlayer Score: " ^ Int.toString(playerScore) ^ "\n")
end;
The value of playerScore is updated in the let expression and prints the correct value in the "in" clause but the actual value of playerScore is not saved and resets after this expression finishes.
Is there anyway to retain the value of playerScore on a global level after this expression executes? The problem is that I cannot keep track of the score since it resets to 0 every time.
Thanks

A "variable" can never be assigned to in ML. The value of playerScore, once initialized, will never change. Period.
In your example, you are declaring a new variable called playerScore in an inner scope, which hides the outer one. It is a different variable, with no relation to the outer one of the same name.
If you want to use mutability, you can use a mutable data structure, the simplest of which is a "ref" cell, which is a single-item mutable cell. You can use the ref function to create a ref data structure, the ! operator to access its contents, and the := operator to change its contents. But mutation only occurs through mutable data structures like ref, arrays, etc. Variables cannot be changed.

Related

equivalent of .copy() for functions

Let's assume I work with a collection of objects and I want to define one of their methods at creation. For example:
class Operator:
def __init__(self, id, operation):
self.id = id
self.operation = operation
def test(self, arg):
print(self.operation(arg))
operators = []
for i in range(4):
op = lambda x: x+i
operators.append(Operator(i, op))
operators[0].test(5)
operators[1].test(5)
operators[2].test(5)
operators[3].test(5)
Here one could naively assume that we would get "5", "6", "7" and "8", but instead we only get 8s. I suppose this is because every time op is redefined it also gets redefined inside every Operator object.
I don't want that, I want each Operator to have its own operation method. I could do:
operators = []
ops = []
for i in range(4):
ops.append(lambda x: x+i)
operators.append(Operator(i, ops.pop()))
but it feels like a dirty hack.
Is there a cleaner way to do that?
I suppose this is because every time op is redefined it also gets
redefined inside every Operator object.
I don't want that, I want each Operator to have its own operation
method.
The function objects in the operation fields of each Operation object are different. Every time you evaluate lambda, it creates a new function object. For example, operators[0].operation is operators[1].operation is false, and id(operators[0].operation), id(operators[1].operation), etc. are all different.
But Python functions capture outside variables by reference, and in this case, the four separate function objects all capture the same variable, i, and the state of that variable is shared between the functions and the outside scope it was captured from. So when i was changed in that outside scope, it can be seen by all the functions.
If you want each function to capture a different value as you iterate through the loop, there are several ways to achieve this.
One way is to use an optional parameter with a default argument, like this:
for i in range(4):
op = lambda x, i=i: x+i
operators.append(Operator(i, op))
Here, the function does not capture i in the outer scope. Rather, it has an optional parameter, also named i though it's independent of the i in the outer scope. (We name it the same so that the expression inside can use the same variable name.) The default argument of this optional parameter, is the value of the outer-scope i evaluated at the time the lambda is evaluated. Since it's the value that is remembered, and not a reference to the variable i, each function will remember a different value. When the function is called without a second argument, it will use the remembered default argument as the argument of the parameter i inside the function.
An uglier way is to put an immediately-executed lambda around the capture.
for i in range(4):
operators.append((lambda i: Operator(i, lambda x: x+i))(i))
This works because the i in the execution of the outer lambda is different from the outer-scope i, and the i in the execution of the outer lambda only exists for the duration of the execution of that lambda, in which the value of its parameter i is passed in and never changed inside the lambda.
This can also be written as a list comprehension:
operators = [(lambda i: Operator(i, lambda x: x+i))(i) for i in range(4)]
or with map():
operators = list(map(lambda i: Operator(i, lambda x: x+i), range(4)))

Functions - Variables - Scope

I have the below code.
a = 10
def f(x):
return x + a
a = 3
f(1)
If we print(f1) the result is 4. It seems that the second assignment of a is used here (a=3). Could someone explain to me if there is a particular reason or rule for not using the first assignment (a=10)?
Thank you,
Dimitrios
a and x are resolved each time return x + a is executed. You reassign a before calling f. f knows that a is not a local variable so looks up "a" in its enclosing scope. That "a" has a 3 in it, and that is what is used.
As a general rule, it would be very bad for f to use the original assignment when its executed. That would mean that functions couldn't take advange of global variables changing. It would also require that global variables be assigned before the function is even defined, which is not particularly useful for a dynamic language.
You can hack it, though. If you define a parameter with a default value, it will be assigned when the function is defined and you get what you want.
a = 10
def f(x, a=a):
return x + a
a = 3
print(f(1))
You see, you've defined a before you defined f(x). But your call to f(1) was after you assigned the value 3 to "a".
f(x) will always use the current value of "a" when it its called, not when it is defined. If you want a constant value for "a" inside f(x), you should declare it inside the function, something like:
def f(x):
a = 10
return x + a

Why += is considered as an assignment operator while >= is not?

I was thinking why the '=' in '+=' is considered as assignment while '=' in '>=' is not considered as such. There is no importance behind this question but some random thought of a beginner. For example purpose, you can consider that
a = np.array([1,2,3,4,5])
a += 2 # array updated and assigned to a
a>=2 # becomes a boolean indexing filter
a += 2 changes the value of a. It reassigns it's value to be two more than it was before. By the way, += as a whole is the assignment operator, not just the =.
a>=2 does not change the value of a. Yes, it becomes true or false. But 'true' or 'false' is all it is. It does not get assigned to anything. The value of a is as it was before.
You can do b = a>=2. But in that case, = is the assignment operator because it is what assigns the value to b.

Problem in pushing a value to an element of struct in Julia

Say I have a struct:
mutable struct DataHolder
data1::Vector{Float64}
data2::Vector{Float64}
function DataHolder()
emp = Float64[]
new(emp, emp)
end
end
d = DataHolder()
When I try to push a value to only one element of struct d by doing:
push!(d.data1, 1.0)
the value is pushed not only d.data1 but also d.data2. Indeed, the REPL says
julia> d
DataHolder([1.0], [1.0])
How can I push a value to only one element of the struct??
Your problem is not in push!, but rather in the inner constructor of DataHolder. Specifically:
emp = Float64[]
new(emp, emp)
This code pattern means that the fields of the new DataHolder both point toward the same array (in memory). So if you mutate one of them (say, via push!), you also mutate the other.
You could instead replace those two lines with:
new(Float64[], Float64[])
to get the behaviour you want.
More generally, although it is not forbidden, your use of the inner constructor is a bit odd. Typically, inner constructors should have a method signature corresponding exactly to the fields of your struct, and the inner constructor itself is typically only used to provide a set of universal tests that any new DataHolder should undergo.
Personally I would re-write your code as follows:
mutable struct DataHolder
data1::Vector{Float64}
data2::Vector{Float64}
function DataHolder(data1::Vector{Float64}, data2::Vector{Float64})
#Any tests on data1 and data2 go here
new(data1, data2)
end
end
DataHolder() = DataHolder(Float64[], Float64[])
If you don't need to do any universal tests on DataHolder, then delete the inner constructor entirely.
Final food for thought: Does DataHolder really need to be mutable? If you only want to be able to mutate the arrays in data1 and data2, then DataHolder itself does not need to be mutable, because those arrays are already mutable. You only need DataHolder to be mutable if you plan to completely re-allocate the values in those fields, e.g. an operation of the form dh.data1 = [2.0].
UPDATE AFTER COMMENT:
Personally I don't see anything wrong with DataHolder() = DataHolder(Float64[], ..., Float64[]). It's just one line of code and you never have to think about it again. Alternatively, you could do:
DataHolder() = DataHolder([ Float64[] for n = 1:10 ]...)
which just splats an a vector of empty vectors into the constructor arguments.
#ColinTBowers has answered your question. Here is a very simple and a more general implementation:
struct DataHolder # add mutable if you really need it
data1::Vector{Float64}
data2::Vector{Float64}
end
DataHolder() = DataHolder([], [])
Perhaps you'd like to allow other types than Float64 (because why wouldn't you?!):
struct DataHolder{T}
data1::Vector{T}
data2::Vector{T}
end
DataHolder{T}() where {T} = DataHolder{T}([], [])
DataHolder() = DataHolder{Float64}() # now `Float64` is the default type.
Now you can do this:
julia> DataHolder{Rational}()
DataHold{Rational}(Rational[], Rational[])
julia> DataHolder()
DataHold{Float64}(Float64[], Float64[])

How to create an array of functions which partly depend on outside parameters? (Python)

I am interested in creating a list / array of functions "G" consisting of many small functions "g". This essentially should correspond to a series of functions 'evolving' in time.
Each "g" takes-in two variables and returns the product of these variables with an outside global variable indexed at the same time-step.
Assume obs_mat (T x 1) is a pre-defined global array, and t corresponds to the time-steps
G = []
for t in range(T):
# tried declaring obs here too.
def g(current_state, observation_noise):
obs = obs_mat[t]
return current_state * observation_noise * obs
G.append(g)
Unfortunately when I test the resultant functions, they do not seem to pick up on the difference in the obs time-varying constant i.e. (Got G[0](100,100) same as G[5](100,100)). I tried playing around with the scope of obs but without much luck. Would anyone be able to help guide me in the right direction?
This is a common "gotcha" to referencing variables from an outer scope when in an inner function. The outer variable is looked up when the inner function is run, not when the inner function is defined (so all versions of the function see the variable's last value). For each function to see a different value, you either need to make sure they're looking in separate namespaces, or you need to bind the value to a default parameter of the inner function.
Here's an approach that uses an extra namespace:
def make_func(x):
def func(a, b):
return a*b*x
return func
list_of_funcs = [make_func(i) for i in range(10)]
Each inner function func has access to the x parameter in the enclosing make_func function. Since they're all created by separate calls to make_func, they each see separate namespaces with different x values.
Here's the other approach that uses a default argument (with functions created by a lambda expression):
list_of_funcs = [lambda a, b, x=i: a*b*x for i in range(10)]
In this version, the i variable from the list comprehension is bound to the default value of the x parameter in the lambda expression. This binding means that the functions wont care about the value of i changing later on. The downside to this solution is that any code that accidentally calls one of the functions with three arguments instead of two may work without an exception (perhaps with odd results).
The problem you are running into is one of scoping. Function bodies aren't evaluated until the fuction is actually called, so the functions you have there will use whatever is the current value of the variable within their scope at time of evaluation (which means they'll have the same t if you call them all after the for-loop has ended)
In order to see the value that you would like, you'd need to immediately call the function and save the result.
I'm not really sure why you're using an array of functions. Perhaps what you're trying to do is map a partial function across the time series, something like the following?
from functools import partial
def g(current_state, observation_noise, t):
obs = obs_mat[t]
return current_state * observation_noise * obs
g_maker = partial(g, current, observation)
results = list(map(g_maker, range(T)))
What's happening here is that partial creates a partially-applied function, which is merely waiting for its final value to be evaluated. That final value is dynamic (but the first two are fixed in this example), so mapping that partially-applied function over a range of values gets you answers for each value.
Honestly, this is a guess because it's hard to see what else you are trying to do with this data and it's hard to see what you're trying to achieve with the array of functions (and there are certainly other ways to do this).
The issue (assuming that your G.append call is mis-indented) is simply that the name t is mutated when you loop over the iterator returned by range(T). Since every function g you create stores returns the same name t, they wind up all returning the same value, T - 1. The fix is to de-reference the name (the simplest way to do this is by sending t into your function as a default value for an argument in g's argument list):
G = []
for t in range(T):
def g(current_state, observation_noise, t_kw=t):
obs = obs_mat[t_kw]
return current_state * observation_noise * obs
G.append(g)
This works because it creates another name that points at the value that t references during that iteration of the loop (you could still use t rather than t_kw and it would still just work because tg is bound to the value that tf is bound to - the value never changes, but tf is bound to another value on the next iteration, while tg still points at the "original" value.

Resources