A Conversation with Language Creators: Guido, James, Anders and Larry [video]
youtube.com
youtube.com
Maybe I'll just add, with language design, you know one of the things that's interesting, you look at all of us old geezers sitting up here, and we're proof positive that languages move slowly.
A lot of people make the mistake of thinking that languages move at the same speed as hardware or all of the other technologies that we live with.
But languages are much more like math and much more like the human brain, and they all have evolved slowly. And we're still programming in languages that were invented 50 years ago. All the the principles of functional programming were though of more than 50 years ago.
I do think one of the things that is luckily happening is that, like as Larry says, everyone's borrowing from everyone, languages are becoming more multi-paradigm.
I think it's wrong to talk about "Oh, I only like object oriented programming languages, or I only like imperative programming, or functional programming".
It's important to look at where is the research, and where is the new thinking, and where are new paradigms that are interesting, and then try to incorporate them, but do so tastefully in a sense, and work them into whatever is there already.
And I think we're all learning a lot from functional programming languages these days. I certainly feel like I am. Because a lot of interesting research has happened there. But functional programming is imperfect. And no one writes pure functional programs. I mean, because they don't exist.
It's all about how can you tastefully sneak in mutation in ways that you can better reason about. As opposed to mutation and free threading for everyone. And that's like just a recipe for disaster.
Languages evolve slowly because they're not really technologies. Languages are notation.
The Hundred Year Language http://paulgraham.com/hundred.html
That is, "notation" evolves a lot more slowly than "technology". One reason I can think of is that notation is communication -- it requires 2 sides to agree. So just like we're using ancient network protocols and OS interfaces (e.g. the Unix syscall interface), we're also using ancient programming languages.
----
Trivia: Awhile back I watched a talk by Larry Wall about Perl 6, and he actually said that this 2003 essay was one of the inspirations for Perl 6! I was reminded of that during the thread about Redis a week or so ago.
This is, both mathematically and practically speaking, incorrect. People write pure programs all the time.
For example, `f x = putStrLn x` is a pure program. The reason for this is because, despite the name, nothing actually "happens" when this function is evaluated. I suggest you try it: just bind `f "hello world"` to a variable! Nothing happens. Because it's just an immutable value, computed by a pure function with no side effects.
There are situations where IO values get executed, but the crucial point is that computation and execution are logically separated. You can have a structure of IO-producing functions, get a list of IOs from them, filter and modify and reorder them, bind them, process them: they're just data. Pure.
It is clear OP meant a program as in an entire non-trivial system, not individual functions; even if they are programs themselves.
Regardless, when one talks about function purity, it is about the final evaluation, not about partial binding or lazyness.
You misunderstand it as well: it has nothing to do with evaluation mode. Idris is eagerly evaluated, for example, and _that function is still pure_! `f x = putStrLn x` gets "finally" evaluated and yes there is still no side effect. It is so evaluated that you can bind it to a variable and apply another function on it. Just like 2^100 is a pure expression, so is `putStrLn "hello"`.
> It is clear OP meant a program as in an entire non-trivial system, not individual functions; even if they are programs themselves.
It is not clear. Does he mean including the OS and hardware? Because no matter how complex, a pure program is still pure.
The real issue is that people don't understand that `putStrLn "hello"` _is literally just as pure as `2*x`_. They are both like mathematical expressions in that they evaluate to a value and _nothing else_. The name "putStrLn" _suggests_ that this is similar to a print statement but it is emphatically not. You can evaluate it a billion times and nothing gets printed.
The only difference is that the languages you are talking about have captured "doing I/O" as something that can be statically reasoned about, but not "consuming memory" (for example). From a mathematical perspective, one is part of the axioms while another is not.
One could imagine a language where all memory allocation must be done explicitly via a monad, just like how in Haskell all I/O is performed inside an IO monad. If that were the case, you could have additional static guarantees about how memory is used. That doesn't mean that languages which don't do this are somehow inherently "impure"; they are simply "pure with respect to a certain set of axioms" (which don't talk about memory usage).
"The system crashed" example is fun too. All pure functions must return a value, right? Well, that's obviously not gong to be the case if the computer blows up halfway through executing a pure function. "Blowing up" might not be something your type system talks about—Haskell does to some degree (with _|_ inhabiting every type), but Idris for example has total functions which at runtime could most definitely be interrupted by a sudden explosion. They are "total" only with respect to a set of axioms which imply an execution model where computers do not explode mid-program.
However, if you are thinking more in the direction of "you have to reason about where and how exactly your values and functions are stored", you're moving back to C, because the entire point is to disentangle the formal description of computation, from it's actual execution in the real world. If you want to deal with that, you have to do it explicitly and _by reasoning about it_ (with ST, for example). Which is what purity means.
> They are "total" only with respect to a set of axioms which imply an execution model where computers do not explode mid-program
"There is a chance that the computer blows up" is one of those things that have little bearing when reasoning about programs, it's just not in the domain of discourse. It certainly has no formal bearing on the notion of totality, mathematics just doesn't deal with cases such as "what if everyone who counted to 5 suddenly died", it deals with the counting itself. When the chance is actually high, for example radiation in space flipping bits, I'm pretty sure you want to handle that elsewhere.
It's not hard to imagine a fault-tolerance system gaining a lot from dependent types, however, with their ability to prove that your code follows a defined protocol.
(One of my favorite topics, that I've written about in other threads!)
https://news.ycombinator.com/item?id=15560845
Check out how the city seamlessly regrows after you nuke it:
https://www.youtube.com/watch?v=XkSXERxucPc
> Robust-first Computing: Distributed City Generation: A rough video demo of Trent R. Small's procedural city generation dynamics in the Movable Feast Machine simulator. See http://nm8.us/q for more information.
And here's a robust, self-healing membrane, that tolerates random failures (like tiny little exploding computers):
https://www.youtube.com/watch?v=oq0uvF4mm7Y
>To demonstrate the robustness of three membrane implementations in the MFM, an instance of each was placed side by side and subjected to a failure probability of 5e-5 per site per EPS. That is, each site is erased, on average, every 20,000 event cycles.
Intercellular Transport also demonstrates some robust chemical and biological programming metaphors:
https://www.youtube.com/watch?v=6YucCpYCWpY
>Two cells are interconnected by self-healing wire. The magenta substance is being passed from the cell in the west to the cell in the east. The black particles are an ectoplasm, allowing the membrane to identify sites outside of the cell. Similarly, the yellow and blue particles are an endoplasm specific to the cell in which they reside, allowing the membrane to identify sites inside of it.
Demon Horde Sort exemplifies the Robust First approach:
https://www.youtube.com/watch?v=helScS3coAE
Into functional programming? Here's a demo of programming the Movable Feast Machine with λ-Codons:
https://www.youtube.com/watch?v=DauJ51CTIq8
>λ-Codons provide a mechanism for describing arbitrary computations in the Movable Feast Machine (MFM). A collection of λ-Codon molecules describe the computation by a series of primitive functions. Evaluator particles carry along a stack of memory (which initially contains the input to the program) and visit the λ-Codons, which they interpret as functions and apply to their stacks. When the program completes, they transmute into output particles and carry the answer to the output terminals (left).
They are a simple mapping of a tuple (hardware state, queue of world events) onto itself! Truly marvelous!
Vive la pureté!
PS. I am now working on abstracting this further, and I just realized the universe itself is pure too; but somehow I got some strange behaviors when I started to look into particles too closely... will report back soon.
https://www.oracle.com/technetwork/java/100percentpurejavaco...
>[...] 100% Pure Java, JavaStar, JavaPureCheck, [...] JavaSpin, HotJava, The Network Is The Computer, and JavaStation are trademarks or registered trademarks of Sun Microsystems, Inc. in the U.S. and certain other countries.
Registering purity as your trademark and insisting that all of your users rewrite every bit of their existing legacy code in your pure language, and never use or talk to any other languages ever again, always seemed like separatist linguistic supremacy to me.
Apparently, C#'s P/Invoke is an evil impure cross-language conspiracy to dilute our linguistic purity and sap our precious bodily fluids, breeding caravans of mongrel coders who will swarm across our open borders and steal our jobs. That's why JNI and NDK are only used by unpatriotic second class citizens with divided loyalties, so they don't deserve to be well supported or maintained.
https://www.zdnet.com/article/100-pure-java-initiative-wins-...
>"Keep Java pure" is the message behind Sun Microsystems' 100% Pure Java initiative which has already won support from 100 developers but will lock out programs such as Visual J++. The move is an attempt on behalf of the Californian firm to maintain integrity of Java code and build awareness.
https://www.cnet.com/news/100-pure-java-watered-down/
>Viewers of cable news network MSNBC may recently have seen a commercial for Sun Microsystems (SUNW) in which a man tells a priest that he's been thinking "pure" thoughts. When the beatific man exits the confessional, he passes a queue of anxious programmers--one wearing a Visual Basic shirt--ready to admit their sins.
>The commercial, which touts Sun's "100 percent pure Java" campaign, is not your usual television ad. But Sun has in many ways shown an almost religious determination to spread the gospel of Java to the far reaches of the globe, even sponsoring a worldwide educational tour for programmers with the support of Netscape Communications, IBM, and Novell.
When you call a C function, you cannot know whether equational reasoning holds, whereas you can be sure when you're using a pure language. Therefore, when you're working on a project that is 100% in say Idris, you are in fact making a totally pure program. You could make an Idris compiler that inserts random perturbations in various functions, or you could look at crashes etc., but that is not where the concept of purity applies, it's a category error to think it does.
If you say that c is impure you have to admit that any program with the io monad also is. That is, any working Haskell program, even if Haskell allows you to write part of this program out of the io monad.
When I actually get output that says "hello", that isn't pure, is it? And there is some way for me to actually produce that output, isn't there? It may not be "evaluating putStrLn", but it's something.
And if that's all true, then rujuladanh is essentially correct: You're being pedantic in a way that misses the main point.
But at the same time, it's not that surprising, it's not something that comes to us humans naturally. The only way to learn it is to do it. This is something I highly recommend to everyone, you'll find the experience incredibly rewarding.
"It is by far the most problematic part of language design. And it's a single value that -- ha ha ha ha -- that if only that wasn't there, imagine all the problems we wouldn't have, right? If type systems were designed that way. And some type systems are, and some type systems are getting there, but boy, trying to retrofit that on top of a type system that has null in the first place is quite an undertaking." -Anders Hejlsberg
data Maybe a = Nothing | Just aOption<T> types which can have either Some(value) or Nothing for values. Statically compiled languages can typecheck this so you don’t have null pointer errors.
Some languages like TypeScript have this baked into the language syntax, so you can do: let x: Foo | null = bar; and is also properly type checked.
E.g. it’s perfectly fine to say you might have an empty value via ‘Foo | null’ or ‘Foo?’ or with something like null, ‘Maybe Foo’ but to sneak it into plain old ‘Foo’ is problematic (a billion dollars worth of problems).
The only reason why you may need null is to check whether a reference is null. You remove this need by forbidding it in well defined programs.
Option/Maybe monads are one option popular in static functional languages.
For set oriented languages, an empty set is an option. (This is very similar to the previous option, actually.)
Null is kind of a middle ground between these options and a ad hoc use of a specific sentinel value like using a signed integer variable when the data is logically unsigned, so you can use “-1” for missing data.
So I guess the better question is how do you express the idea of skipping checking for Sentinel when the language always checks for sentinel?
If your program typechecked against Netscape Navigator in 1997, does that prove that it will run without type errors in AwesomeNewBrowser 2023?
IMO null/undefined/etc are reasonable fallbacks if your runtime environment can radically diverge from its static model.
I like to transcribe and write up articles about videos like this, and this is a worthwhile candidate, if I can find the time. It would be a lot more accessible as annotated text with links and illustrations, than a long hard to hear video. (It’s also good to check with the people talking to make sure the transcription was right and captured their meaning.)
"I think IDEs make language developers lazy." -Larry Wall
"IDEs let me get a lot more done a lot faster. I mean I'm not -- I -- I -- I -- I -- I'm really not into proving my manhood. I'm into getting things done." -James Gosling
IDEs help people write programs, but to language developers, an IDE might be the easy and less elegant way out of a problem in the design of the language.
Programming language design is user interface design because programmers are programming language users.
"East Coast" MacLisp tended to solve problems at a linguistic level that you could hack with text editors like Emacs, while "West Cost" Interlisp-D tended to solve the same problems with tooling like WYSIWYG DWIM IDEs.
But if you start with a well designed linguistically sound language (Perl, PHP and C++ need not apply), then your IDE doesn't need to waste so much of its energy and complexity and coherence on papering over problems and making up for the deficiencies of the programming language design. (Like debugging mish-mashes of C++ templates and macros in header files!)
In Perl6 I can document those requirements in the subroutine signature:
sub foo( Str $name where *.lines == 1, Int $count where * > 0 ) {
say "Name is $name with count $count";
}
Or if I want to name the concepts and reuse them, I can say: subset LegalName of Str where *.lines == 1;
subset PositiveInteger of Int where * > 0;
sub bar( LegalName $name, PositiveInteger $count ) { foo( $name, $count) }
You can use these subsets for multiple dispatch as well: # Print a single line argument.
multi sub print-it( Str:D $it where *.lines == 1 ) {
say $it;
}
# Print a multiline string as a single line
multi sub print-it( Str:D $it ) {
say $it.lines.join('');
}
# Handle anything that we didn't expect
multi sub print-it( Any $it ) {
Failure.new( "it was unprintable", $it.gist );
}
IME, this makes it easy to write self documenting code.https://www.adacore.com/gems/gem-31
Gem #31: Preconditions/postconditions
>The notion of preconditions and postconditions is an old one. A precondition is a condition that must be true before a section of code is executed, and a postcondition is a condition that must be true after the section of code is executed.
http://www.ada-auth.org/standards/12rat/html/Rat12-2-3.html
>We will look first at the simple case when inheritance is not involved and then look at more general cases. Specific preconditions and postconditions are applied using the aspects Pre and Post respectively whereas class wide conditions are applied using the aspects Pre'Class and Post'Class.
>To apply a specific precondition Before and/or a specific postcondition After to a procedure P we write
procedure P(P1: in T1; P2: in out T2; P3: out T3)
with Pre => Before,
Post => After;
>where Before and After are expressions of a Boolean type (that is of type Boolean or a type derived from it).If your language supports preconditions, postconditions and invariants, then you can pursue the "Design by Contract" approach to programming, coined by Bertrand Meyer for Eiffel.
That's when you guarantee that if all the preconditions are met before calling a function, then all the postconditions will be met after it returns (but the object may go through intermediate states where the postconditions aren't met before it returns, like while inserting an item into a doubly linked list). Unfortunately that idea breaks down when you have multiple threads that might enter a function at the same time, so you have to use object level locks, which have their own problems.
https://en.wikipedia.org/wiki/Design_by_contract
https://en.wikipedia.org/wiki/Eiffel_(programming_language)
>The design of the language is closely connected with the Eiffel programming method. Both are based on a set of principles, including design by contract, command–query separation, the uniform-access principle, the single-choice principle, the open–closed principle, and option–operand separation.
>Many concepts initially introduced by Eiffel later found their way into Java, C#, and other languages. New language design ideas, particularly through the Ecma/ISO standardization process, continue to be incorporated into the Eiffel language.
Larry also considers Laziness one of the three primary virtues of programmers, so maybe he meant the above as a compliment.
A powerful IDE that automatically looks up documentation and guides you through writing a bunch of boilerplate lets a language designer skimp on figuring out ways to let you avoid writing the boilerplate in the first place.
Code generation in the IDE is a poor substitute for a well designed interface.
The Perl6 project was to reimagine the design by breaking things that needed breaking.
If your premise that Larry is bad at language design, then you should be able to find something in Perl6 that is “terribly designed”. I won't be holding my breath, so feel free to take your time.
It's simple, it was added to counter-balance the smug ascertainment of Guido's ignorance, and the taken-for-granted assumption that we'd be so much better if he modeled Python more after ML/Lisp/etc.
>do you honestly think python, as a programming language, is superior to something like racket or f#?
No, I think the subject is moot. Python has been adopted and is widely successful for being Python.
We had MLs and Schemes/Lisps available before and after Python and they didn't manage to get traction. Why do you think having the original Python be more like these would help its case?
Even if the result was a "better language" in the sense you think Racket or F# are, those are still niche, and there's all the reasons to believe Python would be too if it followed their model.
So, it's not "if only Python was ML/Lisp like we would now have an equally successful but better Python!".
It's more likely "if Python was ML/Lisp it would still be niche today, and we wouldn't have a language with the characteristics that people love in Python and adopted it for, but yet another ML or Lisp version".
(a, (b, *c, d), *e) = [1, (2, 3, 4, 5), 6, 7]
Good for nested iterables, but not supporting dict/object unpacking.http://www.dai.ed.ac.uk/groups/ssp/bookpages/quickprolog/nod...
[Amused to see that the page I linked to there is over 20 years old!]
When I first read that, I thought he meant run-time lookups of overloaded method names, which is a form of pattern matching on parameter lists.
https://www.artima.com/weblogs/viewpost.jsp?thread=101605
Five-minute Multimethods in Python, by Guido van van Rossum.
I used to believe that multimethods were so advanced I would never need them. Well, maybe I still believe that, but here's a quick and dirty implementation of multimethods so you can see for yourself. Some assembly required; advanced functionality left as an exercise for the reader.
https://python-3-patterns-idioms-test.readthedocs.io/en/late...
https://pythonhosted.org/multidispatch/
https://adambard.com/blog/implementing-multimethods-in-pytho...
https://opendylan.org/documentation/intro-dylan/multiple-dis...
https://opendylan.org/documentation/intro-dylan/objects.html...
say &infix:«+».candidates.elems; # 27
Here is a quick translation of that second link to Perl6 #! /usr/bin/env perl6
use v6.d;
enum Roshambo < Rock Paper Scissors >;
enum Outcome < Lose Win Draw >;
proto compete ( Roshambo, Roshambo --> Outcome ){*}
multi compete ( Paper, Rock --> Win ){}
multi compete ( Rock, Scissors --> Win ){}
multi compete ( Scissors, Paper --> Win ){}
multi compete ( $a, $b where $b eqv $a --> Draw ){}
multi compete ( $, $ --> Lose ){}
for Roshambo.roll(20) Z Roshambo.roll(20) -> ($a,$b) {
say "$a.fmt('%8s'), $b.fmt('%8s'): &compete($a,$b)"
}
(Note that I didn't really write any actual code for the `compete` subroutines; it was all handled by the signatures.)Pattern matching is very deeply ingrained into Elixir. Two examples to scratch the surface
This is pattern matching in function arguments
def process_file({:ok, {file_name, file_content}}) do
...
:ok
end
def process_file({:error, reason}) do
...
:error
end
def process_file(_), do: error
This is pattern matching in assignment {:ok, content} = File.read(file_name)
Actually the = character is the pattern matching operator, which accidentally ends up assigning values in some cases: it can't assign to the :ok atom but it can assign to the content variable. If File.read returns {:error, "some error"}, the match will fail and the program will stop. Usually I get a notice from Sentry and a supervisor process will start it again, possibly running into the same problem or not. Sometimes errors are caused by external services that have occasional hiccups.Some changes are so structural that turn languages into another one and I'm not sure it's worth it. We have so many languages to choose from.
I daresay another issue is going to be the syntax -- the naïve way of implementing it nests two blocks: one for the pattern-matched block as a whole, and and another for the actual cases, which means that now you've got a double indent. IIRC this was a reason he gave against implementing case statements way back when.
There is one construct that's almost like a block but doesn't indent code, decorators. It's rich enough to implement multiple dispatch in functions, including pattern-matching on values and not just on types. Unfortunately it won't get you the sort of things you could do with actual blocks, e.g. access to other variables.
That made me feel a lot better about having to do the same thing all the time too! Nothing ever works right (or runs fast) the first time, and you have to just keep fiddling with it until it does.