Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
RDL: a lightweight system for adding contracts to Ruby (github.com/plum-umd)
81 points by muhic on Aug 7, 2016 | hide | past | favorite | 41 comments


It's always baffled me why more people don't seem interested in adopting design by contract, it is a ridiculously powerful tool for writing quality software.

Here's a Babel plugin which adds design by contract to JavaScript, squeezing it into existing JS syntax (the result is really similar to Eiffel) - https://github.com/codemix/babel-plugin-contracts

and here's what it looks like in a reasonably complicated program - https://github.com/codemix/malloc/blob/master/src/index.js

(disclosure: I wrote this)


> It's always baffled me why more people don't seem interested in adopting design by contract

Because it's yet another layer on top of other layers, because it has performance implications ... A few languages such as Vala support some form of contracts, this kind of feature is more useful when part of the language itself, not some third party babel plugin that leads to codebase fragmentation as one just had yet another layer to the compilation pipeline. No offense, that's a nice thing you wrote, I'm just not going to use this kind of thing and add more complexity for very little gain. To me tests ARE the contracts.

I would only be willing to pay the cost of transpilation with something like Typescript which adds a signification value. Babel and co ? not worthwhile.


Your argument seems more like one against transpilers in general.

Contracts are useful during development, they're typically stripped out in production builds, so there's no performance overhead there. If you find a bug in production, turn on your contracts and you'll quickly discover the source of the error. Contracts can validate the state of the world at runtime in a way that can never be covered by unit tests, unless your tests cover all possible inputs to your program. It doesn't mean you don't need unit tests too, they are yet another layer of safety.

I use this in combination with Flow, which means I have to transpile regardless, so the overhead is very low for me. Of course I'd like them to be part of the language proper, but for that to happen there needs to be developer demand first.


If it's stripped out during production, that sounds more like an assert than a contract. What's the line over which you'd call an assert a contract...?


Contracts are just structured assertions, they're very similar.


> It's always baffled me why more people don't seem interested in adopting design by contract, it is a ridiculously powerful tool for writing quality software.

Because it's an ugly and slow run-time hack around a proper type system, but without most of the benefits of a real type-system. The compiler cannot optimize with the given types ("contracts"), it can only add run-time checks.

Check what a typed ruby can do, and how the syntax looks much more natural there.

E.g. in my typed perl (cperl) I optimize array accesses to omit out-of-bound checks when the compiler knows that the index is within the bounds. This is true for "shaped" arrays (typed arrays with a constant range), if the constant upper bound of a loop is within the shape, or if the loop just iterates over an array.

If the compiler knows that the given number to an unary op is only an integer, the optimized integer method can be used instead. If both arguments to a binary numeric op are integers, the optimized integer method can be used. ...


I've never seen a type system expressive enough to cover all the things that contracts can do, e.g does a type system exist which can assert the existence and size of a particular file on disk?


This looks cool. Where do you see this fitting in versus using something like TypeScript or PureScript?


It's definitely most useful when combined with type annotations. They're complementary tools, annotations can ensure type safety at compile time, but type safety is only one part of program correctness, whereas contracts can validate just about any aspect of program operation because they have access to all of the runtime information. They're like unit tests that run all the time (until you strip them in production at least).

This thing can be used alongside Flow[0] and other babel plugins which use flow syntax.

[0] https://flowtype.org/


Typing is such an important and powerful construct. It'd be amazing if scripting languages like Ruby and Python built optional typing into future versions, especially if they could be used in speeding up the execution time. If nothing else, type safety for function signatures would be a huge win for productivity, testing, and would cut down on a large class of programmer-introduced bugs.


I used languages with static types for half of my professional life, the last one was Java, and languages with dynamic types in the other half. I never felt like I have more bugs in dynamic typed languages. What I felt is that I don't have to waste time writing stuff like ArrayList<Whatever> when just something = {} works perfectly well in practice.

I understand that many people say that with static typing if it compiles it works, but I remember using debuggers quite a lot.

