Confessions of a Ruby Developer Whose Heart was Stolen by Scala
speakerdeck.com
speakerdeck.com
To me, this type of development is much more natural and sane than the "one big clusterfuck" type of projects I've seen in Java et.al.
Edit: Also, dynamic languages let you make the tradeof between safety and speed. Sometimes you go slow and steady (simple code, tests..) and sometimes you just want to test something out.
implicit class RichSeq[A, C[A] <: Seq[A]](underlying: C[A]{
def cycle: Iterator[A] = {
lazy val circular: Stream[A] = underlying.toStream #::: circular
circular.iterator
}
}All it does is convert a Seq (sequence) to a lazy stream that will infinitely cycle through the values.
Seq(1, 2, 3).cycle.take(8).toList
res3: List[Int] = List(1, 2, 3, 1, 2, 3, 1, 2)
It will work for any seq-like structure (including List. Vector, Queue, etc.)If you try to use it with anything else, like a Map, it won't compile.
Also, note from my example that it's generic and the resulting List is of the correct type.
Just like novice JavaScript developers struggle with "Why 'this' changes here?", it requires some experience with the language.
I don't think so. It is full of syntactic noise, for what is really just notation for building a cyclic structure.
* type annotations stated explicitly, not inferred
* type variables given multiple times
* too much non-verb, non-noun syntax e.g. `#:::`
* and the actual construction of the cycle is stated imperatively with great ceremony (despite it being an applicative)
It is the opposite of dense.
Scala, Haskell treat syntax as a chore.
> take 8 $ cycle [1,2,3]
cycleM x = x `mplus` (cycleM x)
take 8 $ cycleM [1,2,3]
might be more generic? cycle seq = circular where circular = seq ++ circular
(or if you want to really golf it) cycle = fix . (++)
Ok, to be fair the Scala code is a bit more generic as it works for any abstract sequence, but still, Scala's syntax is awfully heavy compared to Haskell.That's one reason I still prefer Haskell even though Scala is easier to sneak into enterprise projects thanks to JVM. Not to mention that Haskell gives stronger static guarantees and has a more sophisticated type inference system.
cycleM = fix . mplus
gives a nice generic version that works on Lists & Sequences but falls afoul of the monomorphism restriction, so it needs a type: cycleM :: (MonadPlus m) => m a -> m a
cycleM = fix . mplusRegarding Scala's complexity - some more advanced features are presented in those slides. Imagine watching a talk about Python where they talk about decorators and meta-classes.
[1] https://www.youtube.com/watch?feature=player_embedded&v=grvv...
Here's a Ruby snippet. I don't want to pick on this particular project [1], rather, I've found that this is typical of Ruby code:
state_machine :state, initial: :active do
after_transition any => :blocked do |user, transition|
# Remove user from all projects and
user.users_projects.find_each do |membership|
return false unless membership.destroy
end
end
My conclusion? Ruby's syntax is awful. There are colons, absolute value bars, implications, and who-knows-what-else flying everywhere! It gets worse, elsewhere in the same file there are messy lines like this one with single-arrow implications, question marks and ampersands [2] [3]: scope :not_in_project, ->(project) \
{ project.users.present? ? \
where("id not in (:ids)", ids: project.users.map(&:id) ) : scoped }
Simple, intuitive syntax? From where I sit, the syntax of Ruby is worse than C++, and approaches Perl levels of awfulness. In most languages, I can at least sort-of grok what's going on from the context when I see unfamiliar operators, but not so in Ruby.This is a copy-paste of a comment I made [4] weeks ago on another article.
[1] https://github.com/gitlabhq/gitlabhq/blob/4caaea824cf51670d1...
[2] https://github.com/gitlabhq/gitlabhq/blob/4caaea824cf51670d1...
[3] I split the line and inserted backslashes because HN gives me a horizontal scrollbar when it's a single long line; apologies if this transformation isn't legal Ruby, or changes the meaning of the code.
* Use setuptools, easy_install, pip while we have fun with our rake and gems.
* lambda : Please have fun, with this vomit of a feature.
* __init__.py explain that to a python beginner.
* Have fun with, { "quote": "shit" } while we use our :symbols
* What scope ?
* Have you migrated code to class Foo(object): yet ?
* How is py3 coming along ? Having fun with the worlds cleanest and bossy language community ?
* Write all your list comprehensions line noise, while we add a elegant method.
I just realized, whitespace is also, precisely the entire content of a pythonista's head.
I think you're making the mistake that because Ruby isn't a C-style language, the equivalent of a Latin script language, it's not understandable. It's just disadvantaged that you can't immediately make some assumptions about what the operators do based on previous experience.
First off, parenthesis in ruby are optional. So calling a method can be done without parenthesis. The following two lines are the same:
print()
print
As are these: print(1, 2, 3)
print 1, 2, 3
Here's some bits that might explain better what stuff does: :state
If you prefix a word with a colon that creates an object of type Symbol, with value "state", which is a bit like the string "state". The difference is that every mention of :state is a reference to the same object, while if you create a string "state", and a bit further do it again, those are two different objects. We use symbols because it runs faster for conditionals etc: to compare two symbols the interpreter only needs to check the reference, while to compare two strings, every character needs to be checked. initial: :active
This creates a Hash with one key-value, with key :initial, and value :active, both symbols. This is equivalent to typing :initial => :active which you encounter a bit further up. do |var1, var2| .... end
This creates a block, which is like a closure. The variable names between pipes are the arguments this block takes. Any code inside the block is only run when the block is called. In Ruby, every function can have a block passed by adding do ... end to it. Inside this function, the block can be called using the keyword yield, e.g. yield(1,2) to call the block with arguments 1 and 2. The find_each method will loop through every value of the Array it is called on, and run the block once for every value. project.users.present?
The question mark is a valid character for a method name, there's nothing special happening here, present? is just the name of a method. This could easily have been called is_present, or just present. a ? b : c
The ternary operator, the same in any language. It translates to "if a, return b, else return c". ->(arg1, arg2) { ... }
This creates a lambda, with arguments arg1 and arg2. A lambda is almost the same as a block, so this is almost the same as typing do |arg1, arg2| ... endMy attempt to parse the above example comes to something like this:
state_machine(STATE, {"initial" : ACTIVE}, function()
{
after_transition({"any" : BLOCKED}, function(user, transition)
{
user.users_projects.find_each(function(membership)
{
return (membership.destroy ? true : false);
});
});
});
I'm writing in JavaScript because its function notation is way better than Python's lambda syntax. From the parent's explanation, I'm pretty sure this is a valid translation, even though I don't know Ruby. But having three levels of nested anonymous functions really makes the code hard to understand. I'm guessing that this is idiomatic Ruby though, and if you program with the pattern long enough, it gets easier.OTOH it's still a barrier to entry entirely separate from the syntax -- if the semantics of Ruby code you see "in the wild" is typically this complicated, it's almost as bad as Haskell and its monads! (I have much stronger math chops than most programmers, I've tried to read introductions to monads twice, had them explained to me three separate times on HN, and still don't understand them at all!)
Also, the return in that block would cause an exception, because it doesn't do what the original programmer thinks it does. I guess he never tried out what happens if destroy returns false (which would happen if destroy fails, e.g. because a validation failed).
If you can call 0-ary functions (functions which take zero arguments) without parentheses, how does Ruby know what a statement as simple as "a = b" does? It must expand internally to something like this (in Python notation):
a = b() if b.is_function else b
But then if the b() call itself returns a function, then is that function auto-called as well? So we'd have something like this as the Python translation: a = b
while a.is_function:
a = a()
This can't be right.And then you get syntactic ambiguities. For example, is the Python translation of the expression x = h-3 (in Ruby) equivalent to (in Python):
(A) x = h-3
(B) x = h(-3)
(C) x = h()-3
(D) x = h()()-3
(E) x = (h-3)()
(F) x = (h()-3)()
Maybe different whitespace, different compile-time definitions, or different run-time values will change the answer! And since the talk says you can monkey-patch Ruby's integer data type, you could even presumably make integers callable so things like "x = (h()-3())()" would be possible!
And then the overloading of the colon and question mark.
Is the expression "k:v" in Ruby a dictionary containing a single key, "k", which is mapped to the value "v"? Or is it a function call of a function called "k" with a single argument, ":v"?
And the ternary operator uses colons too! "a?b:c" could translate as a function called "a?b" being called with a single argument, ":c". Or a dictionary with a key "a?b" which maps to value "c". Or of course the ternary operator! And that's assuming none of the sub-expressions involved are 0-ary functions which are automagically called!
And then what if you want to disable the automagic calling and pass a function object around? Do you have to decorate it with an initial ampersand or something every time you use it? What if you have code like this that puts either a function object or an integer into a variable:
h = flag ? (&my_function) : 5
Then you want to copy h to the variable y. If you say "y = h" then it does the wrong thing when h is a function, because then h would be auto-called. But if you use the ampersand, you do the wrong thing when h is an integer, because then you would be taking &5 and (in C notation) this would change y from being of type "int" to type "int*". Good grief!Which all reinforces my original point: Ruby syntax is aggravating! This language is impossible to deal with!
(Sorry for the double reply, but I feel like this comment is different enough from my other reply to merit its own space.)
For the code samples going forward, keep in mind that in ruby you don't need a literal return statement to return a value from a method. Just whatever value is last, is returned:
def val
5
end
That method would always return 5.> But then if the b() call itself returns a function, then is that function auto-called as well?
No, the only way to pass a method is by using method(method_name_here), which returns a Method object. To execute a Method object, you need to call call on it:
def a
end
method(:a).call
So if you have a method that returns a method, it should not be automatically called, so there's no loop necessary: def b
method(:a)
end
z = b
Then z is a Method object, namely a.Some experiments to answer your questions (I admit I wasn't sure for all of them what the outcome would be). IRB is Ruby's repl:
irb(main):001:0> def a
irb(main):002:1> 5
irb(main):003:1> end
irb(main):019:0> {a: 1}
=> {:a=>1}
irb(main):020:0> {a(): 1}
SyntaxError: (irb):20: syntax error, unexpected ':', expecting tASSOC
irb(main):009:0> def h(num = 10)
irb(main):010:1> num
irb(main):011:1> end
irb(main):012:0> h
=> 10
irb(main):013:0> h-3
=> 7
irb(main):014:0> h -3
=> -3
irb(main):015:0> h - 3
=> 7
irb(main):001:0> a?b:c
NameError: undefined local variable or method `c' for main:ObjectBut again, it steepens the learning curve -- it's so different from other languages that it's actually kind of hard to immediately see all the implications of "well Ruby's lexer is different than C's lexer".
Are the Ruby complaints in the slides real - do people do those sort of things often and expect other languages to behave like that too? Is that because of the standard library or innate to the language?
http://logic.cse.unt.edu/tarau/teaching/SCALA_DOCS/scala-for...
And take the "in Scala" part of the course title with a grain of salt. You're only taught enough to complete the exercises.
I'm happy he found a language he enjoys working with, but I doubt this will change anyone's mind...
For example:
val i = 1;
i = "some string" // throws a compiler error, because it knows i is an integer
It's great when you haven't declared the types, you change for example an int to a double or a class to some other class (eg swapping a data structure) and it just flows through the code base without problems like a dynamically typed language.
It can also be pretty useful to look up types when they get complex in the middle of some function, like a Map[List[(String,SomeObject)]] (a map of lists of (String,SomeObject) tuples). Allowing the types to get complex lets me focus on the problem while giving a crutch to quickly remember where it's at half way through (and while finding other methods/source of data) and keep moving towards the solution.
I noticed a lack of a link to a 1:1 comparison of application code with realistic examples. I think such comparisons and related discussion are often the best way to convince someone to use a language.
And some slides were just blatantly wrong, like Mixin Abuse: apparently that is just a long list of module includes? I have never seen so many module includes in a single class in application code. But, assuming you did have that, you should show what it would look like side-by-side in Scala. In Ruby, there are a lot of options when it comes to including other code or defining code in a class, instance, etc. Those options can lead to much less and more legible code if used correctly.
I remember when people said Java was slow; they made it seem like it was a fad, and no reputable company would use it. And... we see where that went.
Also, Tcl/Tk called and wants its use case back.
Highly dynamic languages, such as Ruby, certainly have their advantages too. But programs in languages which enable, if not encourage, developers to add new methods to the integer '5' can quickly become very difficult to reason about.
Static, strongly-typed languages also provide other benefits such as much better error checking and compile-time optimization.
Can you give me an example of a single ruby developer who thinks this is a good idea when writing new code/a library?
It only takes one, but here are a few thousand to start with: https://github.com/search?l=Ruby&q=%22class+Integer%22&ref=a...
Do your projects use any Gems that are pulling in any of this code?
Could they change to do so in the future?
Most importantly: how much effort is it for you to definitively answer this question?
Our task is that of proving a negative (which as we all know is very difficult). Namely that there is no other code that is relying upon the behavior that you are changing. When there are reasonably well-defined interfaces between components it dramatically reduces the possibility space for implicit dependencies and interactions. So this is a slam-dunk case where tools can make our job much easier.
Without tools, we're basically reduced to "verbal lore" and "honor system". Programmers have to rely upon the shared understanding and behavior of other humans in order to reason about their own code.
You do know that Scala allows just that, right?
Do you happen to know of any coding standards documents for large projects using Scala?
In Python, a private field starts with an underscore, and someone who knows the culture of Python knows that the initial underscore encodes the notion "This field is not considered to be part of this library's public API, and it may go away or change behavior at any time, not necessarily a major version bump of this library, and if you look at it or change it, you'll be responsible for maintaining your code when we break it. Also it's probably totally undocumented, so you'd better read the library code to make sure it does what you think it does before you use it."
A good developer will weigh the work that can be accomplished today by using the private field against the future consequences of the underscore's admonition, and come to a wise decision -- if (s)he decides to use the field, the Python language will defer to the programmer's human judgment and allow it.
A mediocre developer won't understand the underscore's implications, and will blithely use private and public fields in exactly the same way, because Python won't stop them.
In Java, the language itself will prevent this from occurring -- a private field, in Java, is really private. So the compiler [1] prevents mediocre developers from producing brittle code by referencing private fields everywhere.
If you have a lot of mediocre developers in your organization -- which IMHO tends to happen more in larger organizations -- then you want a stricter compiler to stop them from writing unmaintainable code.
I personally prefer the Python way, simply because I've been bitten by this Java "feature" on multiple occasions -- a third-party library marks some field as private, but I really want to use it, to the extent of being willing to deal with instability by updating my code or freezing the dependency, if necessary. But I can't, without making my own fork of the library.
[1] The runtime also prevents private field access. So even if you bypass the compiler's checks by patching it or hex editing the compiled bytecode, you'll still get runtime errors from trying to read private fields outside the class where they're defined.
We may laugh now, but it actually seemed like a good idea at the time.
Remember, a big part of Java's early use case was running untrusted remote code ("Applets") in the browesr without prompting the user (a niche now filled by JavaScript [1] and a declining Flash). Which means you really don't want that code to be able to bypass all your security by using the reflection API to read and write things it's not supposed to.
Applets never really caught on, but now the language features are constrained by backward compatibility.
Also, C# isn't supported on Android AFAIK.
[1] Mostly unrelated to Java despite its name; see http://en.wikipedia.org/wiki/Javascript#JavaScript_and_Java
[1]http://stackoverflow.com/questions/1196192/how-do-i-read-a-p...
(a) Python isn't fast enough, and Pypy doesn't make it fast enough, or isn't a viable option in the target environment. So you write in C, OpenCL or assembly language.
(b) A vital library or other dependency can't talk to Python and you can't quickly find or write a reimplementation or bridge.
(c) Python isn't supported in the target environment, for example, the web browser [1] or the iPhone.
[1] Yes, yes, I know, things like Skulpt and Pyjamas exist. But I think those dependencies are too heavy for a lot of projects.
Googling about SBT came back with a lot of complaints about the state of scala tooling back then.
[1] https://github.com/yareally/android-scala-intellij-no-sbt-pl...
Seems like the tooling is still one of the more painful aspects of Scala. Kudos to Intellij, -1 to Scala.
Excessive configuration to get a new project up in a running is a major demotivator sometimes when you have a new idea and want to just start coding right away.
I've used Scala for a couple Android apps in the past... it was great fun to do so! Still, AndroidAnnotations not working with Scala code requires you to rewrite some of their functionality... but you can do that rather easily with Scala's traits etc.
Edit: seems it doesn't like ActionBarSherlock most likely due to that. I get "Error com.actionbarsherlock.R and com.actionbarsherlock.R$xml disgree on InnerClasses attribute"[1].
Regarding AndroidAnnotations: https://github.com/excilys/androidannotations/wiki
Ahh, I know of that project. Like it quite a bit. Was just confused over the ambiguity of the term "annotations." Thanks for the followup though.
-keepclassmembers class * extends com.actionbarsherlock.ActionBarSherlock { <init>(android.app.Activity, int); }
fixed it. Ugh :/
If C has Valgrind then dynamic typed languages should get something for the types.
http://blog.jetbrains.com/pycharm/2013/02/dynamic-runtime-ty...