Python from Wise Guy's Viewpoint

B

Brian McNamara!

Marshall Spight said:
Interesting, interesting. Thanks for taking me seriously!

I'm trying to map this program into Java, and it's possible ....
Anyone have any comments?

Well, in C++ you could say

template <class F, class A>
typename result_of<F(A)>::type
noisy_apply( const F& f, const A& a ) {
cout << "I am now about to apply " << f << " to " << a << endl;
return f(a);
}

These assume that both "f" and "a" work with the out-streaming operator
(<<). This is just an ad-hoc version of what would be "class Show" in
Haskell. In C++ practice, most functions aren't "showable", but many
common data types are. So the most useful version of the function would
probably be

// This version works for all "f" and all Showable "a"
template <class F, class A>
typename result_of<F(A)>::type
noisy_apply( const F& f, const A& a ) {
cout << "I am now about to apply a function with type "
<< typeid(f).name() << " to the value " << a << endl;
return f(a);
}

Again, provided that we have some notion of "class Show" in our
statically-typed language, then examples like these are easy to type.
(What dynamically-typed languages typically buy you is that every object
in the system provides some basic methods like toString(), which
eliminates the Show-able constraint that the statically-typed version
needs.)
 
M

Marshall Spight

Joachim Durchholz said:
Huh?
Alone the need to downcast whenever I take something out of a container
would suffice to term it as a "serious problem".

Why? Here, you've assumed your conclusion. You've not given
me any reason. I will not accept any a priori criticisms of
downcasting except this one: downcasting requires the programmer
to type more characters.

Downcasts are not a source of problems in Java because
empirically, no problems result from them. (It's hard to
prove the absense of something, eh?) Years pass; hundreds
of thousands of lines of code are written, with no errors
arising from downcasting the result of ArrayList.get().
That has been my experience.

Does the existence of downcasts point out a place where
Java is lame? Absolutely. Does extra effort result from
this lameness? Certainly. Does this extra effort cause
bugs? Nope.

(Anyway, the situation is much better with Java generics,
available now in prerelease form; mainsteam in the next
major version.)


Marshall
 
J

Joachim Durchholz

Would this count?

(defun noisy-apply (f arglist)
(format t "I am now about to apply ~s to ~s" f arglist)
(apply f arglist))

It wouldn't typecheck in Haskell because you don't restrict the elements
of the arglist to be of the "Show" type class, which is the group of
types that have a printable representation.

Other than that, the declaration of this function would be (in a
Pascal-inspired notation)

noisy_apply (f: function (x: a): b, x: a): b

Note that a and b are type parameters here: if noisy_apply is given a
function with input type a and output type b, it will /demand/ that its
second parameter is of type a, and its result type will be b.

I.e. in C++, you'd write something like

template <a, b> {
b noisy_apply ( (b) (f (a)), a x );
}

(syntax is most likely dead wrong, it's been a while since I was
actively working with C++).

For completeness, here's the Haskell type:

(a -> b) -> a -> b

(I hope I got this one right.)
The first "a -> b" translates as "a function that takes any type a and
returns any type b".
The x -> y -> z notations translates as "a function taking input values
of types x and y and returning a value of z type".
If the same type letter occurs more than once, if must be the same type
in calls.
(Yes, the function is polymorphic, though in a different way than OO
polymorphism: most OO languages don't allow expressing the restriction
that the two "a" parameters must be of the same type but the caller is
free to choose any type.)
To sum it all up: the above specifications are all intended to say the
same, namely "noisy_apply is a function that takes an arbitrary
function, and another parameter that must be of the same type as the
input parameter for the supplied function, and that will return a value
of the same type as the supplied function will return".
Modern static type systems can express such types :)


You might ask "where's the argument list"?
The answer is that I'm assuming a "currying" language. All functions in
such a language have a single argument; functions with multiple
arguments are written as a function that takes an argument and returns
another function which expects the next argument.

I.e.
add (3, 4)
is first evaluated as
[***] (4)
where [***] is an internally-created function that adds 3 to its single
argument.
(That's just the theory how the operation is defined. Currying languages
will usually be executed in the obvious manner, unless the code takes
specific advantage of currying.)


HTH
Jo
 
J

Joachim Durchholz

Marshall said:
It's starting to feel like this is merely a demonstration
of Java's weakness in generic programming, and
not something hooked into Goedel.

Anyone have any comments?

You're dead right: Java has insufficient support for typing this.

C++ would allow it, but the result isn't pretty either... which says a
lot about C++'s qualities for higher-order programming.

Regards,
Jo
 
M

Marshall Spight

My point is that type systems can reject valid programs.

Agreed. But: does it matter? One thing that would help
in figuring out if it matters or not would be seeing a
small, useful program that cannot be proven typesafe.

