And yes, the file extension is ".arr"
November 9, 2013 7:49 PM   Subscribe

Pyret is a new programming language for teaching from Brown University that blends Python, Racket, and OCaml.

Interesting parts:
  • ADTs and pattern matching
  • "Graph" declarations for cyclic data structures
  • Functions have lexical blocks for documentation and tests
  • "Refinements": type annotations may insist that the values they describe satisfy an arbitrary predicate
posted by a snickering nuthatch (132 comments total) 38 users marked this as a favorite
 
"Peanut butter, tuna, and tomatoes all make good sandwiches! I bet a PBT&T would be amazing!"
posted by sonic meat machine at 7:57 PM on November 9, 2013 [16 favorites]


I like the inline unit tests a lot
posted by sweet mister at 8:00 PM on November 9, 2013 [5 favorites]


Numbers
Pyret has numbers, because we believe an 8GB machine should not limit students to using just 32 bits.


Um. Huh.
posted by device55 at 8:18 PM on November 9, 2013 [2 favorites]


Correction: the inline unit tests are the greatest thing I have ever seen

They are like glory in my mouth and hands

glory
posted by curuinor at 8:19 PM on November 9, 2013 [9 favorites]


Easy graph types, hooray!
posted by sixswitch at 8:28 PM on November 9, 2013


device55: numbers are not floating point or integer, but actual mathematical numerics. Most languages don't have numbers, or at least not as a convenient default syntax.

Notice the example: ((1 / 3) * 3) == 1

This is true in very few programming languages. Precisely true in even fewer.
posted by idiopath at 8:28 PM on November 9, 2013 [15 favorites]


Yeah, those where: blocks are a really great idea.
posted by RogerB at 8:29 PM on November 9, 2013


ooooooooo! New code to learn!
posted by tilde at 8:32 PM on November 9, 2013


I'm want to try to add unit tests like that to visual studio with a plugin. Put them in a comment block in or next to the method, have the plugin extract them and emit the test code.
posted by Ad hominem at 8:36 PM on November 9, 2013 [1 favorite]


idiopath: Most lisps have this feature, including Racket. See docs.
posted by curuinor at 8:37 PM on November 9, 2013 [1 favorite]


How much of a slowdown do you get with basic arithmetic when you're working with exact numbers?
posted by dilaudid at 8:39 PM on November 9, 2013


The numbers aren't limited to the size of a 32 bit register. Typically you need a large number library to work with numbers larger than maxint 2,147,483,647.
posted by Ad hominem at 8:41 PM on November 9, 2013


I'm reacting, I guess, to the lack of utility in an educationally targeted programming language using a mathematical construct that's largely unused in programming languages.

When the Pyret student tries that example expression in Java,will they understand why it isn't the same?

Having a true high level numerical object sounds really cool, but seems to counter educational goals.

