I know ML is said to have static assignment, but I expected something
like "Error: Can't define foo because it already exists." The default
behavior just seems insane. I don't see how any significant program
could be maintainable under this system.
So a few questions:
1. How do you modify a running program? I'd like to prototype
algorithms using named variables and observe the effects of changing
the values of the variables.
2. How do you find all current values assigned to a name and where
they are scoped?
3. Do the newer functional languages like F# and Haskell also behave
this way?
It's a feature. Lexical scoping is the only reasonable semantics you
can have in a typed language.
> I know ML is said to have static assignment, but I expected something
> like "Error: Can't define foo because it already exists." The default
> behavior just seems insane. I don't see how any significant program
> could be maintainable under this system.
Disallowing shadowing would be a very bad idea, as it would make code
extremely fragile: you could break a perfectly fine piece of code just
by *adding* something somewhere else.
> 1. How do you modify a running program? I'd like to prototype
> algorithms using named variables and observe the effects of changing
> the values of the variables.
If you want to be able to change functions while the program is
running then you have to be explicit about it and wrap those functions
into mutable references.
But the more appropriate answer is: you don't. Typing in definitions
directly into the interactive toplevel is not how you develop ML
programs. You use a build system (CM, make, etc) to track dependencies
and rebuild the necessary parts of your program after changes. Some of
them, like SML/NJ's CM, even work interactively.
> 2. How do you find all current values assigned to a name and where
> they are scoped?
You seem to misunderstand lexical scoping. There is only ever one
value bound to a given name at any point in a program.
It is important to realise that names are just a syntactic way to
locally refer to an otherwise anonymous value. They don't have any
operational meaning beyond that. In particular, they don't imply any
kind of state in ML.
> 3. Do the newer functional languages like F# and Haskell also behave
> this way?
Yes, definitely.
> I've discovered the bizarre behavior of interactive ML when redefining
> objects. It allows you to create a new object with a repeated name,
> but objects referencing the name from an earlier stage keep it with
> the old value.
There's nothing bizarre about this.
> I know ML is said to have static assignment, but I expected something
> like "Error: Can't define foo because it already exists." The default
> behavior just seems insane.
Functional languages don't have assignment, they have binding. In a
typical imperative language, a name refers to an lvalue (i.e. a region of
memory). An assignment operation modifies the named lvalue. In a
functional language, a name is bound to a value. Rebinding the name causes
it to be bound to a different value. Anything which has previously been
evaluated will have replaced the name with the value.
> I don't see how any significant program could be maintainable under this
> system.
You can develop programs without ever using the interactive mode, so its
behaviour doesn't really matter.
What really would be bizarre was if the interactive mode implemented a
fundamentally different language (with C-like variables and assignment
rather than functional variables with binding).
> So a few questions:
>
> 1. How do you modify a running program? I'd like to prototype
> algorithms using named variables and observe the effects of changing
> the values of the variables.
You can probably use refs for this.
> 2. How do you find all current values assigned to a name and where
> they are scoped?
No idea.
> 3. Do the newer functional languages like F# and Haskell also behave
> this way?
Anything which doesn't behave this way isn't a functional language.
And I thought I understood lexical scoping and closures... But, the
behavior I'm remarking on is that ML allows a name to have multiple
bindings within the same scope. In other words, a single scope can
contain multiple objects with the same name. I don't understand how
that is beneficial.
Also, with redefinable bindings, the result of a program depends on
the order of evaluation. Doesn't that break referential transparency?
Part of my confusion comes from descriptions of F# Interactive which
describe it as a REPL that is used somewhat like a Lisp.
It is useful in conjunction with features like open/include, where
requiring disjointness would be a major pain (in fact, OCaml imposes
certain restrictions on this, and it *is* sometimes painful). It also
is useful for certain idioms of functionally "updating" values without
having to reinvent new names all the time. For example:
val s = init()
val s = stepX s
val s = stepY s
val s = stepZ s
Now imagine you want to temporarily disable step Y in the middle --
just comment it out.
> Also, with redefinable bindings, the result of a program depends on
> the order of evaluation. Doesn't that break referential transparency?
Not at all. You are not "redefining" anything, it's just shadowing.
Think of it as a sequence of nested "let" expressions. The order of
evaluation does not matter for that. In fact, evaluation order does
not matter *because* of the shadowing semantics -- it would if it was
interpreted as redefining existing bindings.
(That said, order of evaluation matters in ML for other reasons.)
> On Dec 16, 11:31 pm, Andreas Rossberg <rossb...@mpi-sws.org> wrote:
>> Lexical scoping is the only reasonable semantics you
>> can have in a typed language.
>
> And I thought I understood lexical scoping and closures... But, the
> behavior I'm remarking on is that ML allows a name to have multiple
> bindings within the same scope. In other words, a single scope can
> contain multiple objects with the same name. I don't understand how
> that is beneficial.
Strictly speaking, you start a new scope whenever you define a new
variable in a let-construct with multiple bindings:
let
val x = e1
val x = f(x)
in g(x) end
is expanded into
let
val x = e1
in
let
val x = f(x)
in g(x) end
end
In the expanded version it is clear that a new scope is introduced.
> Also, with redefinable bindings, the result of a program depends on
> the order of evaluation. Doesn't that break referential transparency?
The order of evaluation in SML is fixed to be left-to-right, so no.
Also, the expanded version of the multiple bindings make the order
explicit.
Note that SML does allow assignment, but you use a different notation
for that:
val x = ref 7 (* make x a reference to an assignable cell
initialised to 7 *)
x := 18 (* update the cell to be 18 *)
!x (* get the contents of the cell *)
Using these, you can write an imperative while-loop:
fun factorial n =
let
val s = ref n
val p = ref 1
in
while !s>0 do (
p := !p * !s;
s := !s - 1
);
!p
end
which returns n factorial.
However, this is not considered good style. You would normally write
the factorial function using recursion:
fun factorial n =
if n=0 then 1
else n * factorial(n-1)
Torben
What you've assumed to be "the same scope" is actually different scopes.
--
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
http://www.ffconsultancy.com/?u