452 karma · joined July 29, 2012
- Reporting failures can more easily report context about the function being tested
- Since "where" blocks are actual blocks, we define helper functions and variables inside them that don't need to clutter up the namespace of the current scope
- It's easy to turn the assertions on and off; for example when importing a Pyret module we skip running the checks of that module by default.
- The localized information also plays into our story for type inference (which is work in progress), but starts from the unit tests in the where: block to figure out what the programmer intended for input/output types.
Re: scope, you're right that using "def" the way I did isn't very idiomatic Ruby, but I always run afoul of it because it looks so similar to what I'd write in another language. I took that example down for the moment; I still have a personal gripe with it, but my "fundamentally broken" language was a bit strong. If I think of a more illustrative example, I'll put something back up.
The currying example I still think is weird in Ruby, and it's because it interacts bizarrely with the syntactic choice about optional argument lists. The thing that is wrong with JavaScript and Ruby is that they both have dot expressions and application expressions; o.m and f(x). It looks like
o.m(x)
should be a composition of dot lookup followed by an application, since both of those raw expressions make sense on their own. But in neither does the decomposition actually work:
m = o.m m(x)
There's no state or funny mutation going on here, but a simple kind of substitutability isn't working. JavaScript does it especially poorly, and Ruby has this awkward inability to decompose because of its choices about application syntax. Now, in Ruby I'm aware that with or without parens are actually both method calls, so it's not like there's a field access and an application in the underlying language model. But Ruby then adds the syntactic convenience of no arguments to make it look like access is possible, but that syntactic convenience is a bit of a leaky abstraction.
I should note that Python actually gets this nicely right IMO, and dot lookup curries self so this works out.
The underlying thing that irks me and I'm calling out here is the non-compositionality of what looks like two expressions that should compose. This is something we felt like figuring out and getting consistent for Pyret.
My gripe is that the simplest, cleanest construct in the language for defining named functional abstractions---"def"---doesn't support closing over variables, which other languages do, so I have to warp my thinking a bit to program in Ruby. The analogous program in the four languages I mentioned above (and Pyret, and more) works and creates a closure. Maybe I just don't think well in Ruby, because I end up feeling frustrated that Ruby seems to not like functions.
All that said, "in fundamental ways" is a little harsh. At least in this example, Ruby hasn't done anything to violate the integrity of static scoping the way, say, JavaScript's "with" does. I'll try to see if I can come up with a better example that's a clearer problem and less a personal dislike.
We've been designing the static system with exactly this in mind. The current implementation doesn't do anything fancy yet, but that's just because we've been getting off the ground and focusing on ergonomics and curriculum first. Getting performance in return for types is absolutely where we're going.
The note about local variables is particularly important; we actively don't want Python's model of variables and scope.
data BinTree: | leaf | node(value, left, right);
We use "end" or ";" in order to have unambiguous delimiters for the ends of syntactic forms and avoid needing to depend on whitespace (we added some discussion on pyret.org about our philosophy on indentation and why we don't want to depend on whitespace).Making that leading pipe optional is a good idea. I made an issue for it, we'll think about if it'll break or confuse anything and add it if it doesn't:
https://github.com/brownplt/pyret-lang/issues/106http://cs.brown.edu/~sk/Publications/Papers/Published/mfk-va...
One issue is that it's too regular: since open-paren means so many different things (start of a "defun", start of a function application, start of an argument list in a "defun", start of a syntactic form like "cond" or "if", start of a clause of "code", the list goes on), a typo can easily and drastically change the kind of error message you get.
More closely matching syntactic forms to the type of behavior the expression has will hopefully let us improve error messages and grokkability of the difference between concepts. We're collecting data about common syntax errors and actively asking what we can do better in syntax design based on what we observe about Pyret's use.
I have two responses:
1. You're right that exposing too much, too soon is a recipe for disaster. People can only fit so many new concepts in their head at once, and blasting them with refinement types from day one isn't a great idea. However, there's two things at work here: the role of the curriculum and the role of the language. Pyret is careful to allow a gradual transition to more and more complex features. Annotations are not necessary on day 1, and neither are iterators. We write untyped, recursive-descent functions on lists in the beginning, and build up to types and the pleasant terseness of "for" loops. If Pyret required introducing these concepts to write any programs, that would indeed be a problem, but we've put thought into the dependencies between curricular concepts and the needed language constructs to mitigate exactly this concern.
2. I think that some of the features you listed are actually a huge necessity in early programming, particularly special purpose syntax for data structures. Teaching the definition and implementation of a balanced binary tree to folks without language experience in Python or Java requires a huge amount of boilerplate explanation. You need to describe classes, fields, and constructors just to do the equivalent of Pyret's "data". The alternative (at least in Python) is to encode everything in lists and dictionaries, but hopefully in 2013 we've moved beyond writing all our data structures that way :-)
As to why we're advertising it as a teaching language: Our group has a design sense of how to do this, and Pyret's success in the classroom is what we're able to study, measure, and focus on improving. That doesn't mean we won't be thinking about managing million-line codebases or getting great performance out of the runtime, but those concerns won't necessarily be the main guide of our design decisions. We may very well end up with something that's a superb general-purpose language, but I'm not yet willing to give folks that expectation, or do so at the expense of the learning experience. Make sense?
EDIT to add: One thing that the Racket community has very successfully done is separate teaching languages from the main Racket language. A probable future for Pyret is splitting it into teaching and professional versions. I'd love to hear feedback about what more you'd like to see in Pyret, with this split in mind.
The getting started guide (http://www.pyret.org/getting-started/) and tour (http://www.pyret.org/tour/) is the best reference for getting started right now. If you didn't see those then they probably need more prominent placement.
I have a few thoughts right off the bat.
1.
It would be nice to put a Gatherer link in every response, just for browsing purposes.
2.
Split cards are weirdly reported, as if they are two separate cards. In Gatherer these are reported with an id and a "part" field:
http://gatherer.wizards.com/Pages/Card/Details.aspx?multiverseid=249394&part=Malice
It looks like you're only getting the first part. See that Spite//Malice is missing fromhttp://mtgapi.com/api/v1/fetch/search/malice
But I can search for "spite" and I find:
http://mtgapi.com/api/v1/fetch/id/249394
3.
It looks like you're using the Oracle text for every version (for example, http://mtgapi.com/api/v1/fetch/id/198 and http://mtgapi.com/api/v1/fetch/id/226588 have the same rules text, though they appeared differently when printed). Is there any way to get that information? That could be really cool for mining information about how terminology has changed over time.
http://cs.brown.edu/~sk/Publications/Papers/Published/emcahk...
Please Continue to Hold: An Empirical Study of User Tolerance of Security Delays
o.f()
Can be decomposed in Python: func = o.f
func()
JavaScript fails this test, and it's a shame.I can't seem to go back and edit, though...
The only thing you can't do with JS emulation of .bind() is the ability to create a varargs constructor (https://bugs.webkit.org/show_bug.cgi?id=26382#c29). But at the same time, I don't totally grok that example yet. The relevant bit of the spec is http://es5.github.com/#x15.3.4.5.2, which describes the [[Construct]] behavior of .bind().
On a pedantic note, due to complications with the new() operator, you can't actually implement .bind() in JS using only .call() and .apply() and get the prototype of the resulting objects right.
1. "To the left of the dot" should be "to the left of the dot or bracket". E.g. o["foo"]() also passes o as this.
2. When you pass callbacks to built-ins like forEach, you can supply a thisArg, and if you don't, this will be bound to undefined (http://es5.github.com/#x15.4.4.18). (EDIT): Copying from cwmma below. Some callbacks, like setInterval and setTimeout, pass the global object rather than undefined. I suspect that DOM callbacks tend to use the global object and JS callbacks tend to use undefined, but that's not a blanket statement by any means.
3. This isn't purely JS, but when interacting with the DOM, this is also implicitly bound as a DOM element in event listeners. E.g. document.addEventListener('click', function() { console.log(this); }) will print the document object when you click on the page. This is quite relevant for any JS web development; if it makes it easier to conceptualize, imagine that the browser is calling a method with the DOM element to the left of the dot.
4. When the caller is strict mode code and the function is called without method-call syntax, undefined is passed as the this argument rather than the global object. You should always use strict mode to avoid accidentally handing the global object around (in strict mode, the "set to global" option is skipped in http://es5.github.com/#x10.4.3).
5. When using this at the toplevel (which is allowed), it is bound to the global object even in strict mode (http://es5.github.com/#x10.4.1.1).
And a few more esoteric ones:
6. In addition to call and apply, Function.prototype.bind() can also change the this parameter of a function, and violate the "left of the dot or bracket" rule (http://es5.github.com/#x15.3.4.5, see boundThis).
7. Inside a with() {} block (which you should never use, but we're trying to cover our bases here), this is bound to the object passed to with in the parentheses.
8. If a property is a getter or a setter, this is bound to the object to the left of the dot or bracket in the field access or assignment expression. This actually matches the rule in the post except for the fact that the "call time" is implicit; there are no () in the expression o.x, but it may call a getter function for x that passes o as this (http://es5.github.com/#x8.12.3).
(EDIT): One more doozy:
9. The dot and bracket operators implicitly convert primitives like numbers, strings, and booleans to objects, so it's not exactly what's to the left of the dot:
Number.prototype.identity = function() { return this; }
var x = 5
var x2 = x.identity()
typeof x2 === 'object' (true!)
typeof x2 === 'number' (false!)
You can get the raw primitive if you make the function strict: Number.prototype.identity2 = function() { "use strict"; return this; }
var x = 5;
var maybe_five = x.identity2();
typeof maybe_five === 'object' (false)
typeof maybe_five === 'number' (true)
Please correct me if I've forgotten any... function fibs(size) {
return f();
function f() {
... implementation here ...
}
}One concern:
"Each pattern will be tried in order until a match is found."
As far as I know, ECMAScript doesn't specify any enumerations on objects that obey an order... browsers do syntactic order, but it's by no means a guarantee. (e.g. http://stackoverflow.com/a/280861)
Another implementation of the core semantics + desugaring approach, dubbed S5 (https://github.com/brownplt/lambdas5), is better engineered and actually uses SpiderMonkey's parser to handle more programs.
S5 runs underscore.js without any issues. Do give it a shot if you're still interested in trying things out.
Also, it's worth mentioning that S5 handles features of the new ECMAScript 5 specification, like strict mode, getters and setters, and new APIs, that the original λJS simply doesn't handle, so it's a much more complete account of JavaScript.