Discussion:
Interactive ML weirdness
(too old to reply)
Wendell
2009-12-17 00:01:28 UTC
Permalink
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.

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?
Andreas Rossberg
2009-12-17 07:31:05 UTC
Permalink
Post by Wendell
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.
It's a feature. Lexical scoping is the only reasonable semantics you
can have in a typed language.
Post by Wendell
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.
Post by Wendell
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.
Post by Wendell
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.
Post by Wendell
3. Do the newer functional languages like F# and Haskell also behave
this way?
Yes, definitely.
Wendell
2009-12-18 05:11:34 UTC
Permalink
Post by Andreas Rossberg
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.

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.
Andreas Rossberg
2009-12-18 08:30:31 UTC
Permalink
Post by Wendell
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.
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.
Post by Wendell
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.)
Torben Ægidius Mogensen
2009-12-18 10:02:19 UTC
Permalink
Post by Wendell
Post by Andreas Rossberg
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.
Post by Wendell
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
Jon Harrop
2009-12-18 14:30:15 UTC
Permalink
But, the behavior I'm remarking on is that ML allows a name to have
multiple bindings within the same scope.
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
Nobody
2009-12-18 00:39:10 UTC
Permalink
Post by Wendell
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.
Post by Wendell
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.
Post by Wendell
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).
Post by Wendell
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.
Post by Wendell
2. How do you find all current values assigned to a name and where
they are scoped?
No idea.
Post by Wendell
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.
scattered
2009-12-18 15:35:45 UTC
Permalink
[snip]
Post by Wendell
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.
[snip]

I won't cover ground that others have already done so ably. You can't
modify running programs in any straightforward way that I know of, but
perhaps you can accomplish what you want by creating a higher-order
function which takes as inputs the names in question and returns as
output a function which implements the algorithm with the bindings
that you want.

Here is a contrived example:

say you wanted to create a function which prefixes a fixed string to
an input string

What you seem to be doing is something like:

val prefix = "Sir ";
fun prefixer s = prefix ^ s;

then

prefixer "Galahad"; evaluates to "Sir Galahad" as expected.

Now you turn around and type

val prefix = "Father "; (thus introducing a new scope, namely the
scope of *this* binding of prefix)

and discover that prefixer "Brown"; evaluates to "Sir Brown" rather
than "Father Brown" as you apparently expected.

What you could do is the following. Create a higher order function
prefixer_maker via

val prefixer_maker prefix = fn s => prefix ^ s;

The you can create prefixers with different prefixes:

val knight = prefixer_maker "Sir ";
knight "Galahad"; (evaluates to "Sir Galahad")

val ordain = prefixer_maker "Father ";
ordain "Brown"; (evaluates to "Father Brown")

val negate = prefixer_maker "anti";
negate "matter"; (evaluates to "antimatter")

hth

-scattered

Loading...