If these programs are "all around us," and writing equivalent
typesafe programs is somewhat harder, then it matters.
If these programs are hard to come across, and writing
equivalent typesafe programs is easy, then this doesn't
matter.


Marshall
 
J

Jon S. Anthony

Marshall Spight said:
Interesting, interesting. Thanks for taking me seriously!

I'm trying to map this program into Java, and it's possible

Isn't Java a particularly bad thing to try here?
but there are enough different ways to go about it that
I'm having a hard time reasoning about the result.

For one thing, what would f be? Probably an instance of

As written, f can be any object of any type. arglist is any set of
arguments with arity 0 to <hardware limit>. It will do something
useful in all cases and something more useful if f is a function

/Jon
 
D

Dirk Thierbach

Only if you assume binary logic.

Yes, of course.
If there are three values that can arise --- provable-mismatch,
provable-non-mismatch, and undecided --- then you cannot assume that
~provable-mismatch = provable-non-mismatch.

Hindley-Milner type inference always terminates. The result is either
a provable mismatch, or a provable-non-mismatch.
My point is that type systems can reject valid programs.

That depends on what you understand by "valid". A provable mismatch
means that there is an execution branch that will crash if you ever
get there. If for some reason this branch will never get executed,
either because it's (non-provably) dead code, or because you have
an implicit restriction for possible arguments to this expression
the type system doesn't know about, than you could call it a "valid
program", but it will still be rejected, yes.

I am still not sure I get your point. (Maybe we always agreed; I just
don't know).

- Dirk
 
P

prunesquallor

Marshall Spight said:
Agreed. But: does it matter? One thing that would help
in figuring out if it matters or not would be seeing a
small, useful program that cannot be proven typesafe.

(defun lookup (item table if-found if-missing)
(cond ((null table) (funcall if-missing))
((eq item (entry-key (first-entry table)))
(funcall if-found (entry-value (first-entry table))))
(t (lookup item (remaining-entries table)
if-found
if-missing))))

(defun lookup-default (item local-table default-table if-found if-not-found)
(lookup item local-table
if-found
(lambda ()
(lookup item default-table if-found if-not-found))))

(defun transform-list (list local-table default-table if-ok if-fail)
(if (null list)
(funcall if-ok '())
(lookup-default (car list) local-table default-table
(lambda (result)
(transform-list (cdr list) local-table default-table
(lambda (remainder)
(funcall if-ok (cons result remainder)))
if-fail))
(lambda () (funcall if-fail (car list))))))

I know that simple static type checkers will be lost with this.
I do not know if the smarter ones will.
 
J

Jon S. Anthony

Andreas Rossberg said:
It is not, because Lisp hasn't been designed with types in mind. It is
pretty much folklore that retrofitting a type system onto an arbitrary
language will not work properly. For example, Lisp makes no distinction
between tuples and lists, which is crucial for type inference.

Your credibility just took a nose dive.

/Jon
 
P

prunesquallor

Well, in C++ you could say

template <class F, class A>
typename result_of<F(A)>::type
noisy_apply( const F& f, const A& a ) {
cout << "I am now about to apply " << f << " to " << a << endl;
return f(a);
}

I don't mean to nitpick, but APPLY takes an arbitrary list of arguments.
How do you parameterize over that without enumerating the power set
of potential types?

What if F `returns' void?
 
P

prunesquallor

Joachim Durchholz said:
To sum it all up: the above specifications are all intended to say the
same, namely "noisy_apply is a function that takes an arbitrary
function, and another parameter that must be of the same type as the
input parameter for the supplied function, and that will return a
value of the same type as the supplied function will return".
Modern static type systems can express such types :)

Are they happy with something like this?

(defun black-hole (x)
#'black-hole)

(for non lispers, the funny #' is a namespace operator.
The black-hole function gobbles an argument and returns
the black-hole function.)
 
B

Brian McNamara!

(e-mail address removed) once said:
I don't mean to nitpick, but APPLY takes an arbitrary list of arguments.
How do you parameterize over that without enumerating the power set
of potential types?

This isn't really possible for normal C++ functions.

You can always program in a style where every function takes exactly
one argument, which is an N-ary tuple, and use boost::mpl and
boost::tuple to then generalize things. (Indeed, using such libraries,
you can simulate "apply" rather convincingly. But somewhere under the
hood, someone has to have written N different overloads for 0-arg,
1-arg, ... N-arg, up to some fixed ("large enough") N.)

So C++ can only mimic "noisy_apply" so well. I expect that Haskell can
mimic it better in this respect.
What if F `returns' void?

It still works. (You are allowed to say "return f(a)" inside a template
function returning void, provided f(a) "returns" void as well.)
 
B

Brian McNamara!

(e-mail address removed) once said:
Are they happy with something like this?

(defun black-hole (x)
#'black-hole)

(for non lispers, the funny #' is a namespace operator.
The black-hole function gobbles an argument and returns
the black-hole function.)

Finally, an example that I don't think you can type in Haskell.
You score a point for that. :)

If we have a static type system which admits infinite types, then we
can assign black-hole a type. So it's still typeable, just not in any
common language I can name offhand. :)
 
R

Remi Vanicat

(e-mail address removed) once said:

Finally, an example that I don't think you can type in Haskell.
You score a point for that. :)

If we have a static type system which admits infinite types, then we
can assign black-hole a type. So it's still typeable, just not in any
common language I can name offhand. :)

$ ocaml -rectypes
Objective Caml version 3.07+2

# let rec f x = f;;
val f : 'b -> 'a as 'a = <fun>

By the way, I don't see how this function can be useful...
 
S

Stephen J. Bevan

(defun lookup (item table if-found if-missing)
(cond ((null table) (funcall if-missing))
((eq item (entry-key (first-entry table)))
(funcall if-found (entry-value (first-entry table))))
(t (lookup item (remaining-entries table)
if-found
if-missing))))

(defun lookup-default (item local-table default-table if-found if-not-found)
(lookup item local-table
if-found
(lambda ()
(lookup item default-table if-found if-not-found))))

(defun transform-list (list local-table default-table if-ok if-fail)
(if (null list)
(funcall if-ok '())
(lookup-default (car list) local-table default-table
(lambda (result)
(transform-list (cdr list) local-table default-table
(lambda (remainder)
(funcall if-ok (cons result remainder)))
if-fail))
(lambda () (funcall if-fail (car list))))))

I know that simple static type checkers will be lost with this.
I do not know if the smarter ones will.

There are a few undefined functions in the above (entry-key,
first-entry, remaining-entries) which means it would not get past most
static type-checkers. Assuming that typing programs that have
undefined functions wasn't part of the test, then using a simple
association list to represent the table it all type-checks under SML :-

$ cat prune.sml
fun lookup (item, table, ifFound, ifMissing) =
case table of
[] => ifMissing ()
| (k,v)::r =>
if item = k
then ifFound v
else lookup (item, r, ifFound, ifMissing)

fun lookupDefault (item, localTable, defaultTable, ifFound, ifNotFound) =
lookup (item, localTable, ifFound,
fn _ => lookup (item, defaultTable, ifFound, ifNotFound))

fun transformList (list, localTable, defaultTable, ifOk, ifFail) =
case list of
[] => ifOk []
| (h::t) => lookupDefault (h, localTable, defaultTable,
fn result =>
transformList (t, localTable, defaultTable,
fn remainder =>
ifOk (result::remainder),
ifFail),
fn _ => ifFail t)
$ ~/opt/smlnj-110.41/bin/sml
Standard ML of New Jersey v110.41 [FLINT v1.5], July 05, 2002
- use "prune.sml";
[opening prune.sml]
prune.sml:5.15 Warning: calling polyEqual
val lookup = fn : ''a * (''a * 'b) list * ('b -> 'c) * (unit -> 'c) -> 'c
val lookupDefault = fn
: ''a * (''a * 'b) list * (''a * 'b) list * ('b -> 'c) * (unit -> 'c) -> 'c
val transformList = fn
: ''a list * (''a * 'b) list * (''a * 'b) list * ('b list -> 'c)
* (''a list -> 'c)
-> 'c
val it = () : unit
-
 
M

Marshall Spight

Are they happy with something like this?

(defun black-hole (x)
#'black-hole)

(for non lispers, the funny #' is a namespace operator.
The black-hole function gobbles an argument and returns
the black-hole function.)

Ha!

Although this doesn't get me any closer to my goal of
simple, useful, correct program that cannot be proven
typesafe. I don't believe the feature this function
illustrates could be useful; you have to have a handle
on black-hole before you can invoke it, so getting
it back as a return value doesn't get me anything.
But it's a nice example.


Marshall
 
P

prunesquallor

There are a few undefined functions in the above (entry-key,
first-entry, remaining-entries) which means it would not get past most
static type-checkers. Assuming that typing programs that have
undefined functions wasn't part of the test, then using a simple
association list to represent the table it all type-checks under SML :-

I don't expect type checkers to figure out missing functions.
$ cat prune.sml

[code snipped]
val lookup = fn : ''a * (''a * 'b) list * ('b -> 'c) * (unit -> 'c) -> 'c
val lookupDefault = fn
: ''a * (''a * 'b) list * (''a * 'b) list * ('b -> 'c) * (unit -> 'c) -> 'c
val transformList = fn
: ''a list * (''a * 'b) list * (''a * 'b) list * ('b list -> 'c)
* (''a list -> 'c)
-> 'c
val it = () : unit
-

Cool!
 
L

Lulu of the Lotus-Eaters

|2- Development of static type systems (and type inferencers/checkers)
| which are strong enough to offer cast iron *guarantees* but at the
| same time are flexible enough to allow useful programs involves
| some tricky theory that few really understand (I won't pretend I do).
| But some language developers don't want to get to bogged down with
| all that difficult and boring theory stuff for however many months

There's more to it than that. For example, not many Python programmers
understand C3 method resolution order, and why it was adopted in 2.3
over the earlier algorithms. And probably only about three Python
programmers understand some of the regex optimizations added to the SRE
engine. And only ONE person in the universe fully understands the magic
hacks that the Timbot introduced into the latest sorting algorithms :).

But understanding those theoretical issues makes hardly any difference
to regular programmers. Python does a good job of hiding what you don't
need to know from you (but letting you get at it if you really need to).

With a pure, lazy functional language like Haskell, there is much less
you can JUST DO without understanding a lot of theory first. And even
then, you need to program in functional styles, and use these weird IO
monads when you want to interface with the outside world. For your
average Joe (or Jane), having at least the option to freely play with
mutable values, and imperative flow, makes programming a WHOLE LOT
easier to reason about. It may well be that for rockets, nuclear
reactors, and pace makers, type safety is more important than easy
conceptualization--but for a lot of things, a low conceptual burden is
far more important than theoretical, provable correctness.

The bottom line IMO is that languages which can implement a strong and
static type system carry with them a lot of concomitant baggage. You
pretty much need to be purely functional and side-effect free to do it
right. Maybe OCaml walks the line between the sides, but still without
being as easy to use as Python, Ruby, TCL, even Perl--or maybe than
Lisp, with a nod to its enthusiasts.

Yours, Lulu...
 
B

Brian McNamara!

(e-mail address removed) once said:
(defun lookup (item table if-found if-missing)
(cond ((null table) (funcall if-missing))
((eq item (entry-key (first-entry table)))
(funcall if-found (entry-value (first-entry table))))
(t (lookup item (remaining-entries table)
if-found
if-missing))))

(defun lookup-default (item local-table default-table if-found if-not-found)
(lookup item local-table
if-found
(lambda ()
(lookup item default-table if-found if-not-found))))

(defun transform-list (list local-table default-table if-ok if-fail)
(if (null list)
(funcall if-ok '())
(lookup-default (car list) local-table default-table
(lambda (result)
(transform-list (cdr list) local-table default-table
(lambda (remainder)
(funcall if-ok (cons result remainder)))
if-fail))
(lambda () (funcall if-fail (car list))))))

I know that simple static type checkers will be lost with this.
I do not know if the smarter ones will.

If I have read that correctly, it looks like it admits these Haskell
types:

lookup ::
(Eq k)=> k -> Map k v -> (v -> a) -> a -> a
lookupDefault ::
(Eq k)=> k -> Map k v -> Map k v -> (v -> a) -> a -> a
transformList ::
(Eq k)=> k -> Map k v -> Map k v -> ([a] -> b) -> (k -> a) ->

except that in transformList, types "a" and "" must be the same, due
to the awkward[*] way the exception-handling works with if-fail.

[*] Awkward from the static-typing point-of-view, of course. I think a
Haskell programmer would rewrite transformList with this type instead:

(Eq k)=> k -> Map k v -> Map k v -> ([a] -> b) -> (k -> c)
-> Either c b

using the "Either" type to handle the exceptional case. The
implementation would then be just

transformList l locTable defTable ifOk ifFail =
if (null l)
then Right (ifOk l)
else lookupDefault (head l) locTable defTable
(\r -> transformList (tail l) locTable defTable
(\remain -> ifOk (cons r remain)) ifFail)
(Left (ifFail (head l)))

Thus, if one of the lookup fails, we'll get back a "Left" Either,
describing where the problematic input was, whereas if they all succeed,
we'll get back a "Right" Either, with the expected kind of result.

Actually, rewriting all of these functions using the Error monad would
make it all more elegant.

(Of course, the usual "I'm doing this all by hand" disclaimer applies.
Any type errors are my own.)
 
J

Jon S. Anthony

boost::tuple to then generalize things. (Indeed, using such libraries,
you can simulate "apply" rather convincingly. But somewhere under the
hood, someone has to have written N different overloads for 0-arg,
1-arg, ... N-arg, up to some fixed ("large enough") N.)

That's not actually good enough. You also have to have overloads for
all the possible types for 1-arg, ..., N-arg. Actually it's worse
than that - the set of types is not closed, so even in principle this
won't work.

/Jon
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,172
Messages
2,570,934
Members
47,474
Latest member
AntoniaDea

Latest Threads

Top