The inline testing makes testing easy and enforces how important it is (so maybe you'll tolerate the overhead of testing in less friendly languages). But I don't know that abstracted numerics accomplish that.
posted by device55 at 8:42 PM on November 9, 2013 [1 favorite]


curuinor: yes, I am a professional lisp programmer, so I know this

I'm just mentioning that this is not a mainstream feature, which is why it is worth noting.
posted by idiopath at 8:46 PM on November 9, 2013


No arrays? Why do languages avoid arrays nowdays? One of the first languages I learned was APL, which is almost 100% arrays and matrices. I think computer scientists don't like engineers and want us to do arrays indirectly using some weird computer science memory or list thingy. Heck, you are giving us numbers, why can't you give us arrays? (I'd ask for APL matrix functions like multiplying, but then I'd be getting greedy. I can hear them now: "write it yourself". I swear you'd take multiply and divide away from us and make us write those too if you didn't find them useful for computer science).

One of the first programming languages, Fortran, had arrays, and because of that it is still in use today by scientists. So you Pyret people, think of it this way: if you add arrays and matrices you'll be guaranteeing that the language will have legacy that will last long beyond the typical computer science language of the month.
posted by eye of newt at 8:46 PM on November 9, 2013 [13 favorites]


I don't know, it seems much more reasonable that a pedagogical language have numbers that behave more like a non-programmer would expect. Overflow and non-associativity are pretty unintuitive, and if you can isolate them from the other things you need to learn in the course of becoming a programmer then so much the better.
posted by invitapriore at 8:48 PM on November 9, 2013 [3 favorites]


Arrays are only a meaningful concept in Von Neumann machines. Exposing them as a primitive is tying yourself unnecessarily to a particular model of computation.
posted by invitapriore at 8:50 PM on November 9, 2013


Arrays are only a meaningful concept in Von Neumann machines.

I'm sorry, but I'm unable to respond in a healthy and respectful manner to this unmitigated bilge.
posted by flabdablet at 8:52 PM on November 9, 2013 [17 favorites]


Arrays are only a meaningful concept in Von Neumann machines. Exposing them as a primitive is tying yourself unnecessarily to a particular model of computation.

What does that even mean? Strike that, I don't want to know. It sounds like a computer scientist excuse. Arrays and matrices are very useful to a lot of fields, just like strings are very useful. But because they are only 'meaningful concepts in Von Neumann machines' (or, 'not meaningful to computer scientists', more likely), they are left out.

Stop being selfish. Give us arrays...and matrices.
posted by eye of newt at 8:54 PM on November 9, 2013 [5 favorites]


An array is not an ADT is it? So they aren't cool anymore. They have lists though.
posted by Ad hominem at 8:56 PM on November 9, 2013


Notice the example: ((1 / 3) * 3) == 1

Sorry to noob it up, but how does one even implement infinite-precision arithmetic? Keep a history of every operation and cancel out parts of it whenever possible?
posted by Nomyte at 9:00 PM on November 9, 2013


C++, Java, PHP, Javascript, Perl, Python, etc all have arrays and strings either as built in types or basic library types.

I suspect the reason anybody still uses Fortran is the same reason banks still use COBOL.
posted by kmz at 9:01 PM on November 9, 2013 [1 favorite]


The pyret developers are discussing the addition of vectors, so no need for arrays, right?
posted by kandinski at 9:02 PM on November 9, 2013


I think that if and when these students learn Java, they can learn something about the more traditional C-style representation of numbers; maybe in a systems course they can learn about how math is actually implemented in hardware. Knowing about floating point arithmetic and binary representations of data is no longer important for basic programming tasks. Honestly, most intro courses that use Java or Python don't go into the details, except to explain that there are integers and decimals, and neither can be too big, and the decimals act funny sometimes.

Probably a developer or CS major should understand how numbers work in computers (and I'm sure they'll get it later in the Brown curriculum), but programming courses are no longer just for them, and IMO starting off with counterintuitive material is a distraction from teaching students how to reason about and write correct, maintainable code.
posted by vogon_poet at 9:02 PM on November 9, 2013 [2 favorites]


Oh and of course there's plenty of modern languages designed for computation too: R being probably the most well known.
posted by kmz at 9:03 PM on November 9, 2013 [1 favorite]


Nomyte: typically a rational datatype, so 1/2 is actually stored as {numerator : 1, denominator: 2}, or the equivalent, and math ops are extended to handle this datatype (and conversion to int happens as appropriate whenever assigning or returning values).
posted by idiopath at 9:06 PM on November 9, 2013 [1 favorite]


I suspect the reason anybody still uses Fortran is the same reason banks still use COBOL.

I have little love for Fortran, but I should note that it is actually faster than C for numerics. Ever used LAPACK or one of its wrappers? Fortran.
posted by a snickering nuthatch at 9:07 PM on November 9, 2013 [2 favorites]


Not sure why that made everyone so mad. I never said arrays aren't useful, and in fact my day job is built on a foundation of Fortran in the form of LAPACK. Not every language has to be optimized for a particular type of engineering application, though.
posted by invitapriore at 9:07 PM on November 9, 2013


Nomyte: typically a rational datatype, so 1/2 is actually stored as {numerator : 1, denominator: 2}, or the equivalent, and math ops are extended to handle this datatype (and conversion to int happens as appropriate whenever assigning or returning values).

That sounds equivalent to keeping a history, since I imagine a rational data type should also handle objects like

{
    numerator: {
        numerator: 1,
        denominator: 2,
    },
    denominator: 2
}


I'm starting to suspect that a history might make it easier to keep track of invertible operations.
posted by Nomyte at 9:11 PM on November 9, 2013


I was going to look at the source to see how they implement "numbers" but I think this is implemented in Racket. It is some kinda inception type thing, "I heard you like experimental educational programming languages so I implemented one in an experimental educational programming language". It is experimental educational programming languages all the way down.
posted by Ad hominem at 9:14 PM on November 9, 2013 [2 favorites]


Nomyte: keeping a history has big storage costs

{numerator: 1, denominator: 2} / 2 becomes {numerator: 1, denominator: 4}

Because we know how math works we know that this does not lose information as long as we do not overflow. When overflow happens we can catch it (there is a hardware primitive for this that most languages don't use), and promote to BigNum. Rationals keep either Integers or BigNums inside, no need to put anything else in one ever.
posted by idiopath at 9:14 PM on November 9, 2013


Nomyte: Why would it need to do that? That would be immediately calculated to be 1/4.

A history wouldn't help with, for example, (1/3)*6 == 2.
posted by kmz at 9:14 PM on November 9, 2013


Not every language has to be optimized for a particular type of engineering application, though.

I guess that was my point, though, or maybe my pet peeve. Outside of computer science, arrays (and by arrays I mean at least two dimensional arrays, which leaves Perl out) are of pretty basic importance--not just engineering, but just about every field of science, graphics, business, even telecommunications--heck you can do some pretty interesting computer sciencey things with arrays.

I found an interesting discussion of matrices in Pyret here.
posted by eye of newt at 9:15 PM on November 9, 2013 [1 favorite]


Those who like the inline unit tests might also like Python's doctest module.
posted by stebulus at 9:23 PM on November 9, 2013 [5 favorites]


Why would it need to do that?

So it can invert them immediately? It's not hard to descend into a tree to find out that {n: {n: {n: 3, d: 2}, d: {n: 1, d: 3}}, d: 3} is just {n: 3, d: 2}, but it somehow makes more sense to me as a string like 1 × 3 ÷ 2 ÷ 1 × 3 ÷ 3, where you can cancel out instances of "× 3" and "÷ 3."

I think both Matlab and Mathematica have arbitrary-precision arithmetic. I should take a look at how they implement it.
posted by Nomyte at 9:30 PM on November 9, 2013


Yup, python's doctesting is great. I develop a bit for Sage, which is kind of like all the best open source math software glued together with python, with ever-more Sage-native python code thrown into the mix. The source uses inline doctests for writing both documentation and example code, with extra specialized tests thrown in for edge cases. Sage is great, and better than the competition for a number of things; I think at this point mathematica is winning pretty much only in the pretty graphics category....

And, yeah, matrices are easy in Sage.
posted by kaibutsu at 9:39 PM on November 9, 2013


eye of newt:
In Python:
L=[[1,2,3,],[4,5,6],[7,8,9]]
gives me a perfectly serviceable two-dimensional 'array', and there are plenty of easily accessible libraries to let you do matrix multiplication or whatever you like.
posted by kaibutsu at 9:43 PM on November 9, 2013


I like the inline unit tests a lot

While I like them for a teaching language, I think for production code it would be a step backward. I would want at least a separate file. We currently use a separate project for unit tests (using c#).
posted by Bort at 9:47 PM on November 9, 2013 [2 favorites]


Nomyte: the inversions are easy to do without a history, you just need a simplification step. Since no information is lost there is no need to build up the whole history of the value to do a perfect inversion as appropriate.

Since one would expect 1/5 + 1/2 + 1/10 to be 4/5 the simplification step has to happen anyway, so if you already need that why keep a symbol history too?
posted by idiopath at 9:49 PM on November 9, 2013


device55: "When the Pyret student tries that example expression in Java,will they understand why it isn't the same?"

Wouldn't it be nice if you didn't have to learn hardware limitations and floating point as a prereq to doing science, statistics, or any of the other things non-programmers do?
posted by pwnguin at 9:51 PM on November 9, 2013 [2 favorites]


I find the default number type dubious. If these are fractions, they'll decay to floats with the power operator; after that the entire program starts using floats and breaking naive expectations. If they are ASTs, performance will get pretty bad and some tests get undecidable.
posted by Tobu at 9:54 PM on November 9, 2013


Since one would expect 1/5 + 1/2 + 1/10 to be 4/5 the simplification step has to happen anyway, so if you already need that why keep a symbol history too?

Maybe it was stupid of me not to clarify, but I was imagining that simplification would happen after each operation. I'm pretty sure we're thinking of the same functionality, I just was thinking of a list instead of a tree.
posted by Nomyte at 10:06 PM on November 9, 2013


here you can cancel out instances of "× 3" and "÷ 3."

What about "x 6" and "÷ 3"?

I just was thinking of a list instead of a tree.

There's no tree needed. After every operation there's just a numerator and denominator left.
posted by kmz at 10:12 PM on November 9, 2013 [2 favorites]


kmz: "here you can cancel out instances of "× 3" and "÷ 3."

What about "x 6" and "÷ 3"?

I just was thinking of a list instead of a tree.

There's no tree needed. After every operation there's just a numerator and denominator left.
"

So what are the results of sqrt(2) represented as?
posted by pwnguin at 10:14 PM on November 9, 2013


that's not a rational number
posted by sweet mister at 10:15 PM on November 9, 2013


So what are the results of sqrt(2) represented as?

The computer algebra systems I've seen just leave irrational numbers alone, since any decimal representation has necessarily finite precision.
posted by Nomyte at 10:18 PM on November 9, 2013


In case you hadn't noticed, Pyret is implemented in Racket, so it's almost certainly using Racket's existing numerical representation.
posted by Rhomboid at 10:19 PM on November 9, 2013 [2 favorites]


Nomyte: a computer algebra system, now that you bring that term up, doesn't keep a history of the operations, but it does keep the whole equation in a symbolic form as long as possilble before solving, and will simplify before doing any operations on it.

This is distinct from using a numeric tower that includes a Rational type in that with a Rational type I can do algorithmic manipulations of a value, including recursive or mutually recursive computations that are not expressed algebraically and not presented to the compiler or interpreter in algebraic form, but still get proper numeric evaluation of the results.
posted by idiopath at 10:31 PM on November 9, 2013 [1 favorite]


Thanks, I'll have to find out more.
posted by Nomyte at 10:41 PM on November 9, 2013


Yeah I dug through the source for Racket and found their Bignum.c.implementation. It looks to be simply an array of digits.

I didn't look super close but this could be a problem when the index itself becomes larger than maxint.

if you look in the parent dir, you should find numbers.c and their arithmetic library which implements their exact numbers, inexact numbers, and rational numbers and associated math operations based on their bignum implementation.
posted by Ad hominem at 10:48 PM on November 9, 2013


Hopefully someone who actually knows C can take a look and tell me I'm totally wrong.
posted by Ad hominem at 10:50 PM on November 9, 2013


flabdablet: "Arrays are only a meaningful concept in Von Neumann machines.

I'm sorry, but I'm unable to respond in a healthy and respectful manner to this unmitigated bilge.
"

Just so we're on the same page before you commit to being an asshole about this, "array" refers to a sequence of values that are located consecutively in memory, and not merely just a sequential type that can be indexed numerically, right? Because the latter can make sense in the context of a language that implements the lambda calculus (which Pyret isn't, but the point being that such a construct is model-agnostic) but the former really can't.
posted by invitapriore at 10:51 PM on November 9, 2013 [3 favorites]


Yeesh, why would you want inline unit tests? What's the benefit?

I suppose they're a tiny bit quicker to write, because you don't need a separate file, but that's about it.

Successful code is written once and read at least ten times. You don't want to force people to read your unit tests to read your code - even to force people to skip over that when reading your code.

In those toy examples, the tests are shorter than the code. In the Real World, unit tests are often much longer than the accompanying code.

An awful lot of unit tests test the interaction between more than one class. Which class do they live on? You might have two classes, A and B, neither of which logically depends on the other, and wish to test that they interoperate. You don't want to introduce dependencies just to satisfy the unit tests.

More, unit tests themselves often include things that aren't needed for the main code. Again, you don't want to introduce dependencies that aren't needed for the payload code.

So much of reliable engineering involves reducing the interdependencies in your codebase. Unit tests inline increases the count of dependencies for no apparent benefit.

Python's doctests... well, they're fine for small things, but they're difficult to debug (because putting print statements in breaks the tests). In my current project, I started with doctest, but at a certain point it not only got too unwieldy, but I started to get strange random breakages, where doctest wouldn't find symbols... I switched to Python's actual unittest framework and all these issues went away and I was able to write more, better tests.

> Why do languages avoid arrays nowdays?

Say what?! Even PHP has arrays... bad, terrible arrays, but still arrays. I can't name one language without arrays.
posted by lupus_yonderboy at 10:52 PM on November 9, 2013 [3 favorites]


> "array" refers to a sequence of values that are located consecutively in memory, and not merely just a sequential type that can be indexed numerically, right?

That's a pretty idiosyncratic definition - this one is more standard. Lots of languages, like Java, have arrays that do not necessarily correspond to consecutive locations in memory. Sparse arrays, a common special case, are almost certainly not stored as consecutive memory locations.
posted by lupus_yonderboy at 10:58 PM on November 9, 2013 [2 favorites]


This is a programming language from a Rhode Island Ivy League school... and you assume it's somehow not involved with the echaeteon?

Iä! Iä!
posted by Slap*Happy at 11:04 PM on November 9, 2013 [4 favorites]


Fair enough. Basically every language I use in my day-to-day with the exception of C eschews the term, since they all implement their indexed collections as lists or trees, so I think I'd call that usage "diasyncratic" rather than "idiosyncratic."
posted by invitapriore at 11:06 PM on November 9, 2013


Of course it's worth noting that the first concrete definition in your linked Wikipedia page reads "Array data structure, an arrangement of items at equally spaced addresses in computer memory."
posted by invitapriore at 11:07 PM on November 9, 2013


Anyway, I agree that inline unit tests are an excessively strong coupling, but inline contracts make sense. The "where" clauses don't seem to function that way, though.
posted by invitapriore at 11:10 PM on November 9, 2013


Ad hominem: not an array of digits, but an array of machine sized words, combined in the same way that you would combine four bytes to make a 32 bit int. If 18,446,744,073,709,551,616 64 bit words is not enough bits to keep your number, you have bigger problems than the numeric tower I think.
posted by idiopath at 11:14 PM on November 9, 2013 [1 favorite]


RogerB: the 'where' concept is important in IDL (a language optimized for image processing, commonly used in astronomy and for satellite and medical imaging applications).

I find that it takes students a while to wrap their heads around, but makes IDL extremely easy to play with databases in. I've heard there are packages in python that can have similar functionality, but haven't made the transition myself -- after 15+ years it's far more efficient for me to work in IDL. I'd be curious to know if it shows up in any other, more modern languages.
posted by janewman at 12:27 AM on November 10, 2013


(And then I read the Pyret page in more detail and saw that 'where' is used for the unit tests, not to select array elements meeting some criterion. Nevermind...)
posted by janewman at 12:33 AM on November 10, 2013


I'd be curious to know if it shows up in any other, more modern languages

I just looked up IDL's "where" and it looks to be equivalent to R's "which", so R has it. But then, R has everything. Except respect.
posted by Jimbob at 1:03 AM on November 10, 2013 [3 favorites]


(And then I just read janewman's next comment.)
posted by Jimbob at 1:05 AM on November 10, 2013 [1 favorite]


I'd be curious to know if it shows up in any other, more modern languages.

There's a where operator in NumPy, which, although it's a python library, I found really very welcoming, coming from an APL/fortran background.
posted by doop at 1:08 AM on November 10, 2013


And surely LINQ...
posted by Rhomboid at 1:55 AM on November 10, 2013


I'd be curious to know if it shows up in any other, more modern languages.

Isn't this more or less like the map notation in Python, Scala, etc. (but with a different name)?
posted by Blazecock Pileon at 2:27 AM on November 10, 2013


Isn't this more or less like the map notation in Python, Scala, etc. (but with a different name)?

Basically, yes.

The where concept of IDL and others (which is different from the where in Pyret) goes back to the early days of functional languages like lisp, in which it would be called filter. Filter is a higher order function along with things like map and fold (reduce). Higher order functions originate out of Church's work with lambda calculus (functions of functions).

I love using the LINQ flavor of map/fold/filter.

I recommend learning about things like lisp if you program. There is a saying that all languages converge to lisp, and I think there's a lot of truth in it.
posted by Bort at 3:25 AM on November 10, 2013 [2 favorites]


Great. Ruining more potential working coders with Lisp.
posted by clvrmnky at 5:46 AM on November 10, 2013 [1 favorite]


Great. Ruining more potential working coders with Lisp.

Can/would you expand on that? I'm interested in why you think that.

I work in a MS c# shop in the Philly PA suburbs on internal business apps. We find it impossible to get good working coders and it isn't because of their knowledge of Lisp. It's because of their lack of knowledge of even the basics of programming concepts. I'd be surprised to learn that more than half of the interviewees have even heard of Lisp.

Then again, maybe my area of programming doesn't draw many people attracted to understanding the underlying concepts. Seems like a lot of the devs just want to copy/paste code for a paycheck. Maybe my company's rates are bad or we have a bad rep with the local talent, but we need to go through something like 20 resumes (already filtered by contracting companies) to find 5 interviewees to find maybe 1 potential fit.
posted by Bort at 6:26 AM on November 10, 2013 [1 favorite]


Can/would you expand on that? I'm interested in why you think that.

I can't speak for why clvrmnky in particular thinks that, but it's an attitude I've run into in the industry, not just with lisp but with all functional programming concepts. I think it's related to the "industry standard" mantra and the concept of the "Blub" Language. Take mapping and filtering, for example; if you're coming from Ruby, these functional concepts are baked into the language and are easy to express. To create a list of a hundred random numbers and find all numbers in the list that are evenly divided by three, for example:
    (1..100).map {rand 100}.select {|x| x % 3 == 0}
Scala looks very similar:
    (1 to 100).map {_ => Random.nextInt(100)}.filter {_ % 3 == 0}
Python would likely use "list comprehensions":
    [x for x in (random.randint(0, 100) for _ in range(100)) if x % 3 == 0]
In Clojure, a dialect of lisp, you could express it equally easily (although, to my mind, less readably):
    (filter (fn [x] (zero? (mod x 3))) 
            (take 100 (repeatedly #(rand-int 100))))
In Java, this would be the equivalent:
    public List<Integer> getRandomIntsDivisibleByThree() {
        List<Integer> unfilteredRandInts = new ArrayList<>();
        List<Integer> filteredRandInts = new ArrayList<>();

        for (int i = 0; i < 100; i++) {
            unfilteredRandInts.add((int)(Math.random() * 100));
        }
        
        for (Integer n : unfilteredRandInts) {
            if (n % 3 == 0) {
                filteredRandInts.add(n);
            }
        }
        
        return filteredRandInts;
    }
The thing is, many "workaday" programmers learned their trade by essentially memorizing (and copy and paste, of course). The Java syntax works for them because they have studied it for X years and have worked in jobs that used it for Y years. To many of them, the Ruby syntax (which shares many similarities with Java!) is baffling enough; ask them to think in lisp and they're not going to have a good time, even though I'd argue that all of the other solutions are more readable than the imperative Java solution, once you understand them.

This is why one of the key hiring tenets in many good programming shops is that a person be able to learn languages quickly, that they must show an interest in programming outside working hours, and why so many job descriptions for large companies (or recruiters) are laughable: "I have 8 years of experience in C++, 5 in Python, 3 in Java, and I've run a side consultancy implementing small websites in PHP for the last 11 years." "You've only been programming Java for 3 years, you're not experienced enough for this senior position." Someone who's been in this type of environment for their entire career is likely to find the functional concepts and idioms challenging, at best, and, more commonly, threatening.
posted by sonic meat machine at 7:56 AM on November 10, 2013 [8 favorites]


Yes, I was also confused initially by the "where:" clause.

Actually, I was hoping that the language allowed pre-conditions and post-conditions - assertions about what conditions need to be true before a segment of code, and conditions that will be satisfied once the code is done. To my knowledge, only the language Eiffel has this, and it never caught on (not quite sure why, it looked good to me).

There's a really excellent book called "The Science of Programming" that opened my eyes up to this concept many years ago. Once the Internet happened, I searched it up and found this truly inspirational review of it.
posted by lupus_yonderboy at 8:03 AM on November 10, 2013 [3 favorites]


Actually, I was hoping that the language allowed pre-conditions and post-conditions - assertions about what conditions need to be true before a segment of code, and conditions that will be satisfied once the code is done. To my knowledge, only the language Eiffel has this, and it never caught on (not quite sure why, it looked good to me).

Post-conditions are very uncommon, because they're usually handled by unit tests and not dealt with inside the normal flow of code. Preconditions, however, are available even in Java (via Guava, of course). Scala has them built in:
scala> def tripleOnlyEvenNumbers(n: Int): Int = { require(n % 2 == 0); n * 3}
tripleOnlyEvenNumbers: (n: Int)Int

scala> tripleOnlyEvenNumbers(2)
res0: Int = 6

scala> tripleOnlyEvenNumbers(3)
java.lang.IllegalArgumentException: requirement failed
posted by sonic meat machine at 8:23 AM on November 10, 2013


I liked the inline unit tests, too. The main benefit is that the unit tests might actually get written. And updated. And run.

At a more abstract level, most production level languages are designed to solve algorithms. That's a pretty well solved problem. What most languages are not designed to do is create systems, with lots of moving parts. Having a language that makes a strong structural suggestion of "This thing goes here" is pretty refreshing.
posted by underflow at 8:26 AM on November 10, 2013 [2 favorites]


Clojure has both pre- and post-conditions, which offers some really cool opportunities for decoupling a contract from an implementation.
posted by invitapriore at 8:34 AM on November 10, 2013


An awful lot of unit tests test the interaction between more than one class. Which class do they live on?

where: clauses are on functions, not classes. If you have a test that is not appropriate to put in the function itself, use a check: clause, which can go anywhere.

Also, note that where: in pyret is completely different from IEnumerable.Where; it's got nothing to do with LINQ.
posted by a snickering nuthatch at 8:40 AM on November 10, 2013


This seems cool! There's some discussion about it on Hacker News. I've been trying to find out more information about the type system, but haven't really dug anything up except for various comments by the authors in the above link; anybody know if a more detailed specification exists anywhere? Since its from an academic PLT group, I'm expecting some document somewhere full of type theory proofs.
posted by destrius at 8:40 AM on November 10, 2013


Arrays are only a meaningful concept in Von Neumann machines. Exposing them as a primitive is tying yourself unnecessarily to a particular model of computation.

Sequences with O(1) indexing are only meaningful in a particular model of computation? That makes absolutely no sense.
posted by kenko at 10:03 AM on November 10, 2013 [1 favorite]


I can't name one language without arrays.

SQL doesn't have arrays. Some implementations may have them as an extension or proprietary feature, but ANSI SQL doesn't.
posted by jedicus at 11:56 AM on November 10, 2013


I think it's related to the "industry standard"

All languages have an "idiom" or some would say a social more

you could solve the same problem in c# using:
 Random rnd = new Random();
 var filteredNumbers = Enumerable.Range(1, 100).Select(x => x = rnd.Next(0, 100)).Where(x=> x % 3 == 0);
Except nobody I work with would write it that way so it causes friction in development, my coworkers will have to stop and figure that out. This isn't a bad thing, and it doesn't mean my coworkers are incurious. The code is unclear because iterating over a list in that way is a sneaky for loop.

Last week I went on an interview for a senior c# position and the interviewer asked me to whiteboard a pretty standard problem, I started to do it using LINQ and he didn't believe it could be done so I had to do it using a for loop. He then had me optimize it in various ways. I got home later and wrote it using PLINQ and got a pretty good speed boost based on the fact it was now using more than one core.
posted by Ad hominem at 12:15 PM on November 10, 2013 [2 favorites]


> Post-conditions are very uncommon, because they're usually handled by unit tests and not dealt with inside the normal flow of code.

Well, that doesn't really hack it for "design by contract" programming - the idea is that given preconditions and postconditions you can automatically prove things about your programs.

This is very different from unit tests - pre- and postconditions are part of the specification of the program. You want clients of your code to know about those - on the other hand, you really don't want clients of your code (engineers who are using your code, but not changing it - hopefully the majority!) to have to read your unit tests, which are usually long-winded due to heavy repetition.

But you're quite right that this is so rare that I was only able to find one language that did it - Eiffel, which isn't really active (and what a shame, it was a great language).

I suspect that, as usual, people are much more interested in getting new features done than making extremely reliable code.

> I liked the inline unit tests, too. The main benefit is that the unit tests might actually get written.

:-(

As I said, in production code, inline unit tests are going to make your code harder to read, and add unnecessary dependencies, which means fragility. So you're trading away maintainability and reliability just to convince your engineers to do something they should be doing anyway. BAD trade-off!

If your team doesn't have the discipline to write unit tests without spoonfeeding, you have bigger issues to deal with.

> where: clauses are on functions, not classes. If you have a test that is not appropriate to put in the function itself, use a check: clause, which can go anywhere.

Well, they CAN go anywhere, but if you're testing the interaction between two functions neither of which is dependent on the other, where exactly DO they go?

Someone above mentioned having a completely separate project for unit tests which is of course the way to go (at least in languages like Java and C++ that have the "project" concept so strongly - it really isn't necessary in scripting languages).

> SQL doesn't have arrays.

SQL isn't really a programming language but a language to access data (usually from databases). Now, it turns out that SQL is Turing complete - but that's basically an accident. You'd be a glutton for punishment to do even "toy" programs with it (i.e. - try to compute the first n primes in SQL!)
posted by lupus_yonderboy at 12:15 PM on November 10, 2013


Huh, digging into curuinor's / Rhomboid's link I see that Racket has not only exact rationals, which makes eminent sense if you might be dividing integers, but exact elements of Q[i], which is funnier. If you're going to have support for nontrivial number fields, why stop at just one of them? Why not just have an exact algebraic number datatype? (Like Maple's RootOf, say.)
posted by finka at 12:19 PM on November 10, 2013


Anyway, what I really want to see is a language that has exact representations for all computable reals, via coroutines, for example as laid out in Gosper's paper on continued fraction algorithms. Though this wouldn't be so great if you need to make a lot of use of ==.
posted by finka at 12:38 PM on November 10, 2013 [1 favorite]


There's a really excellent book called "The Science of Programming"

That brought back memories. Took a course in my first year that used that as the text. Unfortunately they'd neglected to teach the logic behind it beforehand, which led to an entire year only squeaking the exam because they removed most of the proofs from it.

Changing tack completely, I've started learning Racket because one of our programmers used it in our production pipeline. Naughty Dog use it as their data definition language for their PS3 engine.
posted by inpHilltr8r at 12:47 PM on November 10, 2013


To create a list of a hundred random numbers and find all numbers in the list that are evenly divided by three, for example:

Here's my two-liner in R.
x=round(runif(100,0,100))
x[x %% 3==0]
I'm a bit annoyed at that, I thought I'd be able to do it in one line, and without having to assign the vector to x. Any kind of subsetting operation requires the input vector twice - once to search through and determine the items that match the criteria, and once to select and output the matched items. So I can't see a way to get around storing the vector.
posted by Jimbob at 2:11 PM on November 10, 2013


Well, they CAN go anywhere, but if you're testing the interaction between two functions neither of which is dependent on the other, where exactly DO they go?

How about wherever you would put them in any other language? You have complete flexibility here. You can collect all your tests and put them in separate files as is the xUnit convention, if you prefer.
posted by a snickering nuthatch at 2:17 PM on November 10, 2013


One-liner in R:
x <- Filter(function(y) (y %% 3 == 0), round(runif(100,0,100)))
posted by en forme de poire at 5:05 PM on November 10, 2013 [1 favorite]


Filter and [s/l]apply are one of my favorite things about R. So useful. And now with R 3.0 you can use mclapply instead of lapply and it will automatically fork it into separate processes so you can actually use all of your cores. Side effect free, bishes.
posted by en forme de poire at 5:10 PM on November 10, 2013 [3 favorites]


C# has had AsParalell for a while.

I just spent like 20 puzzling out what interfaces I had to implement to do
RandomGenerator rng = new RandomGenerator(0, 100);
var filteredList = rng.AsParallel().Take(100).Where(x => x % 3 == 0);
and use a custom IList GetEnumerator() to yield the next random number

Before I realized I am wasting my life.
posted by Ad hominem at 5:19 PM on November 10, 2013 [1 favorite]


I was just looking at that too! :)

I figure you need to hang an IEnumberable method on a subtype of Random or an extention method that looks something like this:

IEnumberable GetNextRandom(int min, int max)
{
yield return new Random().Next(min, max); //Put new Random in a static variable somewhere to improve performance
}

Then you could write something like:

var results = Random.GetNextRandom(0,100).AsParallel().Take(100).Where(x => x % 3 == 0);

or as I sometime format it:

var results = Random
.AsParallel()
.GetNextRandom(0, 100)
.Take(100)
.Where(x => x % 3 == 0);

(all but the first should be indented)

And at that point we haven't even generated any random numbers yet, as the iteration doesn't occur until I enumerate the results variable.

posted by Bort at 5:42 PM on November 10, 2013


In Scala, a parallel version of the above would be:
(1 to 100).map {_ => Random.nextInt(100)}.par.filter {_ % 3 == 0}
The code is unclear because iterating over a list in that way is a sneaky for loop.

Fundamentally, though, it's not. The C# version is a bit ugly because it's Java++, but saying that iterating over a collection using map and filter is "a sneaky for loop" is like saying that using a for loop in the first place is a sneaky set of goto statements. After all, everyone who started with BASIC has written those hideous combinations of IF and GOTO, right?
10 PRINT "HELLO! I WILL COUNT TO 10!"
20 LET A = 1
30 PRINT A
40 LET A = A + 1
50 IF A <= 10 THEN GOTO 30
60 PRINT "GOODBYE!"
For loops were an abstraction over this type of logic, and map/filter are abstractions over the concept of "iterating over a collection." In fact, "foreach" is a functional concept: it requires collections to encompass the idea of iteration rather than having the caller explicitly index the collection one after another.

[Postconditions are] very different from unit tests - pre- and postconditions are part of the specification of the program.

I agree with the idea that postconditions are part of the specification; they are, in fact, critical for programming. If you don't know what the postconditions are, you will not have a very high success rate in writing functions at all. The conditions just aren't necessarily formal, because the function is itself an expression of how to arrive at the postcondition. For example, let's say that I have a function to split a delimited line and create a map of it:
def splitRow(r: String): List[String] = r.split("\\|")
If I am working with a file that I know must have five columns, I could conceivably add a precondition:
def splitRow(r: String): List[String] = {
  require(r.count {_=='|'} == 4)
  r.split("\\|").toList
}
But most of the conceivable postconditions are already captured by the type (it will be a list of strings) and the precondition (there will be four pipes, so there will be five columns). If I added postconditions, it'd have to be asserting that, in fact, the thing I am returning is a list of strings, and that there are four strings in that list. It's a bit redundant; and the tests would be checking postconditions on each run, anyway. There might be tests similar to this:
class SplitterSpec extends FlatSpec with Matchers {
  "splitRow" should "throw an error if it gets too few columns in a string" in {
    a [IllegalArgumentException] should be thrownBy { splitRow("||") }
  }

  "splitRow" should "return a five-member list of strings" in {
    splitRow("a|b|c|d|e").length should equal (5)
  }
}
These tests, if well-written, can encompass all the checks for a postcondition but without requiring that it run every time a function is called in production.
posted by sonic meat machine at 6:31 PM on November 10, 2013 [1 favorite]



  class Program
    {
        static void Main(string[] args)
        {
       
             var filteredList = new RandomGenerator(0, 100)
                                               .AsParallel()
                                               .Take(100)
                                               .Where(x => x % 3 == 0);
           var enumeratedList = filteredList.ToList();
        }
    }


    class RandomGenerator : IEnumerable<int>
    {
        int _max = 100;
        int _min = 0;
        Random rnd = new Random();

        public RandomGenerator(int min, int max)
        {
            _min = min;
            _max = max;

        }

        public IEnumerator<int> GetEnumerator()
        {
            while (true)
            {
                yield return rnd.Next(_min, _max);
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }

var enumeratedList = filteredList.ToList() forces linq's lazy evaluation.
posted by Ad hominem at 6:39 PM on November 10, 2013


The C# version is a bit ugly because it's Java++,

Nah, java doesn't do any of this.
 var filteredNumbers = Enumerable.Range(1, 100).Select(x => x = rnd.Next(0, 100)).Where(x=> x % 3 == 0);
a sneaky for loop

it is because in the c# one liner i wrote, the Range(0,100) is only used to make it do something 100 times. Generate 100 numbers and toss them out.

I'm only talking about c# here.
posted by Ad hominem at 6:46 PM on November 10, 2013


for loop" is like saying that using a for loop in the first place is a sneaky set of goto statements.

But of course a for loop or goto corresponds pretty closely to a label and a conditional jump. But they make it clear a block will be called X times.

I just personally feel using c# Range in that way is being too tricky. You have to know that Range is enumerable and the Select will be called X times.

Again, I'm only talking about c#
posted by Ad hominem at 6:56 PM on November 10, 2013


I was going to ask why you implement IEnumerable instead of just using an iterator, but I guess it pretty much amounts to the same thing:
    class RandomGenerator
    {
        int _max = 100;
        int _min = 0;
        Random rnd = new Random();

        public RandomGenerator(int min, int max)
        {
            _min = min;
            _max = max;

        }

        public IEnumerable GetNumbers()
        {
            while (true)
            {
                yield return rnd.Next(_min, _max);
            }
        }
    }

I think I'd end up going with putting an extension method on Random:

    public static class RandomExtensions
    {
        public static IEnumerable GenerateSequence(this Random random, int min, int max)
        {
            while(true) 
                 yield return this.Next(min, max);
        }
    }   


Then the calling code would be:

var random = new Random();
var results = random.GenerateSequence(0, 100).Take(100).XYZ

or just:

var results = new Random().GenerateSequence(0, 100).Take(100).XYZ
posted by Bort at 7:13 PM on November 10, 2013


Maybe so, in the context of C#. I would note that I used range() that way in all of the other languages, though, and I don't feel it would be surprising. There is some syntactic sugar in Ruby to make it a bit prettier:
    100.times.map {rand 100}.select {|x| x % 3 == 0}
Something similar exists in scalaz. Either way, I actually find the range feels natural; but this is what I meant earlier by saying that the "industry standard" idea affects peoples' attitudes towards these structures.
posted by sonic meat machine at 7:17 PM on November 10, 2013 [1 favorite]


but this is what I meant earlier by saying that the "industry standard" idea affects peoples' attitudes towards these structures..

I think we were agreeing, I was just defending developers that use for loops.

think I'd end up going with putting an extension method on Random:

that is probably nicer but u wanted to eliminate something like GenerateSequence but of course it was at the cost if having to add a new helper class.

someone come up with another problem to solve!
posted by Ad hominem at 7:44 PM on November 10, 2013


that is probably nicer but u

I wanted to, not u. I'm giving up for the rest if the day.
posted by Ad hominem at 7:54 PM on November 10, 2013


True, what I'd might really do is kick it up a level of abstraction and create a proper generator class. There's the next problem. Write a Generator class that can be used like this:

var random = new Random();

var results = Generator.From(() => random.Next(0, 100)).Take(100).XYZ
posted by Bort at 7:56 PM on November 10, 2013


That actually was pretty easy:

    public static class Generator
    {
        public static IEnumerable<T> From<T>(Func<T> function)
        {
            while (true)
                yield return function();
        }

    }

posted by Bort at 8:03 PM on November 10, 2013 [1 favorite]


Ha, I was on the train on the phone, got home and sat at my laptop to type pretty much that and you already had it.
posted by Ad hominem at 8:09 PM on November 10, 2013


Should be possible using delegates as well.
posted by Ad hominem at 8:11 PM on November 10, 2013


I really hope that ARRR!!!, the Brown University a capairate group, was inspirational.
posted by redbeard at 8:53 PM on November 10, 2013



A ha! Filter() was the function I was looking for. Never really used it. Now I will.
posted by Jimbob at 10:22 PM on November 10, 2013 [1 favorite]


my attempt at a more readable and idiomatic version of the clojure code sonic meat machine posted above:

(->> #(rand-int 100)
     repeatedly 
     (filter (comp zero? #(mod % 3)))
     (take 100))

posted by idiopath at 4:36 AM on November 11, 2013


Thanks, idiopath. I'm not a Clojure programmer. :)
posted by sonic meat machine at 4:53 AM on November 11, 2013


I took your "(although, to my mind, less readably)" as a challenge. Though I will readily admit the lisp family is an oddball, clojure has some nice facilities for making it clearer without losing homoiconicity.
posted by idiopath at 4:58 AM on November 11, 2013


What does ->> do?
posted by sonic meat machine at 5:13 AM on November 11, 2013


It is a macro that takes each argument and makes it the last argument of the following expression.

breaking it down line by line:

; a function that returns a random integer between 1 and 100
#(rand-int 100)
; an infinite list of the above
(repeatedly #(rand-int 100))
; keep only elements divisible by 3 from the above
(filter (comp zero? #(mod % 3)) (repeatedly #(rand-int 100)))
; from the previous, get only the first 100 values
(take 100 (filter (comp zero? #(mod % 3)) (repeatedly #(rand-int 100))))

The nice thing about the ->> macro is that it mirrors the mental workflow of writing the code very nicely.
posted by idiopath at 6:04 AM on November 11, 2013


And also, thanks to aforementioned homoiconicity, there is this:

user> (require '[clojure.walk :as walk])
nil
user> (walk/macroexpand-all  '(->> #(rand-int 100)
				repeatedly 
				(filter (comp zero? #(mod % 3)))
				(take 100)))
(take 100 (filter (comp zero? (fn* [p1__1312#] (mod p1__1312# 3))) (repeatedly (fn* [] (rand-int 100)))))
here #() blocks have been replaced by fn* with auto-generated argument names, but otherwise it is the same expansion I showed above.

(the homoiconicity means that what that form returns is not a string for the code generated, but a data structure that I could use standard clojure primitives to investigate, or hypothetically modify and then execute)
posted by idiopath at 6:15 AM on November 11, 2013 [1 favorite]


Ad hominem: "I was going to look at the source to see how they implement "numbers" but I think this is implemented in Racket. It is some kinda inception type thing, "I heard you like experimental educational programming languages so I implemented one in an experimental educational programming language". It is experimental educational programming languages all the way down."

R E C U R S I O N
posted by symbioid at 9:02 AM on November 11, 2013


clvrmnky: "Great. Ruining more potential working coders with Lisp."

(I (don't (see (what (the (problem (is)))))))
posted by symbioid at 9:13 AM on November 11, 2013


Ad hominem: " I think it's related to the "industry standard"

All languages have an "idiom" or some would say a social more

you could solve the same problem in c# using:
Random rnd = new Random(); 
var filteredNumbers = Enumerable.Range(1, 100).Select(x => x = rnd.Next(0, 100)).Where(x=> x % 3 == 0);
Except nobody I work with would write it that way so it causes friction in development, my coworkers will have to stop and figure that out.
"

Wait, your professional coworkers... OK, so, let me see if I'm reading this right, you're creating a sequence of 100 random numbers, out of 0-100 (?), then iterating through them and finding numbers that are divisible by three, then assigning those numbers to "filteredNumbers"? I imagine that random() doesn't return the 1st number or doesn't return the last number (hence 100 on the rnd.Next(), instead of 99?)

So - if I read that right or fairly close, does that mean I have a shot at getting a coding job?
posted by symbioid at 9:30 AM on November 11, 2013


All languages have an "idiom" or some would say a social more

The singular form of "mores" is "mos". Also related to singulars, that post by Zed Shaw you linked is singularly stupid in just about every claim it makes.
posted by kenko at 9:38 AM on November 11, 2013


Ctrl-F "Pyret" last mention is half way down the comments, and now the word is nowhere to be seen (except just now, of course).

Poor widdle Pyret, overshadowed by all the other languages which are not the original topic of the post :(
posted by symbioid at 9:43 AM on November 11, 2013


Ironically, idiopath, that's not the same algorithm I was implementing; the algorithm I made up to demonstrate map/filter actually ends up with a variable length list of integers (averaging around 33 entries, of course). :)
posted by sonic meat machine at 9:49 AM on November 11, 2013


Oh, yeah, I broke it when I was refactoring, the (take 100) needs to be moved up to be directly under the call to repeatedly.

About the implementation (recursion on experimental teaching languages), Racket grows out of a scheme implementation (DrScheme) that was designed such that you could define subsets or extensions of the language, and tell it at invocation time which "version" of the language you are using. There was a series of languages you could invoke, starting with a very simple one with very few functions and very little syntax, and adding language features and special syntax as the student learns why they are desirable.

As far as I can tell, Pyret is one of those versions that gives up on homoiconic syntax in favor of being more accessible. Which is ironic, because it is the very homoiconic nature of scheme that allows it to be developed so easily out of the DrScheme/Racket core (along with all the other tiny pedagogical languages that it facilitated).

Addendum: indeed, pyret is just a racket syntax extension module:
http://planet.racket-lang.org/
http://planet.racket-lang.org/display.ss?package=pyret.plt&owner=wrturtle
posted by idiopath at 10:06 AM on November 11, 2013


Sequences with O(1) indexing are only meaningful in a particular model of computation? That makes absolutely no sense.

Can you outline an implementation of such a sequence without assuming that the underlying runtime has access to an O(1)-addressable, indexed memory store? If all you can rely on is that symbol dereferencing is O(1), I think the best you're going to do is something like Clojure's persistent vectors.
posted by invitapriore at 10:47 AM on November 11, 2013


OK, so, let me see if I'm reading this right, you're creating a sequence of 100 random numbers, out of 0-100 (?), then iterating through them and finding numbers that are divisible by three, then assigning those numbers to "filteredNumbers"? I imagine that random() doesn't return the 1st number or doesn't return the last number (hence 100 on the rnd.Next(), instead of 99?)

The sequence of numbers 1-100 created by Enumerable.Range(1, 100) aren't actually used.

The Select(x => x = rnd.Next(0, 100)) "Projects each element of a sequence into a new form." x = rnd.Next(0, 100) is "A transform function to apply to each element."

Each number from the original sequence is set to a new random number between 0 and 100.

The .Next(0,100) is just saying give me another new random number between 0 and 100.

Where(x=> x % 3 == 0) gives us all numbers, from the new sequence of 100 random numbers, that are divisible by 3

What we were messing around with in c# yesterday was eliminating the "generate a sequence of 100 numbers then throw them away" by generating a sequence of 100 random initially.

So - if I read that right or fairly close, does that mean I have a shot at getting a coding job?

You were way closer than a lot of "professional" C# developers would be I imaging.
posted by Ad hominem at 11:03 AM on November 11, 2013 [1 favorite]


Can you outline an implementation of such a sequence without assuming that the underlying runtime has access to an O(1)-addressable, indexed memory store?

Me? No. But it's worth observing, I think, that languages whose models of computation are functional still provide arrays—Haskell and ML, for instance, have arrays.

I wasn't aware, for that matter, that having an indexed memory store committed you to a particular model of computation.
posted by kenko at 11:40 AM on November 11, 2013


The nice thing about the ->> macro is that it mirrors the mental workflow of writing the code very nicely.

Except, of course, if you don't want to propagate your forms into the last position of the following form. I go back and forth between finding the -> and ->> macros convenient and useful and finding them to be abominable hacks.
posted by kenko at 11:43 AM on November 11, 2013


I wasn't aware, for that matter, that having an indexed memory store committed you to a particular model of computation.

I mean, aren't we talking about a random access machine? That seems like a model of computation to me.
posted by invitapriore at 12:40 PM on November 11, 2013


kenko

(->> "hello"
     (map int)
     (map (partial + 4))
     (map char)
     (apply str)
     .toUpperCase
     (#(clojure.string/replace % "PP" "CK")))
totally contrived example of my usual solution (yeah in real code I would comp those various mapped functions into one map).

luckily there are conventions about where arguments go (sequential collections go in the last arg, associative collections go in the first arg, etc.) so if your own functions follow these conventions things will tend to chain nicely
posted by idiopath at 1:47 PM on November 11, 2013


Wait - I thought you said von Neumann, but the von Neumann, is a "RASP" according to the article, and the RAM is actually Harvard.

What I am curious about is how RAM is organized in the harvard architecture, I know there's the data and program having two separate pathways, but does harvard have a memory access system using serial memory locations in the data sector that are accessed as arrays?

Is the memory pretty much the same as von Neumann, aside from the separation of data/programs....?
posted by symbioid at 2:08 PM on November 11, 2013


totally contrived example of my usual solution (yeah in real code I would comp those various mapped functions into one map).

I've done things like the last line before, but it's inelegant to have that immediate function invocation there. I've also used these:

(defmacro last->first
  "Make the last argument of f be the first argument. Lets you thread
   fns that normally work with -> using ->>:

   (->> \"a b c\"
        (last->first clojure.string/split #\" \")
        (remove #{\"b\"})
        (map keyword))

   becomes

   (map keyword (remove #{\"b\"} (clojure.string/split \"a b c\" #\" \")))"
  [f & args]
  `(~f ~(last args) ~@(butlast args)))

(defmacro first->last
  "Make the first element of the form the last element. Lets you
   thread fns that normally work with ->> using ->:

   (-> \"a b c\"
       (clojure.string/split #\" \")
       (first->last remove #{\"b\"})
       first)

   becomes

   (first (remove #{\"b\"} (clojure.string/split \"a b c\" #\" \")))"
  [& args]
  `(~(second args) ~@(concat (nthrest args 2) [(first args)])))
But, eh.

One of the Racket guys made a really nice version of a threading macro that let you specify exactly where the holes would go.
posted by kenko at 2:12 PM on November 11, 2013


Yeah I dug through the source for Racket and found their Bignum.c.implementation. It looks to be simply an array of digits.

I didn't look super close but this could be a problem when the index itself becomes larger than maxint.
If you are planning on working with numbers up to 10^(2^64) you're going to have other problems long before you run into index wrapround issues.
posted by dfan at 2:38 PM on November 11, 2013 [1 favorite]


(and by arrays I mean at least two dimensional arrays, which leaves Perl out)

The hell you say. Short hours ago I was merrily disembowling my fourth discovery of $rows[0][0][0]. False laziness in cut and pasted code.
posted by Ogre Lawless at 7:21 PM on November 11, 2013


If you are planning on working with numbers up to 10^(2^64) you're going to have other problems long before you run into index wrapround issues.

What can I say, I try to think ahead.
posted by Ad hominem at 7:30 PM on November 11, 2013


I mean, aren't we talking about a random access machine? That seems like a model of computation to me.

No. We're talking about arrays, which are a data structure that exists within the context of an abstract machine that any given programming language constitutes a definition of.

Different languages attach different sets of attributes to the structures they describe as arrays, but the key features that make an array an array are these:

1. An array is a collection of multiple member elements.
2. Array members don't need their own names in order to be manipulated; access to array members occurs via the name of the array and some kind of computable index.
3. The computational cost of using an index to reference an array member is typically low, equal for all members, and independent of the total number of members.

There are many different ways to map arrays as defined by a high-level abstract machine onto the facilities provided by any given low-level underlying machine that implements that abstraction.

To offer a definition of arrays that's completely tied to one kind of implementation, and then claim that providing arrays necessarily requires a computational model based on that kind of implementation, is straight-up question-begging.
posted by flabdablet at 9:29 PM on November 11, 2013


Also, the fact that some implementation architectures can make point 3 very hard to achieve and possibly not worth being strict about (the canonical Turing machine being one example) does not make the idea of an array any less useful and certainly doesn't make the decision to include arrays in an abstract machine's repertoire of types reflect a dedication to any given practical low-level computational architecture.
posted by flabdablet at 9:40 PM on November 11, 2013 [2 favorites]


I mean, aren't we talking about a random access machine? That seems like a model of computation to me.

What if I just extended the untyped lambda calculus with numbers (i.e. not Church numerals) and two primitives for getting and setting parts of something called "memory"?
posted by kenko at 11:02 AM on November 12, 2013


Okay, okay, I concede the point in light of your comments, kenko and flabdablet. I can see how offering arrays as a primitive doesn't meaningfully constrain your choice of computational model.
posted by invitapriore at 11:55 AM on November 12, 2013


kenko: I just learned Clojure 1.5 has as-> which is designed to address your complaint.

as->

sadly it is unsearchable even on symbolhound right now.

it takes an arbitrary placeholder as the second arg, I picked snowman to make things festive:
   (as-> {:a 0 :b 1}
         ☃
         (assoc ☃ :c ☃)
         (update-in ☃ [:a] inc)
         (into {} (map (fn [[k v]]
                         (if (number? v)
                           [k (inc v)]
                           [k v]))
                       ☃)))

{:c {:a 0, :b 1}, :a 2, :b 2}
posted by idiopath at 7:24 AM on November 20, 2013 [1 favorite]


« Older Now Let Us Mourn Together: A Brief History of...   |   "Stop at nothing... Betray, violate, cause... Newer »


This thread has been archived and is closed to new comments