Functional programming
Steven D'Aprano
steve at pearwood.info
Tue Mar 4 03:56:33 EST 2014
On Tue, 04 Mar 2014 17:04:55 +1100, Chris Angelico wrote:
> On Tue, Mar 4, 2014 at 4:35 PM, Steven D'Aprano <steve at pearwood.info>
> wrote:
>> On Tue, 04 Mar 2014 05:37:27 +1100, Chris Angelico wrote:
>>> x = 23 # Compiler goes: Okay, x takes ints. x += 5 # Compiler: No
>>> prob, int += int --> int x = str(x) # Compiler: NO WAY! str(int) -->
>>> str, not allowed!
>>>
>>> It's fine and correct to infer that x is an int, x is an int, x is a
>>> str. It's *not* okay to make the third line a SyntaxError because you
>>> just put a str into an int variable.
>>
>> It won't be a Syntax Error, it will be a compile-time Type Error. And,
>> yes, it is fine. That's the point of static typing! The tradeoff of
>> being able to detect a whole lot of errors *at compile time* is that
>> you give up the ability to re-use the same variable for different types
>> in a single scope. (You can have an x which is a string elsewhere, just
>> not in this scope where it is an int.)
>
> Okay, a compile-type type error, same difference. What I'm saying is
> that the auto-detection can't know what else you plan to do.
Obviously it can't see the code you haven't written yet, but it can see
what you *do* do.
> If you
> explicitly say that this is an int, then yes, that should be disallowed;
It's that "explicitly" part that doesn't follow. Having to manage types
is the most tedious, boring, annoying, *unproductive* part of languages
like Java, C and Pascal. Almost always, you're telling the compiler stuff
that it can work out for itself.
In the same way that managing jumps for GOTO has been automated with for
loops, while, etc., and managing memory has been automated, there's no
good reason not to allow the compiler to manage types. Dynamically typed
languages like Python do so at runtime. Type inference simply allows
statically typed languages to do the same only at compile time.
[...]
>> That Haskell has homogeneous lists is not a property of the type
>> system, but a design choice. I'm sure Haskell will also have a tuple or
>> record type that allows fields of different types.
>
> If it's not the list type, pick some other. It's not uncommon to want to
> have a record that has different types (C does this with struct, C++ has
> a few more ways to do it); what I'm finding odd is that whatever goes
> into it first is specifying for everything else.
That's because in Haskell the design was made that lists *must* be used
for homogeneous data. If you read Python documentation from back in the
1.5 and early 2.x days, there was a *very* strong recommendation that
lists be used for homogeneous data only and tuples for heterogeneous
data. This recommendation goes all the way up to Guido.
# Yes
[1, 3, 4, 2, 5, 9]
(1, "hello", None, 3.5)
# No
[1, "hello", None, 3.5]
That is, lists are for collections of data of arbitrary length, tuples
are for records or structs with dedicated fields.
That convention is a bit weaker these days than it used to be. Tuples now
have list-like methods, and we have namedtuple for record/struct-like
objects with named fields. But still, it is normal to use lists with
homogeneous data, where there is an arbitrary number of "things" with
different values, but all the same kind of thing.
In the case of Haskell, that's more than a mere convention, it's a rule,
but that's not terribly different from (say) Pascal where you can have an
array of integer but not an array of integer-or-real.
The thing is though, how often do you really have a situation where you
have a bunch of arbitrary data, or unknown length, where you don't know
what type of data it is? Sure, in the interactive interpreter it is
useful to be able to write
[1, "spam", None, [], {}, 0.1, set()]
and I write unit tests with that sort of thing all the time:
for obj in list_of_arbitrary_objects:
self.assertRaises(TypeError, func, obj)
kind of thing. But that doesn't have to be a *list*. It just needs to
have convenient syntax.
>> I have not used Haskell enough to tell you whether you can specify
>> subtypes. I know that, at least for numeric (integer) types, venerable
>> old Pascal allows you to define subtypes based on integer ranges, so
>> I'd be surprised if you couldn't do the same thing in Haskell.
>>
>> The flexibility of the type system -- its ability to create subtypes
>> and union types -- is independent of whether it is explicitly declared
>> or uses type inference.
>
> I'm not sure how you could have type inference with subtypes. How does
> the compiler figure out what subtype of integers is acceptable, such
> that it can reject some?
You seem to be under the impression that type inference means "guess what
the programmer wants", or even "read the programmer's mind". Take this
example:
> x = 5
> x = 7
> x = 11
> x = 17
> x = 27
>
> Should the last one be rejected because it's not prime? How can it know
> that I actually wanted that to be int(3..20)?
It can't, of course, any more than I could, or anyone other than you. But
if you asked a hundred people what all those values of x had in common,
93 of them would say "they're all integers", 6 would say "they're all
positive integers", and 1 would say "they're all positive odd integers".
[Disclaimer: percentages plucked out of thin air.]
No type system can, or should, try to guess whatever bizarre subtype you
*might* want. ("Only numbers spelled with the letter V.") If you want
something stupid^W weird^W unusual, it's your responsibility to work
within the constraints of the programming language to get that, whether
you are using Python, Pascal or Haskell.
> That's why I see them as
> connected. All sorts of flexibilities are possible when the programmer
> explicitly tells the compiler what the rules are.
And you can still do that, *when you need to*. Assuming the type system
has a way of specifying "integers that include the letter V", you can
specify it when you want it.
> Static and dynamic typing both have their uses. But when I use static
> typing, I want to specify the types myself. I'm aware that's a matter of
> opinion, but I don't like the idea of the compiler rejecting code based
> on inferred types.
Well, so long as you admit it's an irrational preference :-)
The bottom line is, if the compiler rejects code, it's because it has a
bug. There's *no difference* between the compiler telling you that you
can't add a string and an int when you've explicitly declared the types,
and the compiler telling you that you can't add a string and an int when
it has determined for itself that they are the types because that's all
that they can be.
--
Steven
More information about the Python-list
mailing list