I'd say that if one wants types there are plenty of languages to choose from. There is Crystal if one likes Ruby.

Contracts are interesting. I remember Eiffel and other languages. Those could be useful regardless of the kind of typing the language use.

BTW, invariants are another useful tool when designing some algorithms and were neglected recently.


> I never felt like I have more bugs in dynamic typed languages. What I felt is that I don't have to waste time writing stuff like ArrayList<Whatever> when just something = {} works perfectly well in practice.

Java is a pretty terrible language when it comes to static types. If you have a language that allows you to work with types in a succinct and expressive way, you end up with a powerful modelling tool that can help you reason about and design programs. The side-effect of this is to have far less runtime crashes (you know what cases to check for, like nullable data), great documentation for your future self and your fellow devs, and a greater liberty to do large scale refactors without worrying about breaking things.


The self-documenting nature of statically typed programs is something that critics often miss. Making a contribution to an existing codebase is far easier when it is statically typed. You can very easily understand what you ought to do, because the constraints of the type system help you figure out exactly what things are, what arguments you can pass to a method, how the other programmers used the types, etc.

In contrast, contributing to an existing codebase in PHP or Rails is much harder. Nothing will stop you from sending the wrong arguments to a method, or adding members to classes that should not be modified, or even simply refactoring and forgetting to change all of the usages of the types and methods you modified.


Well, the converse argument is that most dynamically typed languages allow you to explore the codebase in a REPL by playing with it. And there's a reasonable amount of noise that is added by static types, since often the types don't tell you how to use them, so you have to read the docs anyhow.

Which really means there's a big difference between static types that everyone knows and uses, and those that someone invented for particular use. That difference seems more significant than the static/dynamic divide.


There are statically-typed languages with REPLs, e.g. Scala, C#, F#, and the upcoming Java 9, so REPLs are not an advantage of dynamic languages. There are even REPLs for C++.

Many people's impressions of static languages are based on Java from 10 years ago. It's really quite different now.


I find the Haskell, SML, and OCaml repls very nice to use as a exploratory tools. It's made all the nicer due to the types, because you can quickly iterate on gluing things together in different ways. It's something I miss with Rust, although there is progress being made to implement one.


Agreed.

Kotlin is an excellent example of such a language, and I'd highly encourage anyone, especially devs with a strong Java background, to try it out.


I have similar experience. For the most part I agree with you, but types often make reading code a lot easier. I've found that many people tend to encode their types into the variable names in languages without type checking. A lot of refactoring tools work more easily with types as well. If you have a method like "write", it's nice to be able to rename it and know 100% for sure that you didn't break something.

I'm with you on the excess typing thing, but I still think types are useful just for tool integration. One day I hope that a popular language will emerge that presents an AST so that we can have non-type based refactoring browsers like Smalltalk's.


It's probably unfair to compare bugs to bugs, since with a typed language, your compiler and editor caught many "bugs" before they even hit your application.


While I enjoy using dynamic languages in small projects, I dislike to use them in the typical enterprise projects.

Most of my time is spent playing the game "which type is this?".

Yeah, one can write unit tests to play the role of the compiler's type checker, but in the enterprise unit tests are pretty low in the deliverables list, usually the bullet following documentation.


> especially if they could be used in speeding up the execution time

Unfortunately I feel like this is where types are least likely to be useful in Ruby and Python. Static typing means that you know the type of a value coming into your function, but you can achieve exactly the same thing by dynamically profiling the type, just-in-time specialising the function for it, and then on each call all you need to do is check that the type is what was expected. The check will probably be just one machine word comparison. So static typing saves you just one instruction!

However it would remove the time, memory and complexity needed for the profiling. I'm not sure that's really a big overhead though.

I work on implementing existing programming languages using new technology, and in some cases we basically discard static typing information for the purposes of optimisation, because we can work out better information for ourselves at runtime using profiling.



There are some really interesting developments in clojure around this, if you're interested.


