[HN Gopher] Learning Ruby: Things I Like, Things I Miss from Python
___________________________________________________________________
Learning Ruby: Things I Like, Things I Miss from Python
Author : OKRHero
Score : 194 points
Date : 2021-02-16 06:50 UTC (16 hours ago)
(HTM) web link (medium.com)
(TXT) w3m dump (medium.com)
| zem wrote:
| ruby is one of my favourite languages, but there are two things I
| wish it had done python's way - inline type annotations, and
| imports acting as namespaces. I wouldn't have thought to miss the
| latter before I got a day job working in python, but it really
| does make code in larger projects a lot easier to read and
| maintain when you can tell at a glance where every symbol is
| defined.
| LimaBearz wrote:
| Can we talk about Ruby's laissez faire approach to syntax.
| Ignoring all the purely technical stuff every ruby project I've
| seen that has at least a small team behind it looks like a
| kitchen sink. Bob does it X way, Tina does it Y way, Joe does it
| X way. Every person does it their own way and readability becomes
| an eyesore as the project grows.
|
| If I didn't know any better I've seen teams so bad it's almost a
| game of "look how gross this block is, now let's see how much
| worse you can make the next one".
|
| I've always found ruby very off-putting for this reason alone. I
| don't want to inherit or work on something where everyone just
| wants to show off. How do organizations manage this? Is it just
| me?
| hrktb wrote:
| I'd argue the issue is better acknowledge in ruby than in many
| other popular languages, which just assume having few options
| means everyone can mostly code as they want from there.
|
| Sister comments are pointing to rubocop which mostly makes it a
| solved problem. I'd add that having multiple potential ways to
| do things, and being able to also favor a specific way through
| linting is very powerful.
|
| You can actually choose the best option for your current
| project and enforce it, while choosing a different tradeoff for
| another project and still enforce it there. And you still can
| opt-out of the rules on a case by case basis in a standard way.
| mparramon wrote:
| Following Rubocop's Ruby Style Guide [1] to the dot helps a lot
| with this, and it's quite easy to enforce using the rubocop gem
| + a GitHub action
|
| [1] - https://github.com/rubocop-hq/ruby-style-guide
| ljm wrote:
| I wouldn't really go that far though, as you'll end up with a
| codebase that the linter likes (and _appears_ clean) but is
| still an utter nightmare to read because of all the forced
| indirection. I 'm talking about things like: restricting
| method length to 10 lines, restricting class length to 100
| lines, and similar ones that prevent you writing code in a
| certain way because the preferred approach to writing code is
| 'out of sight, out of mind'.
|
| Default Rubocop is why you have codebases where code is still
| complicated, it's just hidden away inside single use modules
| (concerns) or inside single-use private methods. As far as
| I'm concerned you just can't defer the architecture of your
| codebase to a tool like that and I'm sure it teaches more bad
| habits than good ones.
|
| Instead, decide these things for yourself and keep it in
| check during code review or the stage before that. Maybe even
| design rubocops of your own to help. The linter can still do
| a good job without telling you how to handle complexity.
| sodapopcan wrote:
| Couldn't agree more. Our Rubocop is FULL of customizations
| and the odd override too (though there is a case some
| overrode a rule which caused massively confusing code).
|
| "Single use private methods" is a little triggering to
| me... not even rubocop's fault. I've seen the likes of this
| before (single use): def
| redirect_to_home_unless_user_present redirect_to
| :home unless user.present? end
|
| Why!?? (that is a rhetorical "why")
| ysavir wrote:
| That's a horrible usecase for single use private methods,
| agreed. But I think they're an excellent way to breakdown
| a complex chunk of code into smaller components, using
| the method names to describe what they're doing. That
| requires good naming, though; without good names, the
| effort is moot.
| ljm wrote:
| I think they serve a good purpose once you realise you've
| stepped out of OOP. You have the idea of a command, or a
| service, and it does a bunch of procedural things as part
| of its execution. It only has one public method, so the
| private methods are a way to describe the steps of the
| command.
|
| But that's an architectural decision, it's not code
| style. If rubocop tells you how to write a function then
| you're going to see nonsense like the example in our
| parent comment.
| ysavir wrote:
| > I think they serve a good purpose once you realise
| you've stepped out of OOP. You have the idea of a
| command, or a service, and it does a bunch of procedural
| things as part of its execution. It only has one public
| method, so the private methods are a way to describe the
| steps of the command.
|
| It doesn't have to be a step out of OOP, and their usage
| is not limited to "Single Public Method Objects". Single
| use methods are just a convenient and clean way to make
| complicated code easy to digest, OOP or not.
| ysavir wrote:
| Very refreshing to see this take and I largely agree.
| People all too often refer to linters as The Rule rather
| than The Tool, and forget that a robot will never be able
| to evaluate the quality of a codebase. That requires human
| input (though Rubocop is very efficient for people that
| want to avoid human input--often the root cause of the
| problem).
|
| That said, I am a fan of single-use private methods and
| think they have their place, with or without method-length
| rules.
| andrewzah wrote:
| You can configure rubocop however you want.
|
| If you find the defaults to be not good (which I agree),
| then simply update the rubocop.yml.
| sodapopcan wrote:
| In fairness, ljm was responding to a comment that
| suggested following the style guide "to the dot".
| ljm wrote:
| That was that's exactly the point I was aiming to
| address, thanks :)
| tomthecreator wrote:
| The solution is the same as in many other programming
| languages: adopt a coding standard and enforce it in an
| automated fashion, i.e. in the CI/CD pipeline. Code style then
| becomes mostly a non-issue and not an object of discussion.
|
| In the case of Ruby:
|
| * most popular style guide: https://rubystyle.guide/
|
| * automated tool for linting and enforcing code style:
| https://rubocop.org/
| pelasaco wrote:
| I just would like to point out that even though that is the
| most sane way, it comes with it owns set of problems. One of
| them is when developers start to code to cheat the linter, or
| they complicate the code just to "make the linter happy",
| another is when the linting rule introduces problems/errors
| like https://github.com/rubocop-hq/rubocop-rails/issues/418
| msbarnett wrote:
| Yeah I would never recommend relying on just a linter. The
| linter can reduce scut work, but you always want to have at
| minimum a thorough code review process that's looking at
| things like "ok, we made the linter happy; but are we happy
| with the result, or should we have just disabled the linter
| here?" and "the linter didn't catch that we solved this
| thing with X here but with Y&Z last time. Let's rationalize
| our approaches and get everything on the same page"
| [deleted]
| crazypython wrote:
| > Can we talk about Ruby's laissez faire approach to syntax.
| Ignoring all the purely technical stuff every ruby project I've
| seen that has at least a small team behind it looks like a
| kitchen sink. Bob does it X way, Tina does it Y way, Joe does
| it X way. Every person does it their own way and readability
| becomes an eyesore as the project grows.
|
| This is why Python has deliberately chosen to not add Ruby's
| blocks. To make any code readable by everyone. (And to prevent
| bad coding patterns that blocks promote, but that's besides the
| point.)
| LordHeini wrote:
| There are multiple ways to do things in many languages. And
| Ruby is a rather sane one.
|
| The worst language in that regard is Scala by far.
|
| There are the "Scala is Haskell" people where everything is a
| pure function and written in point free style.
|
| There are the ones who think Scala is just Java with type
| inference.
|
| And the everything needs to be a DSL people.
|
| Or everything needs to be implicit and generic in a static
| singleton.
|
| There are a bazillion ways to write the most trivial stuff. Use
| for comprehensions, recursion, higher order functions, or your
| usual for each loop. If you hate your coworker use some fancy
| type system trickery. Does not matter pick and choose.
|
| Ruby is mostly confined compared to many similar languages like
| Python.
|
| I would say that even JavaScript is way more chaotic in that
| regard.
| burnthrow wrote:
| We can talk about anything we want. Don't even have to open the
| link. Now let me tell you what _I_ think about Ruby...
| blunte wrote:
| When it comes to showing off with Ruby, I think that only
| occurs in two cases: creating unnecessary DSLs ( _cough_ Rspec)
| when plain Ruby was already adequate, and doing as many chained
| collection operations in one line as possible.
|
| The former adds a layer of abstraction which is often
| unnecessary, and the latter can go from succinct to slightly-
| too-clever to fully obfuscated.
|
| As others have mentioned, Rubocop can help keep some uniformity
| (although some of the default rules suck, imo). And beyond
| that, the company just has to have people who agree to be
| decent. It will happen that developers of different skill
| levels will write more or less readable code depending on the
| perspective of the viewer, but that's true in all languages.
| You _could_ write Ruby code like you would write Basic.
| calessian wrote:
| That's not exclusive to Ruby, though. Is there a language in
| which a project of at least a small team would not diverge?
|
| Devs at my current dayjob have established, in multiple rounds,
| that it is impossible to find at least a minimum of viable
| coding standards, where every draft has been heavily opposed.
| msbarnett wrote:
| > Devs at my current dayjob have established, in multiple
| rounds, that it is impossible to find at least a minimum of
| viable coding standards, where every draft has been heavily
| opposed.
|
| Tangential, but this mostly sounds like a leadership issue?
| Ultimately software development is a team sport, and teams
| work if and only if individuals put the team's interests
| ahead of their own - and it's not realistic to expect a group
| of individuals to develop that mentality by consensus. Absent
| leadership, all you have is a bag of individuals who will
| bicker about the bikeshed endlessly.
|
| A good leader would hear that feedback and accommodate it to
| an extent, but they also have to take the responsibility for
| saying "alright folks, I've heard your feedback, but
| ultimately it's more important to the team as a whole that we
| have _something_ than it is that everybody got that
| _something_ to be what they liked most. This _is_ arbitrary,
| but this is what we 're running with, so it's time to fall in
| line".
|
| (This is something of a pet peeve of mine, as my team
| occasionally has to work with an ops group that is stuck in
| the stone age solely because they've lacked clear leadership
| for a decade or more. Every time they meet to discuss
| aligning and modernizing their approach ends in a lack of
| consensus as to _how_ , and consequently in 2021 they
| continue to manage their servers each by hand, each in their
| own way, a collection of expensive single points of failure.
| Literally any approach they discuss would improve on the
| status quo, so any competent leader at this point should
| simply roll the dice, pick one at random, and get on with the
| task of moving forward)
| gilbetron wrote:
| While not perfect, Go has much more "developer convergence"
| than any other language I've worked in. Ruby has numerous
| ways to do the same thing, but one of Go's design objectives
| was to minimize that.
|
| So divergence does occur, but some languages make it a lot
| easier than others.
| viraptor wrote:
| > Python and Ruby are really similar.
|
| Yes they are, as seen in the _why's old project:
|
| https://github.com/whymirror/unholy
|
| > Compile Ruby to Python bytecode. And, in addition, translate
| that bytecode back to Python source code using Decompyle
| (included.)
| breatheoften wrote:
| ruby has enumerators which are quite a bit like pythons
| generators -- i actually think they are easier to reason about
| and do complicated things with -- although i learned python a
| long time ago and haven't tried to use generators for anything in
| a long time.
| Sparkyte wrote:
| Maybe I'm part of the hate train, but the natural progress is
| either Python to Java or Python to Go. Ruby is alright, but not
| scalable unless you're trying to be a startup where you can just
| drop in a bunch of gems and make it work.
| gls2ro wrote:
| How big is big enough for you to think that Ruby is scalable:
|
| Gitlab size? GitHub size? AirBnB size? Shopify size?
|
| I think this is a kind of fallacy to take into consideration
| scalability as the main metric when choosing the programming
| language unless you are truly doing a project which has as main
| selling point scalability.
|
| I would also say that that 90% (it is just a guess based) of
| projects started with any of these languages including Python,
| Java, Ruby, Go, Elixir, Javascript will not reach a level where
| scalability is truly a problem.
|
| There are some things to think about when choosing a
| programming language like:
|
| 1. It is a language which has by default solutions to problems
| I would encounter. See for example choosing Python over Ruby as
| there are more AI libraries in Python than Ruby, or choosing
| Elixir over Ruby if serving multiple concurent connections will
| be at the core)
|
| 2. How quick do you want to iterate the product? How many
| libraries in your specific domain are available? ...
|
| 3. Availability of developers in your area or with a specific
| domain knowledge if needed
|
| 4. How much the infrastructure cost to serve some users per
| month
|
| Also I agree with you - even if you presented it as a negative
| trait - that if I want to just spin up a quick prototype, I
| will almost always choose Rails. I have by default a lot of
| gems providing a lot of functionalities out of the box: user
| management, payments, activity feeds, image/video/audio upload
| and processing, .... Add there some TailwindUI/Bootstrap/Bulma
| screens and already I can have a good looking prototype to test
| an idea without too much work.
| Sparkyte wrote:
| Coming from operations perspective Ruby is only scalable
| because it is great for protyping. It as a deliverable can be
| challenging due to size and complexity involved with
| compiling gems. I'm sure it has gotten easier to deliver with
| Kubernetes but Java and Go are much easier to ship and
| deploy.
| gls2ro wrote:
| In my case I managed to scale Rails (a Ruby web framework)
| both vertically (of course this is always simple with
| resource lakes like some cloud providers are offering) and
| horizontally.
|
| For example I can spin up as much pods on GCP or dynos on
| Heroku without doing almost any changes in my Rails app.
| Things are even more simpler if I would use Sinatra.
|
| Of course I am not asserting here that Ruby is a fit for
| all types of web apps. But what I disagree with your
| statement that sounds like a very general conclusion
| stating that Ruby is not scalable.
|
| So talking specifically about programming languages and not
| frameworks, how do you scale Java or Go without operational
| overhead?
| codeduck wrote:
| Sorry, this is false.
|
| I'm a Java developer who became a Ruby developer who became
| an SRE.
|
| Ruby is far easier to deploy and use than Java or (in my
| limited experience) Golang, specifically because it has a
| single, mature, sane packaging system i.e. bundler.
|
| Only Elixir + hex come close to the ease of packaging with
| bundler. The only time you need to compile gems is if
| you're shipping ones with native extensions - and there are
| enough alternatives available that you shouldn't need to
| use gems with native C extensions.
|
| If you're running ruby on a modern web server - like puma -
| it will scale far further than your architecture will
| likely permit without redesign.
| Sparkyte wrote:
| I worked with both Java and Ruby. It's easier to ship
| Java than Ruby. I've even used puma and unicorn. But when
| you compare it to just getting a springboot build with
| Java it's kind of pointless. Puma doesn't even matter
| anymore especially in a K8S cluster unless the
| application is big and tall.
| codeduck wrote:
| The biggest issue with Rails is remembering how to DISABLE
| everything you don't want. API-based Rails is really nice.
| masklinn wrote:
| > the natural progress is either Python to Java
|
| Python to java is not progression, it's criminal malfeasance.
| TameAntelope wrote:
| You'd be laughed out of every dev shop I've ever worked at
| for suggesting that switch.
| [deleted]
| DarkWiiPlayer wrote:
| > I Miss: Generators
|
| Excuse me, what?! Python has stackless symmetric coroutines; Ruby
| comes along and brings you stackful symmetric coroutines, what's
| there to miss?
|
| How is `return to_enum :each unless block` not infinitely nicer
| than pythons yield? (and waaay more powerful as a little cherry
| on top)
| pmontra wrote:
| No idea what that statement does and I'm a rubyist. Anyway I
| think that Enumerator::Lazy is what one should use in most
| cases. Quite syntax heavy compared to Python though, but what
| is not in the very core of a language is always more verbose.
|
| https://ruby-doc.org/core-3.0.0/Enumerator/Lazy.html
| inopinatus wrote:
| Hopefully you do actually recognise what that does: the
| standard library has many methods documented to return an
| enumerator if no block is given. This is how.
| orf wrote:
| Genuinely no idea what your statement does, but "yield 123" or
| "yield from xyz" is infinitely more clear.
| regularfry wrote:
| I think nestling somewhere in the core of the GP's comment is
| that Ruby also has `yield`, and using it (or related
| functionality) is pervasive.
| inopinatus wrote:
| That's the idiom for an enumeration method called without a
| block. It's why you can chain them, as in ary.map.with_index
| and so forth.
| vidarh wrote:
| It creates an Enumerator (for practical purposes consider it
| a continuation) over the method "each" unless you've passed
| in an argument named "block", which by convention would be a
| block argument (a closure passed after the argument list
| effectively) that will evaluate to Proc object.
|
| Consider this example: class Foo
| def each yield(42) yield(:hello)
| yield(:world) end end
|
| You can now do: Foo.new.to_enum(:each)
|
| (In this case the ":each" is redundant - to_enum defaults to
| :each, so you could do Foo.new.to_enum) And you will get back
| an Enumerator that supports the Enumerable interface, giving
| you methods like map, inject, to_a (to array) etc. as a
| facade to the original object so you can do (for example:
| Foo.new.to_enum.to_a => [42, :hello, :world]
|
| (you can achieve a similar thing by including Enumerable into
| the class in question, but to_enum lets you do this on
| arbitrary objects without messing around with the class)
|
| It allows any method to return something that behaves like a
| collection class without needing to pass more than a method
| that implements "each" by yielding each value of the
| collection.
|
| You can also do this with a bare lambda, btw., but the syntax
| is a bit awkward as "yield" is syntactic sugar over calling
| the "call" method over an implicit (unnamed) block argument
| that isn't quite as transparent as you might like:
| ob = ->(&block) { block.call(42); block.call(:hello);
| block.call(:world) }.to_enum(:call)
|
| The explicit "&block" argument is needed because "yield" has
| lexical scope where defined. "ob" is now an enumerator that
| supporters each, to_a, map etc.: ob.to_a
| => [42, :hello, :world]
| DarkWiiPlayer wrote:
| > for practical purposes consider it a continuation
|
| But it's not though. While coroutines can be used to
| implement one-shot continuations and vice versa, they are
| very different features and behave completely differently.
| vidarh wrote:
| While you're technically right, in terms of using it as a
| shortcut to give superficial understanding of how
| Enumerator functions the distinctions are largely
| irrelevant.
| DarkWiiPlayer wrote:
| > "yield 123" or "yield from xyz" is infinitely more clear
|
| It's funny because the first is exactly how you write this in
| ruby.
| diarrhea wrote:
| Generators aren't only used in the context of coroutines.
| inopinatus wrote:
| The article misleadingly states "Ruby 3.0 has Fibers". Not
| false, but Ruby _2.0_ had fibers, they are used for building
| a variety of cooperative concurrency behaviours, and they go
| back years.
| DarkWiiPlayer wrote:
| But generators literally are coroutines though
| mparramon wrote:
| I would love to find an MVC framework with the extense library
| support of Rails but for Javascript. Being able to simply `gem
| 'devise'` and have full authentication at your hands (including
| views, email templates, etc) is a godsend.
|
| At many places where I've worked at this paradigm is exactly what
| is needed, but people know and want to use JS. If we choose to go
| that way then, we need to write a lot of glue code and think
| about setting up architecture standards, folder and naming
| conventions, ORM support, authentication, job queues, etc; all of
| this comes by default with Rails and makes life so much easier.
|
| Lately some options seem to be gaining steam:
|
| * RedwoodJS [1]: by Tom Preston-Werner (founder of Github), comes
| with React and GraphQL baked in, pretty new project though
|
| * Next.js [2]: brings a lot of nice conventions to the frontend,
| but still some things missing on the backend side
|
| * SailsJS [3]: similar to Rails, but small community
|
| * Strapi [4], hapi [5] et al: plenty of backend functionality,
| but frontend agnostic.
|
| Has anybody had experience with any of these (or others) and can
| report back?
|
| [1] - https://redwoodjs.com/
|
| [2] - https://nextjs.org/
|
| [3] - https://sailsjs.com/
|
| [4] - https://strapi.io/
|
| [5] - https://hapi.dev/
| busk07 wrote:
| https://adonisjs.com <- feels the closest to rails but with
| nodejs instead
| illiriath wrote:
| I've had good experience using Nest.JS [1] for the backend and
| next.js for the frontend. Nest has defaults for ORM,
| validation, task scheduling, authentication, etc, I find it a
| lot easier to work with when compared to plain express projects
| with tons of glue.
|
| Disclaimer: I have never tried Rails
|
| [1] https://nestjs.com/
| claudiojulio wrote:
| https://adonisjs.com
| twic wrote:
| It's crazy to me that every other language doesn't have a
| Rails, literally just a clone. It has been so obviously
| valuable, and is now so well understood.
| pjmlp wrote:
| We were using AOLServer and its clones, Vignette, Zope before
| Rails was invented.
|
| Most of the Java and .NET world eventually moved into full
| blown CMS, with component frameworks, graphical layout
| editors, user management and third party connectors to
| distributed platforms.
|
| For me, Rails just feels too little, with a quite slow
| language.
| berkes wrote:
| > Being able to simply `gem 'devise'` and have full
| authentication at your hands (including views, email templates,
| etc) is a godsend.
|
| I'm on the other end of the spectrum.
|
| I mean, I see the benefit of such "roll X quick by adding a
| gem" for a proof of concept, a demo or even an MVC.
|
| But even for the latter, it breaks down rapidly. Such gems
| bring their own libraries. They force you to "do it their way".
| A gem like devise comes with its own addons (omniauth) that
| paint you even quicker into a corner.
|
| I've consulted and developed at many Rails codebase where most
| of the problems were caused by years of development on top of
| such gems. A weak, wobbly base, and the entire castle built on
| top of that. Where 90% of the devise features were unused or
| worked around. Where more code was present to customise and
| work around devise-isms, AASM-weirdness, CanCan-troubles, than
| a complete "roll your own from scratch" would have ever caused.
|
| So, my "devise advise" always is: sure, use it to get your
| product to market rapidly. But be sure to pull it out and
| replace it with a focused, fitting architecture, sooner rather
| than later.
|
| Such gems fit very well in the problematic development-workflow
| where one "searches for an existing gem which offers some
| version of Foo" rather than "figuring out how we want to do
| Foo, then look for a gem that does it our way".
| alejandro_del wrote:
| Check https://blitzjs.com/
| AlphaWeaver wrote:
| I've used 1, 2, and 4.
|
| Strapi [4] is much less stable than it looks. It's pretty
| buggy, isn't designed very well, and is just all around messy.
| I used it in production once and never will again.
|
| Next.js [2] is well designed and well maintained, but is
| _totally_ a frontend framework. It 's not just "missing a few
| things" on the backend side, there _is_ no backend side. Works
| great if you already have an API (JAMStack) but can 't stand
| alone in the way that Rails or Laravel might be able to.
|
| Despite being the youngest, RedwoodJS [1] is the best option
| here. I was actually involved pretty early on as a contributor
| on the project before I had to step away (unrelated, personal
| reasons) and was seriously impressed by their progress. TPW
| runs a tight ship, and all of the devs that work on the project
| are smart and amazing. They ship _very_ quickly and it 's
| rapidly growing into the closest thing to a Rails competitor in
| Javascript. I wouldn't be surprised if it takes the JS world by
| storm in a year or two.
| patates wrote:
| https://nestjs.com/ is nice, although I'm not sure about your
| front-end expectations.
| cjohnson318 wrote:
| I like using Django [0] with django-rest-framework [1] and dj-
| rest-auth [2]. This gets you Rails-inspired organization, with
| a nice abstraction for REST endpoints in general, and a
| login/logout/registration/email verification/social auth in
| particular. Everything is built on the very well maintained
| allauth [3] module. Here's a good quickstart [4]. I've used
| Ruby in production, but I like Python's "one way of doing
| things" approach.
|
| [0] - https://www.djangoproject.com/ [1] - https://www.django-
| rest-framework.org/ [2] - https://dj-rest-
| auth.readthedocs.io/en/latest/index.html [3] - https://django-
| allauth.readthedocs.io/en/latest/overview.htm... [4] -
| https://www.rootstrap.com/blog/registration-and-authenticati...
| gls2ro wrote:
| I don't have a recommendation, but here is a quick test I use
| when looking for a replacement for Rails:
|
| How quick can I create a minimum simple SaaS like application
| that has the following functionalities:
|
| - User management (Signup/Login/Reset password/Confirmation...)
|
| - Simple payment processing (like have on single plan, one time
| fee, and after paying the user will see some other default page
| then not paying)
|
| - Sending emails in HTML and plain text (for user management
| and after payment is processed)
|
| - Use slugged/permalink like URLs
|
| - Processing some task in async queue
|
| And so far there are a lot of alternatives out there but for me
| Rails provides the easiest experience to have this up and
| running quickly as there are already battle-tested gems for
| almost all these functionalities.
| andi999 wrote:
| Just wondering: is rails transactional? What I mean how are
| race conditions avoided if e. g. two requests are processed
| at the same time.
| ryanbrunner wrote:
| Rails has support for database transactions, but depending
| on what you want to do, things won't be magically
| transactional all by themselves (that said, it's really
| just a matter of wrapping transactional code in a block).
|
| There are a couple of footguns in Rails involving
| transactions - by default, validating uniqueness of a
| column in Rails will be done at the app layer which is
| prone to race conditions, and with a typical CRUD model for
| an application, there's nothing in particular preventing
| overwrites of model data if say, a field was present on a
| form and two people were editing it, even if the "later"
| editor made no changes to that particular field.
|
| When multiple systems and things other than databases (or
| multiple databases) are involved, you're generally on your
| own as well.
| pbowyer wrote:
| It's important to say that these footguns are not
| exclusive to Rails. I don't know a single web framework
| which avoids them - if it exists I'd like to know so I
| can learn how they do it.
| RickSullivan wrote:
| Elixir's Ecto has the best solution to this problem in my
| experience--it doesn't do database validations on things
| like uniqueness, but provides easy error handling when
| constraints are hit.
|
| It encourages you to write validations for things that
| don't need to hit the DB, but actually uses the DB for
| handling constraints, since it's the real source of truth
| for data consistency.
| andi999 wrote:
| Thanks. So typical CRUD applications usually run on luck?
| (or on not beeing unlucky if the average number of
| request per hour is not so high).
| msbarnett wrote:
| Not really?
|
| Most CRUD applications go the one extra step of putting a
| uniqueness constraint on the column instead of just
| relying on the app layer validation, and comparing hidden
| time stamps on the form if multiple people might edit the
| same row simultaneously. It's exceedingly
| straightforward.
|
| Did you really think thousands of web apps up to and
| including GitHub and shopify were squeaking by on blind
| luck? If basic database transactions and integrity
| weren't doable in Rails, that'd have had to have been
| fixed years ago.
| andi999 wrote:
| Any reference how this is best done with rails?
| msbarnett wrote:
| Depends on what you mean by "this", I suppose.
|
| Most of your basic questions are covered by the starter
| Rails Guide for ActiveRecord, eg) transactions,
| validations, how to add a uniqueness constraint to the DB
| in a migration
|
| https://guides.rubyonrails.org/active_record_basics.html
|
| The ActiveRecord Queries guide also covers some
| mechanisms for handling row-level write contention
|
| https://guides.rubyonrails.org/active_record_querying.htm
| l#l...
| brightball wrote:
| It's got a very helpful transaction block wrapper structure
| that allows you to do some really interesting things with
| database transactions.
|
| To be perfectly honest, one of the things that I always
| love about Rails is exactly how good the database layer is
| with ActiveRecord. I like using the advanced features of my
| database, especially PostgreSQL, and I've found that it's
| really easy to do with Rails.
|
| The "Scopes" feature of active record that let's you save
| small snippets of a query and chain them together is
| fantastic. I've used it to create complex filter functions
| that combine full text search and coordinate distance as
| simple methods that can be attached to any query.
|
| Being able to add a
| `.search("fender").distance_from(location, 5, :miles)` onto
| any query made my life so much easier.
|
| It's been a few years, but I wrote about some of my
| favorites.
|
| https://www.brightball.com/articles/rails-gems-to-unlock-
| adv...
| gls2ro wrote:
| In case of DB:
|
| I think ActiveRecord has #lock (see https://api.rubyonrails
| .org/classes/ActiveRecord/Locking/Opt...) and that together
| with #transaction (https://api.rubyonrails.org/classes/Acti
| veRecord/Transaction...) are enough for my use cases.
|
| In case of file uploads to cloud providers: there I don't
| know for specific method to help with race conditions when
| transforming the resources.
| nickjj wrote:
| I think this is a very uphill battle because JS and most other
| languages are missing a key ingredient that Rails has had for
| 10+ years. It's having a long running large real world SAAS app
| that Rails is extracted from.
|
| That means every feature of Rails is derived from a practical
| use case, and then it's well tested in that app before it's
| pushed out to the community. That's tested from an API and
| functionality point of view. By the time end users start to use
| it, the feature has likely been running in production for 6+
| months. Maybe even serving billions of page views through it if
| you factor in Shopify and GitHub also running off Rails master.
|
| It's all constantly in use, kept up to date and iterated on as
| a whole.
|
| Creating a starter kit / boiler plate of libraries and opinions
| in another language / web framework is not the same. I think
| this is why even after 10+ years there's only a handful of
| popular Rails alternatives in other languages.
| ivanhoe wrote:
| There's plenty of JS code written out of the real needs and
| by teams running huge projects, take just Facebook as an
| example. Problem is that there are thousands of solutions for
| everything, and that saturation and bad signal to noise ratio
| makes it hard sometimes to pick the right lib (or even hear
| about it in the first place). Rails have a huge benefit of
| having a single team behind it, so you always have a
| recommended way of doing things, and the whole community is
| smaller, less libs to test, less rubbish to avoid, making the
| experience much more linear...
| pjmlp wrote:
| > OOP all the way.
|
| Python is surely OOP all the way, just ask the type system about
| what are the metaclasses of every, single, basic, data type.
| [deleted]
| azangru wrote:
| Ah, but can you call a method on a primitive such as number in
| Python? :-) 30.downto(0) do |n| print a[n]
| end
| maxnoe wrote:
| There are no primitives in python.
|
| Everything is an object with methods.
| pjmlp wrote:
| Since you didn't specify which method exactly,
| print((30).to_bytes(2, byteorder='big'))
| divtiwari wrote:
| I'm currently learning Ruby as I plan to implement the 'Building
| Git' book by James Coglan. Ruby has lot many features/syntactic
| sugar and a weird OO model. Makes me feel quite uncomfortable.
| Also, no good IDE except RubyMine.
| cies wrote:
| The OO model is very clean. Pretty much Smalltalk's model.
| Wanna see weird OO, look at JS or even Python (both were not OO
| in first version).
|
| IDE's will never be really good for Ruby (or any dynamically
| typed language), as it can only infer so much from the code.
| Type hints in the new Ruby version will maybe change this, but
| it is an addon so it may take a while to materialize.
|
| Ruby is --to me-- a dynamically typed language with very clean
| OO, expressive syntax, and as much FP goodness as possible
| without sacrificing it's OO essence.
|
| There is a language that has --in my view-- similar
| fundamentals, except that it is strict/strongly typed. This
| language is Kotlin: very clean OO, expressive syntax, and as
| much FP goodness as possible without sacrificing it's OO
| essence. IntelliJ is a great IDE for Kotlin and IDEs for Kotlin
| can actually deliver (due to the static typing). And yes,
| Kotlin is null-safe (no reason to carry this mistake around in
| static typed langs any further).
|
| Oh and the syntactic sugar in Ruby is called cocaine. :)
| pansa2 wrote:
| > _JS or even Python (both were not OO in first version)_
|
| This isn't true for Python. Quoting Guido: "classes were
| added late during Python's first year of development at CWI,
| though well before the first public release" [0].
|
| I don't think it's true for JS either - the first versions
| didn't have classes, but I believe they already had
| prototype-based OO.
|
| [0] http://python-history.blogspot.com/2009/02/adding-
| support-fo...
| cies wrote:
| I dont consider prototypes-based OO a real OO.
|
| I thought Python had classes in the first release but not
| to be used by library/application makers. But I was
| probably mistaken. Thanks for setting that straight.
|
| What I think is rather unidiomatic in OO langs is Pythons
| "function/methods" like `len(x)` which is implemented as
| `__length__()`. This is just weird/unintuitive/unidiomatic
| OO... I do not know what Guido was on when he thought this
| was a good idea.
| pansa2 wrote:
| > _I dont consider prototypes-based OO a real OO._
|
| Fair enough - in some ways it's like half-way to an OO
| system. Yes, it's theoretically elegant to have
| everything be an object and not to have classes. However
| in the vast majority of cases it seems prototype-based OO
| is only used as a foundation upon which to build a class
| system.
|
| > _What I think is rather unidiomatic in OO langs is
| Pythons "function/methods" like `len(x)` which is
| implemented as `__length__()`._
|
| AFAIK the reason for the double-underscore method for
| `len` is to avoid it accidentally working for, say, a
| Rectangle class that exposes `length` and `width`.
| Toutouxc wrote:
| Could you elaborate on the "weird OO model"? Is it the thing
| with eigenclasses?
| pansa2 wrote:
| > _it seems that Ruby is determined to keep adding syntax._
|
| With async/await, type hints, the walrus operator and now pattern
| matching, Python's designers are just as guilty of this as
| Ruby's.
| jspash wrote:
| It really feels like Ruby has reached the point of featuritis.
| Nobody wants to admit that the language is "done". Well, done
| enough.
|
| None of the recent changes in Ruby are game changers for me
| except maybe for pattern matching. But since I've fully drunk
| the Elixir cool-aid, I won't be needed that now. Sorry Ruby. It
| was a great ride, and I'll miss you. But it's time for me to
| move on.
| Ancapistani wrote:
| I've used both, but am a long-time Python user and prefer it
| for most things. It's my "first and true love of languages".
|
| In Python, "format-strings" were a welcome improvement and
| "f-strings" took me a bit to warm up to but are worth it. The
| walrus operator, though? Sure, it's useful in a narrow case,
| but it's extremely narrow. Likewise the pattern marching
| stuff - it just doesn't feel like "Python" to me, and doesn't
| seem to solve any real-world problem that's remotely close to
| justifying the cognitive overhead.
| [deleted]
| pansa2 wrote:
| I'm still amazed at how Python code with type hints looks so
| different to Python without them. Consider the program near the
| end of the article - if we remove the type hints:
|
| - The `AstNode` abstract base class becomes completely
| unnecessary and can be deleted.
|
| - We can't use `@dataclass` and are forced to write an `__init__`
| method for each class. But this is good, right? "Explicit is
| better than implicit"?
|
| - It's then clear that our three classes each have two methods,
| one of which is __init__. Following the advice from "Stop Writing
| Classes" [0], each class should just be a function (in this case,
| a generator).
|
| These changes reduce the code above "AST representation of the
| Ruby statement" from 70 lines to 40, and simplify some of those
| lines as well - for example, `arg_reprs = (arg.representations()
| for arg in self.args or [])` becomes simply `arg_reprs = args`.
|
| The knock-on effects of type annotations seem to be doubling the
| complexity of the code. This is why I'm far from convinced that
| static type checking in Python is worth the cost.
|
| [0] https://www.youtube.com/watch?v=o9pEzgHorH0
| josteink wrote:
| > The knock-on effects of type annotations seem to be doubling
| the complexity of the code. This is why I'm far from convinced
| that static type checking in Python is worth the cost.
|
| If adding types to your existing code cannot be done without
| creating new types, that's a sign that your type-system lacks
| expressiveness.
|
| For instance C# 1 lacked a good and general way to describe
| types for functions (that is, decoupled from a class).
|
| In later C# revisions this have been greatly improved, making
| this pain go away, and accepting type-safe function-value
| parameters as easy as any other parameter.
|
| I'm guessing Python is lagging quite a bit in that regard. That
| doesn't mean that type-safety (when done right) isn't massively
| useful and worth the effort 200 times over.
| masklinn wrote:
| I think you're missing their point. They're not necessarily
| saying types are not useful, they're saying types encourages
| a form of thinking which tends towards class-based
| overcomplications.
|
| Now this is also a very yagni view, and maybe TFAA uses the
| structure they build for inspection before the final
| consumption, but their point that the posted script could be
| half the size and work the same without loss of
| readability... is compelling.
| nerdponx wrote:
| The sentiment in Python has been trending toward using
| "classes" (either with dataclass or attrs [1]) as
| essentially namespaced logicless containers, and away from
| dicts (or TypedDicts [2]) and namedtuples/NamedTuples [3])
| for that purpose.
|
| This is very different from the useless "you don't need
| classes" classes.
|
| Think of classes nowadays serving as being both traditional
| OO classes and Scala case classes: they can be types of
| stateful objects, or just dumb record types, which you now
| can easily extend with logic if you do happen to need it
| (and you often do, in nontrivial applications).
|
| _Now this is also a very yagni view, and maybe TFAA uses
| the structure they build for inspection before the final
| consumption, but their point that the posted script could
| be half the size and work the same without loss of
| readability... is compelling._
|
| I find this "it would be half as long and work the same"
| argument totally bizarre. Do you look at a basic Rust
| program and say "it would be half as messy and look the
| same if we did away with all the move/borrow stuff"?
|
| Of course not. Yes, YAGNI is fine and all, but at least
| recognize that there is an upside to this tradeoff. I am
| not going to need internal state on the object, but I sure
| as hell am going to want the type checking.
|
| And yes, you could (perhaps should) use Typescript or
| Kotlin for new web server projects instead. But some people
| _want_ Python with types, including me.
|
| I wouldn't accuse Python of being "concise" in the first
| place, so I am happy to add some up-front verbosity in
| exchange for a much smoother developer experience later in
| my project, with significantly less mental overhead as my
| IDE can do a lot of work for me.
|
| 1: https://www.attrs.org/en/stable/
|
| 2: https://docs.python.org/3/library/typing.html#typing.Typ
| edDi...
|
| 3: https://docs.python.org/3/library/collections.html#colle
| ctio... and https://docs.python.org/3/library/typing.html#t
| yping.NamedTu...
| masklinn wrote:
| > The sentiment in Python has been trending toward using
| "classes" (either with dataclass or attrs [1])
|
| That is not even remotely what is being done here. And
| just because you _can_ do it doesn 't mean you _should_
| or _must_ do it. Not every argument needs to be bundled
| in its own object.
|
| > I find this "it would be half as long and work the
| same" argument totally bizarre. Do you look at a basic
| Rust program and say "it would be half as messy and look
| the same if we did away with all the move/borrow stuff"?
|
| Again, missing the point. The point was not about the
| typechecking but the design perspective it engenders and
| encourages.
|
| > Of course not. Yes, YAGNI is fine and all, but at least
| recognize that there is an upside to this tradeoff.
|
| Why would I? There is none in the article's snippet.
|
| > I am not going to need internal state on the object,
| but I sure as hell am going to want the type checking.
|
| Type checking doesn't require classes. You can typecheck
| a funtion.
|
| > I wouldn't accuse Python of being "concise" in the
| first place, so I am happy to add some up-front verbosity
| in exchange for a much smoother developer experience
| later in my project, with significantly less mental
| overhead as my IDE can do a lot of work for me.
|
| You're still missing the point entirely, but I hope
| you're happy about all those strawmen you beat down.
| nerdponx wrote:
| _Type checking doesn 't require classes. You can
| typecheck a funtion._
|
| I am not talking about type checking a function. I am
| talking about type checking data structures, replacing
| this: record = { "a": 1, "b": 2 }
|
| with this: record = Record(a=1, b=2)
|
| The latter can be statically type checked much more
| easily than the former.
|
| This is analogous to Scala case classes and Haskell
| records. And it can be a significant productivity
| improvement in big codebases, because it reduces your
| reliance on documentation and working memory. Instead, it
| lets you depend on the type checker and IDE to provide
| auto-completion to prevent a whole class of errors.
|
| In fact, it lets you write _less_ runtime code and have
| shorter, simpler code paths in some cases, because you
| can mostly skip checks like "if x is an int" (at least
| in internal/private functions), and even skip the
| corresponding unit tests, and let the type checker deal
| with it instead.
|
| Edit: as for the example in the article... really? This
| is just single dispatch. What would you write instead,
| one big if/else statement? A dict of functions to
| dispatch to? `AstNode` is an interface, and all of these
| subclasses implement the interface.
| foxes wrote:
| Then types in python are not expressive enough. Other
| languages most of the time you don't even necessarily need
| to specify the types - the compiler/interpreter can figure
| it out and still type check the program.
| nerdponx wrote:
| Maybe, maybe not. Data classes are meant to feel like
| Scala's case classes, or Haskell's record syntax.
|
| The point is to stop passing around dicts/hashmaps/tables
| and tuples, and to start passing around containers with a
| known and fixed set of keys/names, and known types for
| the values/elements.
|
| I don't know of any language where the compiler
| automatically infers field names and types, and I don't
| know if I would ever want such a thing.
| josteink wrote:
| > I don't know of any language where the compiler
| automatically infers field names and types, and I don't
| know if I would ever want such a thing.
|
| Typescript can do this to unannotated code, and it's
| surprisingly useful.
| nerdponx wrote:
| I've already started checking out Typescript as a
| general-purpose scripting language to replace Python,
| Ruby, Perl, and to some extent Bash/Zsh. Knowing this
| makes it even more attractive. The only annoyances I've
| had so far are: difficult-to-search docs, and the `tsc`
| command-line compiler is slow.
| sportsracer wrote:
| Typing in Python is, at times, at odds with the language.
|
| Said another way, patterns which were once Pythonic are now
| discouraged by the type system (or, mypy I suppose, since
| the door is still open for other type checkers to emerge).
| Examples:
|
| - Duck typing. I was taught that's Pythonic, yet mypy
| really only accepts nominal subtyping i.e. it infers type
| compatibility from the type hierarchy. There is
| typing.Protocol, but I've never encountered that in code.
| Ruby's new type annotation language RBS introduces
| interfaces, so maybe it'll stay true to its duck typing
| roots.
|
| - Lists with mixed types. I might as well bookmark this
| page, because this behavior of mypy always surprises me: ht
| tps://mypy.readthedocs.io/en/stable/common_issues.html#inv.
| ..
|
| In both cases, mypy reminds me way more of Java's type
| system than of Python's, which is maybe why heavily typed
| Python code often ends up looking like Java.
| gjulianm wrote:
| I don't think you can draw that conclusion from that code. I've
| been using Python for a few years and moved some projects to
| static type checking. It's not always worth the cost in every
| situation, of course. For such a simple script I wouldn't add
| type checking. But for larger projects where there are multiple
| classes and a lot of types, I've found that the effort is very
| much worth it: a lot of type errors are found faster (some of
| them hadn't been caught by unit testing), the code becomes more
| consistent and easier to read, and developing is easier if you
| have an IDE that understands the types and gives you
| appropriate suggestions.
|
| It's also worth it in plumbing code, as it tends to be more
| difficult to test properly and having types will catch a whole
| class of errors.
|
| The fact that the program at the bottom of the article can be
| simplified is not an issue of typing, in fact static typing
| could also be used in your approach without any issue.
| viraptor wrote:
| > We can't use `@dataclass` and are forced to write an
| `__init__` method for each class. But this is good, right?
| "Explicit is better than implicit"?
|
| I don't think that applies. @dataclass is an explicit wrapper
| with specific result and can be inspected and interacted with.
|
| Implicit would be something like "when a class has more than
| one attribute and doesn't define init or repr, add those
| methods assuming they're wanted/needed".
|
| > The `AstNode` abstract base class becomes completely
| unnecessary and can be deleted.
|
| You're mixing type annotations with class usage. You can have
| either one without the other.
|
| For example without AstNode you could still have an
| `Expr|MethodCall|Lambda` type hint on relevant variables. Or
| even alias that expression to AstNode without having the class
| and inheritance.
|
| Finally, classes help describe/construct the structure present
| in "stmt". Sure, you could replace those with functions, but
| that means your data is either reduced to untyped / string-
| tagged lists/dicts, or you're running the exact function
| immediately inline. It doesn't matter for this toy example, but
| it would in any practical use of this code.
| mumblemumble wrote:
| @dataclass is one of those ones where it depends on how you
| look at it.
|
| To me, it's a great application of "explicit is better than
| implicit." Because @dataclass explicitly indicates that this
| class has a certain set of behavior, and is meant to be used
| a certain way. If you manually implement exactly the same
| behavior, you've made the individual statements more
| explicit, but you've made the class's actual function
| implicit. A reader needs to figure it out by reading the code
| and working backward from there. An editor of the code needs
| to recognize the intended behavior and not break it,
| otherwise the understanding that other people built up by
| reading the code may break in subtle ways.
| jasfi wrote:
| If you're going to use Python with types, then you may as well
| use Nim. Nim has a similar syntax, but with better performance.
| You can also interface with Python code via Nimpy.
| nerdponx wrote:
| I like Nim, but I don't think there is much of anything
| similar between these languages _except_ the syntax.
| craftinator wrote:
| I love using Nim, it's an absolute pleasure after years of
| fighting with Python's "I hope I got this right..." style of
| type guessing. The biggest problem most people run into is
| that python has a huge library ecosystem.
|
| I haven't actually tried interfacing them, it's next on my
| docket. Have you found any good learning resources for that?
| BiteCode_dev wrote:
| It's more of a design issue here. You could do away from
| classes while keeping types by using 2 lines:
| Expr = Callable[[str]], Callable[..., Iterable[str]]]
| AstNode = Expr | Callable[... ], Callable[..., Iterable[str]]]
|
| You then don't event need to call representation() , as each
| node is callable. No classes needed.
|
| But my instinct tells me a node will eventually have more
| attributes and methods. Stuff to get parents, children,
| siblings, filtering and so on.
|
| So it's probably a good call to use a class for later. Also you
| want to keep AstNode as a node could be an expression, a
| statement or a comment.
| rkangel wrote:
| To echo the sibling - I believe the most important skill a
| developer can have is reworking* code, and doing it readily
| and continuously. If you can do that, then you don't ever
| have to worry about what code MIGHT do in the future, you
| just design it for what it DOES do and then rework it later
| if necessary. It's quicker and the result is cleaner. Also,
| even if you tried your hardest to predict what was going to
| be needed, there are always things you can't predict and at
| that point you either rework your code or have bad code.
|
| * I could have said refactoring, but some people have very
| specific definitions attached to that word so I use something
| more general here
| mumblemumble wrote:
| > But my instinct tells me a node will eventually have more
| attributes and methods.
|
| I couldn't agree more with Jack Diederich's thoughts on that
| sentiment, at 5:40 in the video linked above:
|
| https://youtu.be/o9pEzgHorH0?t=340
|
| > _Lots of times people think they might need something
| later. You don 't. Or, you can just do it later._
| BiteCode_dev wrote:
| https://docs.python.org/3/library/ast.html
|
| Experience helps with this.
|
| It's a middle path as usual: naive approaches waste as much
| time as overenginering.
| nerdponx wrote:
| YAGNI is a double-edged sword. It's almost no additional
| overhead to write a class now if you think you might need
| to add methods and internal state later. But it's quite a
| bit of refactoring work if you decide _not_ to write a
| class and then realize you need one after the fact.
|
| Maybe this is an indictment of Python as a language, but I
| think it's a case of not taking anything too literally.
| YAGNI fetishism is like DRY fetishism all over again.
| mumblemumble wrote:
| I guess? At the same time, working in some languages
| (Racket and C come to mind), it's surprising how long I
| can go without anything ever becoming a class, ever.
| Seemingly indefinitely.
|
| My sense is that many of us are taught to think in terms
| of classes and methods instead of data structures and
| functions, so it's very easy to slip into thinking you
| need objects in order to manage state, even when that's
| not the simplest way to do it.
|
| Strictly speaking, though, I think that the only time in
| Python that you really have no workable alternative to
| classes is when you also need dynamic dispatch.
| Groxx wrote:
| Personally: it's much easier to YAGNI in languages with
| relatively-strong compile-time checks and relatively-
| sophisticated tooling.
|
| If you lose either of those, a little bit of preventative
| work goes a loooooong way. The friction (largely due to
| risk) of correcting things later is so large that it just
| starts snowballing unless your team is _extremely_
| attentive.
|
| In that sense, Python is... middle of the road, with an
| extremely broad spread. Tooling is an odd blend of
| surprisingly good vs incapable of doing simple inference
| / detecting all calls, and type safety depends heavily on
| how you write things (some tactics are fairly strong,
| many are so weak as to be useless or make things much
| worse).
| mumblemumble wrote:
| Personally:
|
| Python has a great type checker. Several, actually.
| They're not compile time, because Python doesn't exactly
| have a compile time, but still. 11/10, use everywhere,
| spend less time on unit tests, be happy.
|
| Second, to me, these sorts of concerns about refactoring
| are. . . maybe not a smell, but worrisome, all the same.
| They suggest poorly-modularized designs with no internal
| component boundaries that might have limited the scope of
| impact of such a change. That said, even if you do find
| yourself needing to do a far-reaching refactor like that,
| refactoring in a dynamic codebase isn't necessarily
| harder, it's just different. Not being able to lean on
| the compiler to help you boil the ocean, for example, is
| fine, because boiling the ocean wasn't necessarily the
| best approach in the first place. The strangler pattern
| will help here, not just with type errors, but also with
| other problems the compiler might not have been able to
| help with, and will also be friendlier to the people who
| do your code reviews.
| RangerScience wrote:
| I'm real confused about generators, but maybe I'm just missing
| something about what Python generators do?
|
| Like, aren't generators just a block of code that can produce the
| next value, wrapped in an iterator?
|
| 'Cuz like, sure, they're not front in center in Ruby programming
| and if they are in Python I can see why one language would teach
| you the other, but ruby blocks and yield make this pretty damn
| easy. So what am I missing?
| mjfisher wrote:
| Not a huge amount in concrete terms of capability, but the
| little differences add up in how the languages are used in
| practice. Python has functions you can refer to and pass around
| directly (as opposed to Ruby's blocks and procs), and that
| makes functions that are also generators just a little bit more
| friction free and more likely to be used. Python also has
| explicit support for differentiating between generators and
| regular functions in other parts of it's syntax (e.g. list
| comprehensions) that tend to make people reach for generators
| more quickly in Python than other languages.
| nerdponx wrote:
| Python's lazy, composable iterators/generators are my #1
| favorite Python language feature and I find myself missing it
| in every other language I use.
| [deleted]
| jcheng wrote:
| Ruby has generators, but it's not just blocks and yield.
| Generators are special because after they've yielded (in the
| Python sense) a value, they are actually no longer on the stack
| at all. But when their next value is requested, they pick up
| execution right where they left off with all local variables
| intact, as if they had never suspended execution.
|
| If that doesn't make sense to you, it's definitely worth taking
| a few minutes out to look up some tutorials on generators in
| Python or any other language that supports them.
|
| In Ruby, you can create a generator by passing a block to
| Enumerator#new; the block's parameter is a "yielder" object
| whose << operator behaves like Python's yield keyword. Keep in
| mind that this block is NOT invoked once per iteration; it is
| invoked once, it might suspend and resume though (or suspend
| and not resume!).
|
| https://stackoverflow.com/questions/35826375/enumerator-new-...
| jonnytran wrote:
| Yes, the difference is between internal iterators vs.
| external iterators.
|
| For external iterators, Ruby's Enumerator syntax is a little
| less convenient. But I think it's a small price to pay for
| not having 2 colors of functions [1]. In Ruby, an Enumerator
| is just an object with methods like any other object. And
| there are things built in to core to make working with lazy
| enumerators just as easy as regular ones.
|
| To GP's point, you rarely actually need enumerators because
| of a block's ability to break in Ruby. It's the most useful
| thing that's in so few languages [2]. For example, if I
| really wanted to write an infinite loop yielding fib -- not
| saying I would do it this way but -- the quickest way to do
| it in Ruby, adapting the Stack Overflow example, is probably
| more like this: def fib() puts
| "Enter function" a = b = 1 i = 0
| loop do puts "Inside loop" yield i, a
| puts "i: #{i}, a: #{a}, b: #{b}" a, b = b, a + b
| i += 1 end end
| n_from_user_input = 5 fib do |i, x| break
| if i == n_from_user_input puts "Use value: #{x}"
| end
|
| 1: https://journal.stuffwithstuff.com/2015/02/01/what-color-
| is-...
|
| 2: https://yehudakatz.com/2012/01/10/javascript-needs-blocks/
| blunte wrote:
| Thankfully it is not "OOP all the way".
|
| You can write a lot of functional, or at least simply procedural
| (modular) without ever needing to define your own class. Sure,
| you'll be instantiating some objects implicitly or explicitly at
| times, but you don't have to make your code be class-based.
|
| You can write pure functions, although you do have to be aware of
| space/time concerns depending on the situation. Obviously you
| want to avoid copying huge data structures frequently. But in a
| lot of cases, the performance cost is low, so you can respect
| your inputs by not mutating them and just return some
| value/collection.
| buf wrote:
| I still think Ruby on Rails is the #1 choice for launching a non-
| static web project in 2021. The lifecycle I've seen with the 4
| companies I've built on Rails:
|
| 1. Pure vanilla rails
|
| 2. Then the front end grows into an SPA as functionality becomes
| more complex
|
| 3. More microservices like search emerge into their own thing
|
| By step 3, I've got a dozen engineers and things are still as
| smooth as one engineer. Engineering is never the bottleneck. The
| tech debt is super easy to spot. It's incredibly simple to know
| how to scale horizontally to many more engineers.
|
| Rails can get you very very far. It's not sexy, but it gets me to
| market faster than anything out there.
| andy_ppp wrote:
| Phoenix and Elixir, if you understand the choices made, is also
| a wonderful choice. The whole contexts thing allows you to
| design your system as micro services before you even add in
| network partitions, and Ecto, not being an ORM (it's a DSL for
| writing SQL in elixir) is incredible, better than it's possible
| to be in active record. Then there's channels, presence,
| performance etc.
| syspec wrote:
| I want to use this, but every time I try to switch it is not
| there yet.
|
| Sure it has the core stuff, but things like ActionMailer and
| the built in S3 support in ActiveStorage, Devise, keep me
| going back. Too many of those things are not there... yet ...
| with Phoenix
|
| Also nothing beats active record after they added Arel which
| turns your query into an AST and optimizes it before sending
| it out - Ecto is not really there yet and doesn't look like
| it will get there
| andy_ppp wrote:
| I think most packages are just as good if not better than
| their Ruby equivalents these days (and really we can do all
| of the things you mentioned in elixir), the active record
| pattern is obviously easier to understand than Ecto, but if
| you know how to write well optimised SQL you never want to
| go back to an ORM (even if it tries to rewrite your queries
| for you with magic).
| anhari wrote:
| ActiveRecord and the scenic gem for database views is a
| very powerful combination. For the vast majority of
| queries, you don't need to write optimized SQL by hand.
|
| When you do, you can push that work to a version
| controlled database view, and wrap it in a ActiveRecord
| class interface and still get all of the ORM niceties.
| andy_ppp wrote:
| The difference here is between two different
| philosophies:
|
| 1) I want the thing to just work, it doesn't matter about
| underlying complexity, give me the magic and the version
| controlled database view and the ORM niceties.
|
| 2) I don't trust magic as it has bitten me in the arse
| before, let me make and be able to fix things myself as
| I'm working at a (slightly) lower level.
|
| I'm not saying either is correct but in Phoenix/Elixir
| magic stuff tends to be kept to a minimum. One man's "for
| the vast majority of queries" is another persons "will
| work fine until you need to do something complex".
|
| It's all trade offs and I choose mine carefully as I'm
| sure you do.
| realusername wrote:
| What I love with Phoenix/Elixir is that there's plenty of
| features I wouldn't bother with in a more traditional
| language. You want a PubSub? It's just there, you can use it
| in one line. You want a Redis? Registry is there, also in one
| line. It's like a whole tech stack in one language.
| jamil7 wrote:
| I saw a presentation about Phoenix/Elixir that described
| this once and it really impressed me. It's been 4 or so
| years that I've been meaning to pick up Phoenix for a
| project but still haven't.
| vram22 wrote:
| Was it a publicly available online presentation? If,
| please share title or link if you can.
| [deleted]
| realusername wrote:
| You can watch this small twitter demo:
| https://www.youtube.com/watch?v=MZvmYaFkNJI. It's what
| sold me on the technology.
| vram22 wrote:
| Thank you.
| jamil7 wrote:
| It was a slide from a conference I believe but I wouldn't
| know how to find it - some elixir people in the thread
| might know. It was something illustrating all the
| external tools you'd normally reach for and how
| Erlang/Elixir just had all of this built into the
| language and VM.
| sfusato wrote:
| That sounds like one of Sasa Juric's talks:
|
| GOTO 2019 * The Soul of Erlang and Elixir * Sasa Juric
|
| https://www.youtube.com/watch?v=JvBT4XBdoUE
|
| https://elixirforum.com/uploads/default/original/2X/a/a25
| 24c... (also from Sasa Juric's book)
| [deleted]
| jamil7 wrote:
| Thats it! Thank you!
| nickjj wrote:
| > Phoenix and Elixir, if you understand the choices made, is
| also a wonderful choice
|
| Until you want to integrate Stripe, BrainTree, PayPal or
| Paddle and then you can't find an official library for
| Elixir. Even common things like pagination isn't fully solved
| with well maintained libraries in Elixir. Then there's things
| like wanting to create notifications (showing unread icons,
| sending email / sns notifications, etc.) where Rails has this
| solved with well supported gems but there's nothing like this
| in Elixir. There's dozens of other things too, and all of
| this adds up to you having to be an expert in Elixir and have
| to write all of this functionality from scratch.
|
| If you're in it for the long haul and are investing years
| into the project with a big team that's fine, but if you're a
| solo developer building an app and want to focus on writing
| your app's features, in my opinion Phoenix can't really be
| compared to Rails. Especially with Hotwire Turbo being
| recently announced, now you can get all of those awesome real
| time features into a Rails app quite easily (IMO even easier
| than Live View).
|
| I started a SAAS app with Phoenix a while back and ultimately
| backed out of it because all of the above. It just felt like
| I was in a perpetual state of having to write libraries
| instead of my application. It was one of those things where I
| got super excited initially but after 2 years of trying,
| waiting for certain features to come, etc. it ended up not
| being for me.
|
| I have no regrets tho because I learned a really big lesson
| from that, which is if you want to build something now, use
| whatever tech stack you know right now and if you're very
| much inclined to deviate from that and learn something new
| then pick something based on what exists today in that tech
| stack. Not what's "on the horizon" or "coming soon". You
| could set yourself up for waiting forever, or if that feature
| you wanted in the future does come out it could be way
| different than what was originally claimed and now it feels
| like you've delayed what you wanted to do for so long for
| nothing.
|
| In other words, nothing is ever going to be perfect. Just
| pick something and build it. Also trust what folks do, not
| what they say.
| andy_ppp wrote:
| I think often the things that don't exist are not there for
| good reasons... using Stripe's api for example from a
| module is pretty trivial in my experience, it's just HTTP
| and you don't need to be super clever about it. What
| specifically are you struggling to paginate, I've never
| found that difficult to implement even with quite clever
| options. Again notifications doesn't sound particularly
| difficult and I don't see why I'd want to rely on some
| complex gem that does every option when I don't need them.
| Elixir+Phoenix I agree is definitely lower level but there
| are things it does much better than Ruby on Rails. I'd
| rather not have to rely on gems and write the simplest
| implementation specifically for my use case. It's a trade
| off, but one I'm much happier with.
| nickjj wrote:
| > I think often the things that don't exist are not there
| for good reasons... using Stripe's api for example from a
| module is pretty trivial in my experience, it's just HTTP
| and you don't need to be super clever about it.
|
| It's way more involved than inserting an auth token
| header into an HTTP request and calling some API
| endpoint.
|
| For example, what about verifying webhooks? The official
| libraries for Stripe (Python, Ruby, Node, PHP, Go, JS,
| etc.) deal with this for you.
|
| But with Elixir, you're on your own. This is very low
| level code to have to deal with and it's extremely
| important you get it right.
|
| You're left having to parse Stripe's specification on
| this and then implement the code yourself in Elixir. It's
| so tricky and involved that the Dashbit company (the
| creator of Elixir and members of the core team work
| there) wrote a blog post on it at
| https://dashbit.co/blog/how-we-verify-webhooks.
|
| But before a few months ago that blog post didn't exist.
| Also this isn't the only thing you'll have to do yourself
| when it comes to interacting with Stripe using Elixir.
|
| Then you'll have to do similar things for other payment
| providers all which are different in a lot of ways, but
| with Rails you have the combination of having official
| Ruby clients from those payment providers and even the
| Pay gem which lets you support payments from multiple
| providers. That could easily be a few months of dev time
| just for that abstraction alone if you had to go about
| that from scratch and your implementation wouldn't have
| any track record until you start using it and ironing out
| the bugs from real world experience.
|
| > Again notifications doesn't sound particularly
| difficult and I don't see why I'd want to rely on some
| complex gem that does every option when I don't need them
|
| Don't take this the wrong way but this seems to be the
| mindset of almost everyone I chatted with when it comes
| to Elixir. When someone asks how to do something, the
| answer is it's trivial or easy to implement but there's
| rarely any examples posted on how to do it and almost
| never in a production ready way.
|
| In my mind trivial or easy means I can sit down in maybe
| a few hours or a day and write a production ready
| solution, complete with tests and have it work exactly
| how I want without running into any major roadblocks.
|
| I'd be curious to see how you would implement
| https://github.com/excid3/noticed or
| https://github.com/pay-rails/pay in Elixir / Phoenix.
| Based on your responses of saying these things are easy
| I'm guessing you've written large apps with Phoenix where
| you've developed features like this in a production app?
| It would be fantastic if you could post some code
| examples or a blog post on how you went about this. Not
| just to answer my specific question but I'm sure the
| community would appreciate having concrete examples of
| how it's done. This way maybe more folks would use the
| framework.
| andy_ppp wrote:
| Here's some links by way of an answer for you...
|
| Stripe, including webhooks support, actively developed:
| https://github.com/code-corps/stripity_stripe
|
| Global pay solution that supports everything: they are
| all a bit crap you're right, the best I've found is
| https://github.com/aviabird/gringotts and ex_money really
| is amazing that integrates with it (I would suggest it's
| better than the equivalent ruby gem). To be fair I'm not
| sure I'd want to use the pay gem with anything complex as
| you need to be able to use the specific quirks of each
| API properly (for example it can't pause subscriptions on
| stripe right now even though stripe supports this:
| https://github.com/pay-
| rails/pay/blob/master/lib/pay/stripe/...).
|
| You're also right about noticed, after looking into it
| more it would be worth building for elixir for sure.
| Ravenx represents a start but it's unmaintained and
| doesn't have a huge number of strategies. It depends on
| how much I needed to do notifications like this. For the
| apps that I've built we've just needed database and
| grouped emails sent once per day, no need for texts or
| slack etc. There's no reason these couldn't be added
| fairly simply but I agree noticed is very neat. It's
| definitely a few weeks work to roll your own from scratch
| so to be honest I'd probably just integrate with Twilio
| and just pay for someone else to handle this for me.
| nickjj wrote:
| Thanks.
|
| > Stripe, including webhooks support, actively developed
|
| I've looked into Stripity Stripe. For some time it was
| unmaintained and ended up getting taken over by another
| maintainer. It's also not as comprehensive as the
| official Stripe libraries. There's also a very big
| difference in using an official Stripe library and hoping
| for the best with a random one someone developed. Just
| skimming the code base it looks like the Checkout module
| is missing features that exist in the official Stripe
| library in every other supported language.
|
| According to the README file for Stripity Stripe it's
| also using Stripe's API version from 2019. There have
| been multiple major API updates since then, and there's
| been an open issue since November 2020 to add support for
| newer API versions with no replies. Personally I would be
| using one of those major features too.
|
| And this really is the point I'm trying to drive home.
| With Ruby, Python, Go, PHP, Node, Java and .NET these are
| problems you don't even need to think about. You just
| pick the payment provider's official SDK and start coding
| immediately, often times there's also an abundance of
| resources to implement the billing code itself into your
| app too through blog posts, official docs, YouTube
| videos, and even paid products like
| https://spark.laravel.com/. Stuff that makes integrating
| billing into your app (through Stripe, BrainTree and
| Paddle) being something you get done in 1 day instead of
| 3 months.
|
| With Elixir it becomes weeks of comprehensive research,
| evaluating questionable libraries, opening PRs, and
| becoming a full time library developer just to get to the
| point where you could even maybe begin to start accepting
| payments with just Stripe. Then you have to repeat the
| whole process for PayPal, BrainTree and / or Paddle.
| These are all super popular payment providers.
|
| > the best I've found is
| https://github.com/aviabird/gringotts
|
| I asked the Gringotts developers if they would be
| supporting PayPal about 5 hours after they announced the
| project ~3 years ago. He said it was coming and to stay
| tuned. It's now ~3 years later and PayPal support isn't
| there. Neither is BrainTree or Paddle. Here's the open
| issue for PayPal support from 2018 (not by me, I asked on
| another site)
| https://github.com/aviabird/gringotts/issues/114. The
| Stripe integration is also missing a ton and hasn't been
| touched since 2018.
|
| By the way, the Pay gem is really good. It's a smart
| abstraction and supports a ton of different subscription
| / 1 off payment use cases. Even complex ones like the
| type of app I was building.
|
| > It's definitely a few weeks work to roll your own from
| scratch so to be honest I'd probably just integrate with
| Twilio and just pay for someone else to handle this for
| me.
|
| Twilio ends up being 1 potential delivery method, it's
| not really someone you pay to solve the problem for you.
|
| There's wanting to show notification in the app over
| websockets, saving them into a database, emailing them
| out only if they are unread, maybe sending a text through
| Twilio, Slack and other providers.
|
| The noticed gem handles all of this for you (and supports
| Twilio too).
|
| Notifications in general is another example where other
| frameworks have this solved in very good ways, but it
| becomes another example where you have to stop developing
| your app and start developing a notification library with
| Elixir.
|
| At this point we've only talked about payments and
| notifications too. There's lots of other examples and
| these are all things to think about if your goal is to
| build an application.
|
| Now don't get me wrong, it still takes a lot of custom
| code and effort to build an app with Rails but the time
| spent isn't on low level generic problems. It's letting
| you focus on your custom application's business logic.
|
| And with less opinionated frameworks like Flask you can
| lean on Python being extremely well supported by almost
| every service that offers an SDK and also being able to
| find solutions to nearly every web dev problem online in
| some form or another (blog posts, youtube, stack
| overflow, etc.). That goes a long ways because at least
| you're not starting from scratch.
| tomduncalf wrote:
| This is interesting - I'm evaluating what to use for the
| backend of a side project right now after having focussed more
| on front-end/apps for the last few years, so I'm catching up on
| the latest progress in this area. Interesting to see that Rails
| is still often considered the best option, seems like they must
| be doing something right.
|
| Ideally in my mind, I'd like to use JS (well, TS), because it's
| the language I work in most of the time these days... but I can
| pick up different languages as needed, that's not a major issue
| and I've done some Ruby way back when - more important to me is
| making the development of the backend as simple and as quick as
| possible (the less code the better!), and that it will run
| reliably. A good selection of well-tested off-the-shelf modules
| to integrate other services is always a good thing. It seems
| like Rails is well ahead of any JS-based solution in these
| regards, from what I can see.
|
| I had been evaluating PostGraphile, which appeals because of
| the "low-code" approach, but I guess I'm a little concerned
| about doing most of the logic in database functions, because
| I've worked on a system like that years ago and found things
| like testing and debugging pretty painful. If anyone has
| evaulated PostGraphile (or Hasura etc) vs. Rails, I'd love to
| hear your thoughts (or equally, any other solutions to check
| out!).
| capableweb wrote:
| Replace Rails with any framework and you'll have the same
| amount of success. I've done the exact same thing across Ruby,
| PHP, Golang and tons of other frameworks/languages and never
| had problems if following those steps.
|
| Problems usually come from when teams start with point 3 and 2
| when in reality they should stay lean in the beginning.
| TylerE wrote:
| I have seen multiple PHP projects at multiple companies go
| totally pear-shaped. I refuse to work at companies that use
| PHP anymore.
| capableweb wrote:
| Ok? I've seen projects go pear-shaped in probably 20
| different programming languages at this point, and I guess
| after a certain point of experience, you start to realize
| that failures seldom have to do with the programming
| languages themselves, and more about the people actually
| writing and managing the code (and everything else around
| development).
| TylerE wrote:
| When the 3 biggest disaster projects I've ever had to
| touch were all in PHP, I choose to believe that is a
| trend.
|
| Yes, you can write bad code in any language.
|
| PHP makes it hard NOT to. The footguns are all sitting
| out in the open, loaded, and the safeties are off.
| capableweb wrote:
| Well, if you're not interested in factual discussions and
| you simply "choose to believe trends", all bets are off
| since you're pulling in your feelings about a programming
| language into your arguments. Hope all goes well for you
| anyway!
|
| As a counter-point, the biggest disasters I've seen has
| been involving either C++ or C#, but in the end I think
| the reason they were disasters were because of the
| programmers (and their ego) rather than the programming
| languages they worked with. C++ is plenty open and
| without safeties, but that doesn't mean you have to use
| it like that.
| herbst wrote:
| Same experience. I've seen nice PHP projects on the
| internet, but not yet in RL. I know its possible, i guess
| its just that the language does not reward you for
| corecctness.
| jake_morrison wrote:
| PHP used to actually have a performance penalty for
| splitting things up into modules and using object
| oriented design, because it meant loading and parsing
| more files on each request.
|
| I was trying to do that on my first big PHP project, and
| got called out. The architect said "your code is too
| good, PHP is for writing crap". If you are going to write
| good code, you should do it in another language.
|
| Symfony framework is a great example of a PHP framework
| written by Java programmers. All the same verbosity as
| Java, but without the benefits of Java.
| brightball wrote:
| This argument has been made for years and I've yet to see it
| really be true.
|
| You can make anything with anything, but Rails is the closest
| thing to an Aspect Oriented Programming environment that I've
| seen in the wild. It's not easy to replicate that type of
| ecosystem with any other framework. I've spent ample time on
| teams using Go, Python, PHP, Java and C#.
|
| There's just no comparison vs what a small Ruby team can
| accomplish.
| bestinterest wrote:
| I really agree with this statement. I've never seen
| anything like Rails in Java/JavaScript land. In Java
| there's SpringBoot but its nowhere near a cohesive package
| with a strong online community behind it like Rails. It's
| more of a collection of libs put together with a good
| enough attitude.
| bambataa wrote:
| What Rails-equivalent framework does Golang have?
| LordHeini wrote:
| I found Revel to be quite similar.
|
| I would say it is as productive as rails too.
|
| There is no ActiveRecord and way way less magic which slows
| (initial) development speed but it pays off in less bugs to
| fix.
|
| So overall development speed is roughly the same. But with
| Go you gain co routines and nice performance which can make
| life a lot easier.
|
| Gos type system, while somewhat poor, still helps too.
|
| The worst part of the whole thing is actually the weird Go
| std lib templating.
| enumjorge wrote:
| Same question but for Node.
| capableweb wrote:
| NodeJS has even more "batteries included" frameworks than
| Golang. Sails, Hapi, NestJS and Adonis are probably the
| most popular right now, at least from my viewpoint
| (traditional SaaS web development)
| capableweb wrote:
| I myself usually opt for the "small set of composable
| libraries" way of development nowadays, but I've worked
| with teams using Buffalo and Beego which kind of provides a
| "batteries included" approach that Rails follows too.
| regularfry wrote:
| Constant factors vary framework to framework. I can't say
| I've ever seen anyone move as fast on Symfony as on Rails.
| capableweb wrote:
| Symfony might be the odd one of the bunch.
|
| Jokes aside, Laravel is probably a more likely contender to
| provide a Rails experience for PHP.
|
| And it's easy to get clouded by anecdotal data, I'm sure
| I'm biased as well. But after some time in the industry, it
| matters more who the person is that is using the framework,
| than the framework itself. If you put an equally skilled
| developer with the same amount of experience in either
| Symfony or Rails, I'm sure they can be as productive as the
| other. But then skilled developers tend to gravitate
| towards Rails more than Symfony, so hard to judge in the
| real world.
| sedeki wrote:
| I've thought about learning Ruby (and Rails), being a Python
| guy myself. Not sure if it is worth the effort.
| Toutouxc wrote:
| Give it a try. The languages are actually very similar, on
| roughly the same level of "high-low-level-ness", they both
| come batteries included, both are very mature and refined.
|
| Some things you may find nice are: the elegance and ease of
| use of blocks instead of explicitly declared lambdas, the
| standard library full of nicely named methods with aliases
| for the most used ones (e.g. array.map / array.collect do the
| same thing, some people hate this), the {do,if,case,loop}-end
| syntax that doesn't fuck your code up when you just quickly
| comment these parts out without reindenting the rest, and if
| you're into OOP (as in OOP, not explosion-in-the-factory-
| factory), then of course the hardcore OOP-ness.
| syspec wrote:
| Try rails you won't look back
| mehphp wrote:
| I tried (am trying at current company) and still don't like
| it. I completely see the appeal for getting an idea up and
| running quickly, it absolutely delivers on that.
|
| However, I joined a rails company about a year ago and the
| codebase is just a mess at this scale. I find it annoying
| that in order to know where dependencies are coming from, I
| can't just go to the top of the file and see what's
| imported. I have to know how rails injects it and then
| track it down from there.
|
| Don't get me wrong, there are issues with django, Go, etc..
| but for the most part I can jump in and track down what's
| happening much easier.
|
| Now, Ruby itself isn't bad at all.
| awj wrote:
| Yeah, that's gotta be one of my biggest complaints after
| years of working with Rails. Eventually you sort-of
| memorize the conventions and can relatively accurately
| guess where a file lives, until someone decides to get
| clever and put things in a weird place.
|
| It also implicitly discourages you from asking yourself
| if you _should_ be accessing the thing you are. IMO a lot
| of the tight coupling in Rails codebases begins with
| being able to grab literally anything and use it with no
| one the wiser unless the read that specific line of code.
| vidarh wrote:
| I love Ruby. Have used it for 15 years, but I can't stand
| Rails. It's just massive overkill for most things.
|
| I prefer to start with Sinatra + Sequel for web projects.
| Sometimes Padrino + Sequel. As your project grows, sure,
| you'll pull in many things similar to Rails, and probably
| pull in some projects that started out with Rails too,
| but it allows you to be much explicit about which
| dependencies you pull in, why, and to limited where it
| gets pulled in.
| cies wrote:
| I'd go with something that has static/strong types when
| picking a new tool. Not saying I dont love Ruby, but when the
| codebase/team becomes large i've always regretted not having
| strong type safety. Today I'd go with Kotlin.
| forgingahead wrote:
| Depends on your use-cases - Rails is unparalleled in my
| opinion for getting a web app up and running, testing startup
| ideas, and building quickly in response to customer feedback.
|
| I've been using Python recently as I've done more ML work,
| and I do prefer Ruby (though the syntaxes are similar and you
| won't have any difficulty).
|
| If you learn for Rails, start with 5.2.3, webpacker and the
| other black magic in Rails 6 will frustrate you and currently
| eliminates a lot of the ease of getting started and building
| relatively sophisticated web apps quickly.
| 20thCB wrote:
| "If you learn for Rails, start with 5.2.3, webpacker and
| the other black magic in Rails 6 will frustrate you and
| currently eliminates a lot of the ease of getting started
| and building relatively sophisticated web apps quickly."
|
| Anyone else agree with this?
| buf wrote:
| Nope.
|
| Webpacker is here to stay and takes about 5 minutes to
| understand. Might as well take advantage of it so you can
| quickly add complex front end components via React or
| whatever flavor of FE you prefer.
| adsteel_ wrote:
| I agree, webpacker makes learning Rails harder. It's a
| shame. But learning on a non current version is a short
| term solution. A good tutorial, like Michael Hartl's,
| will get you over that hump.
| zeku wrote:
| I'm learning rails 6 right now for a work project, and
| webpacker is just broken so I can't even get hello world
| to work.
| pjmlp wrote:
| I don't know, I just keep using Java and .NET, alongside their
| top of the line JIT/AOT compilers and GC implementations.
| flippinburgers wrote:
| The single most important part of rails is the orm. That,
| honestly, seems to be the "thing" that people are actually
| getting excited about when they gush over rails. A close second
| would be rspec. Finally, in production, it can be very helpful
| to be able to inspect the state of the db using the rails
| console (as long as the developer is wise enough to sandbox
| their actions).
|
| I find the tooling (I use vim with solargraph) to be not only
| underwhelming but frustrating. No I am not willing to use a
| different editor. Rails code is not at all discoverable because
| I cannot follow method calls/objects half of the time. In order
| to alleviate this, I suppose, rails tries very hard to
| standardize things. Ok. Fine.
|
| The ease of creating model relationships can also produce a
| nasty set of relationships between tables if you are not
| careful. One project I worked on used the fast_jsonapi where
| one table had relationships spanning most of the system.
| Dealing with that was borderline ridiculous.
| dialamac wrote:
| > the language designs are so similar that I could just as well
| imagine a world where Python is the web development lingua
| franca, and Ruby has all the machine learning libraries.
|
| Well aside from the startling implication that Ruby is a web
| development "lingua Franca."...the latter statement is
| reasonable, as it turns out language design isn't actually that
| important here. But the former is pretty far off the mark. I
| mean, Ruby doesn't even have first class functions and is very
| strongly smalltalkish in its OO purism, it has mutable strings
| a-la Perl. The async story is obviously quite different. Python
| has a much more complicated interpreter, which has contributed to
| it being more difficult to get even simple optimizations that are
| done in Ruby. They're really only similar in the most superficial
| sense... in the same way that all current dynamic interpreted
| languages will do certain things similarly.
| viraptor wrote:
| > it has mutable strings a-la Perl
|
| Yes, but they're not popular these days. Ruby 3 almost
| defaulted to frozen-strings by default (I wish it had) and a
| lot of the ecosystem moved to that style resulting in some nice
| memory saving. Rails enforces it on its code for example:
| https://github.com/rails/rails/blob/0f09dfca363410f51f6f6078...
| mbell wrote:
| You are talking about string _literals_, there is no motion
| nor interest I'm aware of in making all strings immutable in
| Ruby, thankfully.
| capableweb wrote:
| While I agree with most of your comment, "Ruby doesn't even
| have first class functions" sticks out. Not because it's not
| true, but because it doesn't really matter as Procs are first
| class in Ruby and can be used in all the same ways, so it
| doesn't really affect things except introducing something you
| might need to learn coming from JS or any other language where
| functions are first class.
| jablan wrote:
| >it doesn't really matter
|
| This way can be argued for almost every language, as they
| almost all allow some form of passing a piece of code to a
| function. The difference is what matters - whether that's
| elegant enough to be widely used or not. Ruby is, frankly,
| somewhere in between.
| Toutouxc wrote:
| > Ruby doesn't even have first class functions
|
| I'm genuinely curious what is it that you miss in Ruby
| regarding functions (my Ruby is pretty OOPish and I try to
| avoid functional magic).
|
| AFAIK you can do stuff like currying/partial application with
| lambdas and you can get a hold of any method by name and use it
| as a lambda, pass it to other functions, use it as a block etc.
| Is there something we're missing compared to, say, JavaScript?
| pmontra wrote:
| You can pass a function as argument using its name in Python
| and then call it. You must pass it as a symbol in Ruby and
| then send to it. def f(x): return x
| def g(fn, x): return fn(x) g(f, 1)
| inopinatus wrote:
| Ruby can definitely pass around several varieties of
| closure and related constructs, including procs, blocks,
| lambdas, bindings, continuations, fibers, and both bound
| and unbound methods.
|
| Whether we should is another matter, and the syntax and
| idioms certainly lean towards preferring a symbolic late
| binding, but the language is multi-paradigm, and one may
| write purely functional Ruby if desired, immutable values
| and all.
| burlesona wrote:
| The exact same thing in Ruby: f = ->(x) {
| x } g = ->(fn, x) { fn[x] }
| g[f,1]
| jonnytran wrote:
| > You must pass it as a symbol in Ruby and then send to it.
|
| This is just plain wrong. You can absolutely do this, as
| other commenters have pointed out.
|
| It is common to see Ruby code passing around a symbol and
| sending it, but my guess as to why this pattern became
| common is that it can be serialized into plaintext like
| YAML, stored in a database, and called later, like for a
| background job in a web app. But there's no need to do it
| this way.
|
| Another reason you don't tend to see it this way in Ruby is
| because Ruby optimizes for the common case: calling methods
| [1]. Because of this, you can easily create very concise
| DSLs in Ruby, which would never be quite as clean in
| Python.
|
| 1: https://yehudakatz.com/2010/02/21/ruby-is-not-a-
| callable-ori...
| Toutouxc wrote:
| No, passing a symbol and sending is a different mechanism.
|
| The send method is basically the same thing as calling a
| method by name, as in "obj.foo" == "obj.send(:foo)". If you
| only pass a symbol into your "caller" method, the symbol
| goes through the normal lookup: does the receiving object
| respond to this? If yes, then that implementation (at that
| exact moment) is called, if not, you get a method_missing.
|
| You're right that that's not how you do first-class
| functions in ruby. Your example in ruby would be:
| def get_and_call(fn, x) fn.call(x) end
| def upcasinator_method(str) str.upcase end
| upcasinator_lambda = lambda {|str| str.upcase}
| get_and_call(upcasinator_lambda, 'foobar') =>
| "FOOBAR"
| get_and_call(method(:upcasinator_method), 'foobar')
| => "FOOBAR"
|
| As you can see there are two ways to do that -- either you
| create a Proc (a lambda if you care about arity), which is
| the first class function in Ruby, and you call that, or you
| define a method (but that's a method, an OOP concept, a
| procedure that implicitly operates on an object), you get a
| hold of it using the "method" method and then you call it.
| sedeki wrote:
| Wait, Ruby doesn't have first class functions?
|
| I'm pretty sure it does and that it is full-fledged, compared
| to Python's intentionally restricted `lambda` syntax.
| capableweb wrote:
| Nope! You can't pass functions, return them from other
| methods and so on, so it's not first class.
|
| What you do have in Ruby is Proc, that can be used kind of
| like first class functions, so you don't really miss out on
| much.
| burlesona wrote:
| This is incorrect. If you want an anonymous function (with
| no object context) you can write a lambda, store it to a
| value, make other lambdas that call it and return yet
| another lambda etc.
|
| You can do the exact same thing with object methods if you
| want, eg. my_puts = Kernel.method(:puts).
|
| You can even do this in one line and stop the auto
| parenthesis behavior if you want.
|
| foo = method def foo ... end
|
| Now foo will reference the method and you have to use foo()
| to call it.
|
| The reason that people don't do a bunch of that in Ruby is
| that the support for anonymous blocks is so convenient That
| there aren't that many cases where you really need
| anonymous functions detached from any object.
| syspec wrote:
| Ah, so it's the same then
| feanaro wrote:
| Not really the same since lambda in Python is just a
| syntactic variant that is restricted. A function defined
| using `def` can be passed around without restrictions,
| which you simply cannot do in Ruby.
| burlesona wrote:
| There is no practical difference in Python functions and
| Ruby lambdas.
| zwp wrote:
| What are the restrictions on passing around a ruby
| `Method` object?
|
| (Please see my sibling comment for example).
| zwp wrote:
| Maybe I misunderstand your use of the word "function" here,
| but in addition to Proc/lambda you can certainly reference
| a method, pass that around, return it, call it?
|
| See: * https://ruby-
| doc.org/core-2.7.1/Method.html * https://ruby-
| doc.org/core-2.7.1/Object.html#method-i-method
| $ irb 2.3.3 :001 > p = Kernel.method(:puts)
| => #<Method: Kernel.puts> 2.3.3 :002 > p.call
| "Hello" Hello => nil 2.3.3 :003 >
| def run_a_method(meth, *args) 2.3.3 :004?>
| meth.call(*args) 2.3.3 :005?> end =>
| :run_a_method 2.3.3 :006 > run_a_method(p, "Hi!")
| Hi! => nil 2.3.3 :007 >
|
| ps. this also works with the "to_proc" operator (&) so you
| can do this: [1, 2,
| 3].each(&Kernel.method(:puts))
|
| (but I wouldn't necessarily recommend this since it's not
| very readable, at least to my eyes).
| jablan wrote:
| Yeah, and that's not what first-class usually means. I
| simply can't say "my_method = Kernel.puts" the same way I
| can say "my_class = Kernel". Ruby authors seemed to want
| to make parentheses optional by any price, and that
| killed the opportunity to make methods first class.
| zwp wrote:
| So the functionality is equivalent, but ruby's syntax is
| slightly longer. Is there something other than ruby's
| slightly longer syntax that you find lacking?
|
| Ruby's Method class matches the design "everything is an
| object" (strings, numbers, modules & classes
| themselves...). Are regular expressions first class in
| ruby? There is top-level syntactic sugar for REs (/.../)
| but REs are just instances of `Regexp`. It's just syntax.
| I don't think you can argue Python REs are first class: I
| have to drag in `re` and then `re.compile()` a string
| (and my editor won't know to syntax highlight metachars
| or interpolation). I use REs more often than method
| references in either language...
|
| But I'm not trying to "whataboutwhataboutwhatabout" here.
| I'm trying to say there's a difference of philosophy.
| Ruby Methods are just more objects (Method<Object). They
| aren't some sort of blessed type, just like everything
| else. Everything descends from Object. Kernel is just a
| module. Method is just a class. And so on. In that
| context, what does it mean to be "first class"? Objects
| are first class and methods are objects. QED. No?
|
| (FWIW I agree on ruby's optional parens, too much
| inconsistency for a few less lit pixels on the screen).
| orf wrote:
| > and my editor won't know to syntax highlight metachars
| or interpolation
|
| IntelliJ editors do this and it's insanely useful. Works
| with SQL, regex and any other language.
| jablan wrote:
| Sorry, Ruby methods are not objects. They can surely be
| wrapped in objects of class Method, but this feature is
| used quite rarely. Whether this is due to the syntax, the
| confusion regarding the evaluation scope, or something
| else, I have no idea.
| zwp wrote:
| Yeah, you got it, see my reply to capableweb. Thanks!
| vidarh wrote:
| The only way of obtaining a method in Ruby returns it as
| an object. Whether or not they "are" an object is thus
| entirely an implementation detail.
|
| I think part of the confuction here is that "ob.puts" for
| example, does not directly access a method. It denotes
| passing a message to "ob". That message may or may not be
| routed to a "puts" method.
|
| But at _no point_ does Ruby allow you to get hold of any
| kind of reference to a method that is anything but an
| object.
| capableweb wrote:
| > In that context, what does it mean to be "first class"
|
| It's easy. First, what does it mean to be "first class"?
|
| > a first-class citizen (also type, object, entity, or
| value) in a given programming language is an entity which
| supports all the operations generally available to other
| entities. These operations typically include being passed
| as an argument, returned from a function, modified, and
| assigned to a variable
|
| Secondly, how does that apply to functions?
|
| > [First class functions] means the language supports
| passing functions as arguments to other functions,
| returning them as the values from other functions, and
| assigning them to variables or storing them in data
| structures
|
| In what way doesn't Ruby have first class functions? This
| article explains it better:
| https://codequizzes.wordpress.com/2014/05/19/ruby-
| methods-ar...
|
| In practice, because of Proc, you can use Proc wherever
| you'd use functions and get the same effect in practice,
| so this is not a big issue. But just because Ruby has
| Procs, doesn't mean it has first class functions, which
| is a specific concept.
| [deleted]
| zwp wrote:
| Thanks, I had also looked for a definition. Instances of
| Method fulfill both of the cited criteria -- example in
| this thread, above. Also: Method != Proc, but the
| article's comments help.
|
| I, aha, notice this: $ irb
| 2.3.3 :001 > Kernel.method(:puts).object_id =>
| 47165335040720 2.3.3 :002 >
| Kernel.method(:puts).object_id => 47165334993360
| # not the same! 2.3.3 :003 >
|
| That is: #method doesn't return _the_ Kernel.puts method,
| it returns _a new Kernel.puts Method instance on each
| invocation_. So the returned object from Kernel#method
| isn 't the "real" (first-class) method object.
|
| I feel like I'm a lot closer but I'd still like a better
| definition :)
| Toutouxc wrote:
| Isn't the main issue that Ruby simply doesn't have
| functions? Ruby is pure OOP, and there is no way to
| define a method without the implicit 'self' argument.
| Maybe the reasoning was that methods themselves aren't
| really meant to be passed around. Because in today's
| idiomatic Ruby, they mostly aren't -- the late binding
| behavior of 'send' feels much more Rubyish.
| indeyets wrote:
| Exactly. And Ruby methods are not even "functions with
| self" they are message-handlers. Like in Smalltalk or
| Objective-C.
|
| Different heritage
| burlesona wrote:
| You can literally write my_method = Kernel.method(:puts)
| and it'll give you the method to do what you wish with.
|
| As far as defining anonymous functions, literally all you
| have to do is:
|
| my_func = -> (input) { output }
|
| That makes a "lambda" which is literally just a function
| you can pass around anonymously.
|
| You call it via either my_func.call(input) or simply
| my_func[input].
|
| That capability is used all over the place.
|
| Ruby absolutely has first class functions, the only thing
| you can say is it the syntax is slightly different if you
| want to use them anonymously (with no object context).
| jablan wrote:
| > my_method = Kernel.method(:puts)
|
| That's exactly what being first-class is not. You tell
| Kernel to wrap you a method called puts into an object of
| class Method, rather than assigning a method to a
| variable. You don't have to look further than JS for a
| counterexample.
| inopinatus wrote:
| First-class refers to the existence of the language
| construct, not whether or not you like the syntax.
| jonnytran wrote:
| This is clearly a misunderstanding of what a first-class
| function is. If it doesn't meet your criteria in the
| colloquial sense of "first-class", that's one thing. But
| in computer science and programming in general, "first-
| class function" has a very specific meaning [1], and Ruby
| absolutely meets the criteria.
|
| The other bit of confusion is that the actual syntax in
| Ruby optimizes for the common case, which is calling
| methods. Yehuda Katz wrote a good article explaining this
| [2]. Among other things, it allows you to create very
| concise DSLs in Ruby that would never be quite as clean
| in Python.
|
| 1: https://en.wikipedia.org/wiki/First-class_function
|
| 2: https://yehudakatz.com/2010/02/21/ruby-is-not-a-
| callable-ori...
| burlesona wrote:
| Ruby has two kinds of functions. Methods are functions
| attached to objects. Lambdas are functions that are not.
| Ruby lambdas are exactly like javascript arrow functions,
| or python functions.
|
| There's a _reason_ for the difference, since it
| determines the implicit self, which is a meaningful part
| of how Ruby works. Because that difference is meaningful
| it is reflected in the syntax. `lambda`, or the shorthand
| `->` creates a function not attached to an object, and
| `def` creates a function and attaches it to the calling
| context (making it a method).
|
| Perhaps you don't like this, that's fine. But to say
| "nope I want my methods and functions to be
| interchangeable and not to be called lambdas" is purely a
| subjective argument.
|
| Objectively, Ruby has first-class functions that can be
| assigned to variables and passes around and returned from
| other function calls etc etc. They're just called
| lambdas.
| jaimex2 wrote:
| Ruby - a language that is made for developer happiness and
| productivity.
|
| It had me at happiness.
___________________________________________________________________
(page generated 2021-02-16 23:03 UTC)