RDL supports most of the serious research anyone is doing into types in Ruby. See the papers with Foster as an author under Types here http://rubybib.org. I hope the Ruby core team is watching their progress since some kind of typing is being debated by Matz.


That is "the" problem, "how" do we know if the Core Team are watching / discussing / debating. Comparing a similar situation to the Python circle ( You can laugh about Python 3 all you want, but that is another issue ), which is clearly layout and in some way beautifully presented.


After changing focus from Ruby to functional languages recently I've found that optional types can be really helpful in a lot of situations.

Ruby also has another popular and mature library that I am surprised nobody has mentioned yet named 'Contracts.ruby'

https://github.com/egonSchiele/contracts.ruby

This syntax is also more appealing to my eyes than what rdl provides.

  Contract Num => Num
  def double(x)
     x * 2
  end


Very cool. I experimented with something similar in JavaScript.

https://github.com/smizell/snug

For me, it is useful for runtime checking data that may come across the web.


Good to see PLUM (Programming Languages at the University of Maryland) releasing their work on GitHub. They're among the most talented in the department at UMD.


Totally. PLUM is a great team, and this work on ruby contracts from Brianna and Jeff is particularly awesome. By the way, their newest paper about the work in this github is going to be at PLDI: https://www.cs.umd.edu/~jfoster/papers/pldi16.pdf

(disclaimer: I just graduated from PLUM)


Awesome, thanks for sharing! And congratulations on graduating.

I'm an undergraduate, so my experience with PLUM is just that of an admirer looking in ;)


Oh wow, even with support for static type-checking. It really makes me want to give ruby another shot.

Piggybacking on this: apart from F*, does someone know of a language or support for existing ones for contracts, loop-invariants and possibly verification?

Dafny and Spec# aren't very general-purpose and apart from those nothing much comes to my mind.


Ada has robust preconditions and postconditions.

Untested code follows:

    procedure swap(a: in out integer, b: in out integer)
    with
      pre => a <> b -- just for example purposes
      post => (a == b'old) and (b == a'old)
    is declare
      t: integer;
    begin
      t := a;
      a := b;
      b := t;
    end
(Sorry, couldn't think of a sensible small example which uses both pre and post, hence the terrible precondition.)

Note that the postcondition can refer to the old values of the variables with the 'old suffix --- the values will be automatically saved on entry to the function and used for comparison later.

Unfortunately the pre- and postconditions have to be specified in the public part of the module, so can't see any private module variables, which forces you to jump through hoops if you don't want to expose your module's internal state (e.g. checking to make sure that functions on a state machine are called in the right order).

You also get type invariants, where you can specify an expression which must always be true for a number:

    subtype Even is integer
    with dynamic_predicate => (Even mod 2) == 0;
Or, if you really want your mind blown:

    subtype Even is integer
    with dynamic_predicate => (for some N in integer => (Even == N*2));
There's also a static_predicate form which only supports a restricted predicate expression but which can be checked at compile time.


Thank you for the example.

Also: SPARK is another one I forgot. I guess I'll have to think of a nice example and just implement it in a Ada, F* and with RDL to get a hang of the differences and features.


When I think of contracts, I think of Eiffel. Not really sure how much it's actually used, though. Also, maybe Ada? I always wonder why Ada doesn't get any love on HN.


Given that the company is still around, I would say that they manage to have a set of customers that value the language and what it offers in terms of quality.


This is fantastic. They even have type checking. Looking forward to the day this turns into something like tsc.


Don't get too excited. It's not rails enough for the ruby community to ever seriously use it. I know this from firsthand experience going down the same rabbit hole a few years ago.


>rails enough

Cant upvote you enough. I am getting the sense that DHH dictates more of Ruby rather then Matz. ( Which isn't necessarily a good or bad thing )


Anyone know what the performance implications for using this would be?


Some differences from Eiffel contracts: no class invariants, and postconditions don't have access to previous object state.


I wonder if contracts can be used to speed up the execution instead of slowing it down.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: