thoughtbot is your expert design and development partner. We bring digital products from validation to success and teach you how because we care. It’s time to move beyond simply building features and start designing the right product with the right strategy.
Tech stack: Ruby, Rails, JavaScript, Stimulus, React, SQL, PostgreSQL, Heroku, AWS, Kubernetes (depends on the client)
Compensation:
For instance for “Ruby on Rails Developer” in Europe, Middle East, Africa it can vary between $57,000 and $125,000 USD depending on location and seniority.
Position(s): engineering, product, design, and many other roles (but selfishly I’m looking for platform product managers)
Location: US, Canada, Europe - remote or on-site
Description: GitHub is the largest code hosting and devops service in the world. We have dozens of roles open, but I’m looking for two platform product managers. One is for Data Storage – we build the SQL, NoSQL, blob storage, event queue, and job queue systems for the rest of the company. The other is for Git Systems – we build storage and access mechanisms for Git data, and we contribute much of what we build upstream so everyone can benefit from it. (Job posting hasn’t gone live yet, email me at my-lobsters-username@github.com if interested.)
Tech stack: Ruby on Rails, Go, Rust, C#, Kubernetes, Typescript
Compensation: competitive salary and stock grants, discretionary PTO (most people aim for 5-6 weeks), home office and communications stipends, remote-first culture
I agree, it sucks. We can’t even hire in all of Canada, only certain provinces. Because we’re independent of Microsoft, we can’t use all of their nexuses of business, employment tax setups, etc.
I understand the author’s concern that not naming the intermediate values can make understanding the intermediate types unclear, but I think not having the names increases readability. In most cases, the intermediate structures are defined by or near the function definitions that use them.
Unfortunately, Ruby doesn’t have first class support for functions - decode would be interpreted the same as decode() and would throw an ArgumentError. You would have to do something like message.then(&method(:decode)).then(&method(:process)).
I’ve seen people say this a lot, that Ruby doesn’t have first-class functions, but I think that is wrong. It’s just not syntactically as convenient as other languages, but you absolutely can pass methods around as objects.
I disagree with your argument because the entire Higher-Order Functions section confuses the difference between methods and functions. All of the problems you demonstrate come from that misunderstanding.
Next, let’s try to return functions from functions in Ruby:
def returns_function
def addition(a, b)
a + b
end
end
You are not instantiating a function here at all, you are metaprogramming the receiver of returns_function to add a new method. There are times where you’d actually want to do this, but they have nothing to do with higher-order functions.
function addBark (obj) {
obj.bark = function() { return "woof!" }
}
let dog = {};
let bark = addBark(dog);
bark() // Uncaught TypeError: bark is not a function
Huh, JavaScript sucks at methods doesn’t it? (No, this was intentionally wrong)
An apples-to-apples comparison looks a lot closer:
Currying in Ruby
addition = ->(x, y) { x + y }
add_two => addition.curry.(2)
add_two.(4) # => 6
Currying in JS
let addition = (x, y) => x + y
let addTwo = (y) => addition(2, y)
addTwo(4) // 6
Ruby makes different tradeoffs between methods and functions than JavaScript or other languages. JS is more comfortable for pure functions, but methods are janky. Ruby puts most of its ergonomic focus on methods, with implicit block arguments fullfilling the majority of first-class function use-cases, and native procs and lambdas filling in the blanks.
This leads to more complexity in some cases, it’s true. But some of the interactions between them is much richer than JS APIs can provide. For instance:
require 'markaby'
mab = Markaby::Builder.new
mab.html do
head { title "Boats.com" }
body do
h1 "Boats.com has great deals"
ul do
li "$49 for a canoe"
li "$39 for a raft"
li "$29 for a huge boot that floats and can fit 5 people"
end
end
end
I think you’re 1) making a strawman argument, and 2) ignoring the ergonomics of what first-class functions means. The fact that you have to think about any of this at all in order to use Ruby’s “first class functions” means you can only say it has them with a lot of disclaimers. As I put it in the post, “economy-class.” Note that I don’t deny that it technically has them!
JS is more comfortable for pure functions, but methods are janky. Ruby puts most of its ergonomic focus on methods
To be equally pedantic, we weren’t talking about methods, were we? We’re talking about first-class functions. Not first-class methods. Yes, the syntax you call out defines a method on the caller, but do programmers used to first-class functions want to think about that? No, they do not. In fact, the fact that the def keyword defines methods and not functions makes it even more clear that Ruby does not treat functions as first-class.
Yes, you can curry in Ruby. But it’s an incredibly uncommon pattern. Why? Because Ruby doesn’t treat functions as truly first class. You can do it - that was the whole point of my post - but it’s awkward. Wikipedia labels Ruby’s first-class functions support with the equivalent of asterisks:
The identifier of a regular “function” in Ruby (which is really a method) cannot be used as a value or passed. It must first be retrieved into a Method or Proc object to be used as first-class data. The syntax for calling such a function object differs from calling regular methods.
Nested method definitions do not actually nest the scope.
Also yes, you can do cool stuff with instance_eval, but that’s not what we’re talking about. We’re talking about language-level support for first-class functions.
I don’t understand how that could possibly be construed as a strawman, the entirety of my comments were responding to the substance of your blog post. Unless you are referring to the JS example, which I pointed out directly was intentionally wrong to make a point.
The fact that you have to think about any of this at all in order to use Ruby’s “first class functions” means you can only say it has them with a lot of disclaimers. As I put it in the post, “economy-class.” […]
To be equally pedantic, we weren’t talking about methods, were we? We’re talking about first-class functions.
Ruby has first-class functions without any disclaimers. My critique of your argument is that you keep confusing method invocation for function execution and these are two entirely different language concepts.
To be explicitly clear: when I say Ruby has first-class functions, I am speaking of Procs and Lambdas. Those are functions. Methods are not. Two different things.
Block arguments are Procs that are passed as arguments to methods. This is the most common way of using first-class functions in Ruby, and it’s one of the most unique language features.
This happened because the execution binding of a method is the receiver, not the lexical scope. This is important because calling a method is essentially just syntactic sugar for send
This can be confusing in a REPL specifically, because it’s not clear from the above what you are actually doing. In a Ruby REPL, main is an instance of Object, and calling def is actually defining a new method on the Object class.
Method objects exist so that you can translate the method interface into a function interface, but you’re really just bundling up the receiver together with a message to send
bark = dog.method(:bark)
bark.() # => "woof!"
Methods can’t be detached from their receiver
A method is a type of message that an object can respond to. You can’t separate them (well, you can but UnboundMethod is not executable until it is bound to something)
A Ruby method is an implementation of the OOP idea of data and behavior existing together in a single object.
All methods have an implicit block argument (a Proc), which is a first-class function. The syntax of Ruby allows you to seamlessly combine methods and functions into a single interface, but there is no requirement that you do so.
I was never really happy with any templating language, none of them ever really fit and customization was never satisfactory. I started using sh as templating language (https://mkws.sh/pp.html) and never been happier. Makes sense, UNIX tools are great for processing plain text and also for displaying plain text in as human friendly way as possible. Just use standard UNIX tools for processing and outputting the text in any kind of file types.
😬 One rough consensus about templating languages is that they shouldn’t be too capable*; you want your logic in the program, not the templates.
In light of that, a template language that’s capable enough to, say, upload all your data to Elbonia and then wipe your disk sounds particularly dangerous to me. What’s the pp equivalent of Johnny Tables?
Plus the fact that sh has IMHO the ugliest syntax for conditionals/loops outside of Brainf*ck…
* (I was going to say “they shouldn’t be Turing-complete”, but I wouldn’t be surprised if someone has figured out how to implement Towers of Hanoi in Liquid…)
It’s a matter of responsability, who’s responsible for the safety, the programmer or the language? You ilustrated that pretty good with the Johnny Tables example, who’s responsabile for that, SQL (or C# I believe?), or the programmer? Any programming language is “dangerous”, also, any programmer.
It’s as safe as any shell script you write locally and run.
Ugly is relative, I also found it ugly initally, now I find it elegant.
Logic is where you decide to put it, you could write separate programs for the logic the UNIX way and only use pp for presenting the output.
In the context of using it for generating static HTML files, I personally prefer to see it as progressive enhancement for the CLI. You’re already using the standard CLI tools to show data on your terminal, why not use the same CLI tools to show data on a web site, considering a web site is just a way to show off local data in a public way.
It’s as safe as any shell script you write locally and run.
If you use it only with local data, yes. But templates very often run on untrusted data from the network. If your templater doesn’t escape that properly, it’s open to injection attacks; and if the data that failed to be quoted is part of a shell command, you’ve got a full RCE.
logic is where you decide to put it.
Sure, but there’s a lot of evidence from experience that it’s better to separate logic from templates.
If you use it only with local data, yes. But templates very often run on untrusted data from the network. If your templater doesn’t escape that properly, it’s open to injection attacks; and if the data that failed to be quoted is part of a shell command, you’ve got a full RCE.
Agree, I’m only using locally for now. In case of untrusted data, just make sure you properly sanitize it, again matter of responsability.
Sure, but there’s a lot of evidence from experience that it’s better to separate logic from templates.
Agree again, it’s a good idea to develop separate programs for logic and only use pp for displaying the output.
I was going to say “they shouldn’t be Turing-complete”, but I wouldn’t be surprised if someone has figured out how to implement Towers of Hanoi in Liquid
Liquid is definitely turing complete. Being amazingly poorly designed and not fit for purpose doesn’t not prevent completeness ;)
That post is really really cool, I use PEG but would like to be able to create my own PEG one day (I tried different ones and previously used PEG.js which is quite powerful).
Of course, using children for literals is overly-verbose. It’s only necessary when nesting arrays or objects into objects; for example, the JSON object {"foo": [1, 2, {"bar": 3}], "baz":4} can be written in JiK as:
Description: Booking doctors online and providing an agenda for doctors (it’s much more than that ;) )
Tech stack: Ruby/Rails/Javascript/React for the monolith, then TypeScript for the Desktop App (Electron), React Native with a webview for native apps, PostgreSQL, AWS, Kubernetes, etc.
Contact: dorian.marie@doctolib.com, I will redirect you to the correct contact, I’m just a programmer there :)
I don’t think this is very on-topic. A blog post containing in-depth content on exactly what makes this change worthwhile would be much more appropriate for Lobsters.
I’m pretty happy about this pull request I made on rails
Company: thoughtbot
Company site: https://thoughtbot.com
Position(s): https://thoughtbot.com/jobs
Americas
Europe, Middle East, Africa
Location: REMOTE
Description:
thoughtbot is your expert design and development partner. We bring digital products from validation to success and teach you how because we care. It’s time to move beyond simply building features and start designing the right product with the right strategy.
Tech stack: Ruby, Rails, JavaScript, Stimulus, React, SQL, PostgreSQL, Heroku, AWS, Kubernetes (depends on the client)
Compensation:
For instance for “Ruby on Rails Developer” in Europe, Middle East, Africa it can vary between $57,000 and $125,000 USD depending on location and seniority.
Equity would probably be for very senior roles
All the benefits are on https://benefits.thoughtbot.com
For instance for France, I have 25 days of vacation and 10 days of sick leave paid from the first day (not common in France).
Contact: dorian@thoughtbot.com
Hope it helps, it’s a pretty awesome company, you can read more about how we work https://thoughtbot.com/playbook and our values https://thoughtbot.com/purpose
Thanks for posting salary ranges!
Company: GitHub
Company site: https://github.com
Position(s): engineering, product, design, and many other roles (but selfishly I’m looking for platform product managers)
Location: US, Canada, Europe - remote or on-site
Description: GitHub is the largest code hosting and devops service in the world. We have dozens of roles open, but I’m looking for two platform product managers. One is for Data Storage – we build the SQL, NoSQL, blob storage, event queue, and job queue systems for the rest of the company. The other is for Git Systems – we build storage and access mechanisms for Git data, and we contribute much of what we build upstream so everyone can benefit from it. (Job posting hasn’t gone live yet, email me at my-lobsters-username@github.com if interested.)
Tech stack: Ruby on Rails, Go, Rust, C#, Kubernetes, Typescript
Compensation: competitive salary and stock grants, discretionary PTO (most people aim for 5-6 weeks), home office and communications stipends, remote-first culture
Contact: https://boards.greenhouse.io/github/jobs/4020477 or my-lobsters-username@github.com for the PM roles; https://github.com/about/careers for other roles
Looks like I can no longer edit my post. The Git Systems role has been posted.
I’m always surprised that at its scale (and being Microsoft), GitHub is still not hiring in some countries (such as France).
I agree, it sucks. We can’t even hire in all of Canada, only certain provinces. Because we’re independent of Microsoft, we can’t use all of their nexuses of business, employment tax setups, etc.
We go through remote.com
For those wondering, “We” in the above post means Thoughtbot.
French labour laws and regulations are off putting for many companies. We should stop pretending that’s not the case.
I wonder which specific thing is off putting?
Why not write it?
If it doesn’t exist…that’s the plan. Initial research here because I couldn’t find anything obvious myself.
This is an announcement to a list of articles about ruby, currently one: https://timriley.info/writing/2022/03/24/let-the-shape-of-the-code-reflect-its-flow/
I would rather do
Message.new(message).tap(&:decode).process
or decode in the initializer, not sureIn Clojure, the thread-first macro would be used instead of a chain calls in a readable way.
I’m not familiar with Ruby, but I’m guessing the chain of
then
calls can’t be collapsed?I understand the author’s concern that not naming the intermediate values can make understanding the intermediate types unclear, but I think not having the names increases readability. In most cases, the intermediate structures are defined by or near the function definitions that use them.
Unfortunately, Ruby doesn’t have first class support for functions -
decode
would be interpreted the same asdecode()
and would throw anArgumentError
. You would have to do something likemessage.then(&method(:decode)).then(&method(:process))
.I’ve seen people say this a lot, that Ruby doesn’t have first-class functions, but I think that is wrong. It’s just not syntactically as convenient as other languages, but you absolutely can pass methods around as objects.
Method objects and procs/lambdas are interchangeable here.
Here’s my response to whether Ruby has first-class functions: https://briankung.dev/2022/03/27/ruby-has-economy-class-functions/
I disagree with your argument because the entire Higher-Order Functions section confuses the difference between methods and functions. All of the problems you demonstrate come from that misunderstanding.
You are not instantiating a function here at all, you are metaprogramming the receiver of
returns_function
to add a new method. There are times where you’d actually want to do this, but they have nothing to do with higher-order functions.Yes, doing two entirely different things behaves differently! Allow me to flip this comparison on its head:
Huh, JavaScript sucks at methods doesn’t it? (No, this was intentionally wrong)
An apples-to-apples comparison looks a lot closer:
Currying in Ruby
Currying in JS
Ruby makes different tradeoffs between methods and functions than JavaScript or other languages. JS is more comfortable for pure functions, but methods are janky. Ruby puts most of its ergonomic focus on methods, with implicit block arguments fullfilling the majority of first-class function use-cases, and native procs and lambdas filling in the blanks.
This leads to more complexity in some cases, it’s true. But some of the interactions between them is much richer than JS APIs can provide. For instance:
I think you’re 1) making a strawman argument, and 2) ignoring the ergonomics of what first-class functions means. The fact that you have to think about any of this at all in order to use Ruby’s “first class functions” means you can only say it has them with a lot of disclaimers. As I put it in the post, “economy-class.” Note that I don’t deny that it technically has them!
To be equally pedantic, we weren’t talking about methods, were we? We’re talking about first-class functions. Not first-class methods. Yes, the syntax you call out defines a method on the caller, but do programmers used to first-class functions want to think about that? No, they do not. In fact, the fact that the
def
keyword defines methods and not functions makes it even more clear that Ruby does not treat functions as first-class.Yes, you can curry in Ruby. But it’s an incredibly uncommon pattern. Why? Because Ruby doesn’t treat functions as truly first class. You can do it - that was the whole point of my post - but it’s awkward. Wikipedia labels Ruby’s first-class functions support with the equivalent of asterisks:
Also yes, you can do cool stuff with
instance_eval
, but that’s not what we’re talking about. We’re talking about language-level support for first-class functions.I don’t understand how that could possibly be construed as a strawman, the entirety of my comments were responding to the substance of your blog post. Unless you are referring to the JS example, which I pointed out directly was intentionally wrong to make a point.
Ruby has first-class functions without any disclaimers. My critique of your argument is that you keep confusing method invocation for function execution and these are two entirely different language concepts.
To be explicitly clear: when I say Ruby has first-class functions, I am speaking of Procs and Lambdas. Those are functions. Methods are not. Two different things.
It’s also worth mentioning that your example:
You are demonstrating this property, which can also be written as
Block arguments are Procs that are passed as arguments to methods. This is the most common way of using first-class functions in Ruby, and it’s one of the most unique language features.
This isn’t strictly necessary to qualify as first-class, but I think it is the most common way to do it.
Is that more clear? Functions in Ruby are invoked with
call
which has syntactic sugar.()
.Second, methods are not functions.
This happened because the execution binding of a method is the receiver, not the lexical scope. This is important because calling a method is essentially just syntactic sugar for
send
This can be confusing in a REPL specifically, because it’s not clear from the above what you are actually doing. In a Ruby REPL,
main
is an instance ofObject
, and callingdef
is actually defining a new method on the Object class.send
Method objects exist so that you can translate the method interface into a function interface, but you’re really just bundling up the receiver together with a message to send
A method is a type of message that an object can respond to. You can’t separate them (well, you can but UnboundMethod is not executable until it is bound to something)
A Ruby method is an implementation of the OOP idea of data and behavior existing together in a single object.
All methods have an implicit block argument (a Proc), which is a first-class function. The syntax of Ruby allows you to seamlessly combine methods and functions into a single interface, but there is no requirement that you do so.
I use my Yubikey with
ykman
, e.g.With some basic dmenu wrapper it’s been a perfectly viable solution for me too:
I was never really happy with any templating language, none of them ever really fit and customization was never satisfactory. I started using
sh
as templating language (https://mkws.sh/pp.html) and never been happier. Makes sense, UNIX tools are great for processing plain text and also for displaying plain text in as human friendly way as possible. Just use standard UNIX tools for processing and outputting the text in any kind of file types.😬 One rough consensus about templating languages is that they shouldn’t be too capable*; you want your logic in the program, not the templates.
In light of that, a template language that’s capable enough to, say, upload all your data to Elbonia and then wipe your disk sounds particularly dangerous to me. What’s the pp equivalent of Johnny Tables?
Plus the fact that sh has IMHO the ugliest syntax for conditionals/loops outside of Brainf*ck…
* (I was going to say “they shouldn’t be Turing-complete”, but I wouldn’t be surprised if someone has figured out how to implement Towers of Hanoi in Liquid…)
There is a liquid json parser :) https://github.com/culturekings/shopify-json-parser
It’s a matter of responsability, who’s responsible for the safety, the programmer or the language? You ilustrated that pretty good with the Johnny Tables example, who’s responsabile for that, SQL (or C# I believe?), or the programmer? Any programming language is “dangerous”, also, any programmer.
It’s as safe as any shell script you write locally and run.
Ugly is relative, I also found it ugly initally, now I find it elegant.
Logic is where you decide to put it, you could write separate programs for the logic the UNIX way and only use
pp
for presenting the output.In the context of using it for generating static
HTML
files, I personally prefer to see it as progressive enhancement for the CLI. You’re already using the standard CLI tools to show data on your terminal, why not use the same CLI tools to show data on a web site, considering a web site is just a way to show off local data in a public way.If you use it only with local data, yes. But templates very often run on untrusted data from the network. If your templater doesn’t escape that properly, it’s open to injection attacks; and if the data that failed to be quoted is part of a shell command, you’ve got a full RCE.
Sure, but there’s a lot of evidence from experience that it’s better to separate logic from templates.
Agree, I’m only using locally for now. In case of untrusted data, just make sure you properly sanitize it, again matter of responsability.
Agree again, it’s a good idea to develop separate programs for logic and only use
pp
for displaying the output.Liquid is definitely turing complete. Being amazingly poorly designed and not fit for purpose doesn’t not prevent completeness ;)
Nice post! I did a similar project last year cloning Jinja in Python but writing the parser by hand.
Thanks, the idea is that I can run user-written templates, e.g. I define the context, the methods etc.
Kinda like liquid but more powerful and without the default standard library
That post is really really cool, I use PEG but would like to be able to create my own PEG one day (I tried different ones and previously used PEG.js which is quite powerful).
I read https://craftinginterpreters.com/ but mostly followed the examples.
Can’t you use PEG to write your own PEG implementation?
Too true, don’t know what to answer to that
Consider: https://kdl.dev/
The syntax can be a little weird
Job interviews
good luck!
I use Google Domains, no need for an extra account and secured through my Google account
I sometimes subscribe to Google Workspace and most of the times I change the nameservers to Cloudflare
I’m gonna redo the email verification for my app, let users edit their email, improve image generation, fix the title on Google, etc.
Also need to check out of my apartment
Water plants.
Plant.find_each(&:water)
, easy?Sure, but is it concurrent? :)
Parallel.each(Plant.all, &:water)
, there you go, infinite minions watering your plantsMuch better!
Very nice, exactly what I needed!
I should look into doing that for SES too
Need to organize my first speed dating event to make my app profitable
Company: Doctolib
Company site: https://doctolib.com
Position(s): Programmers
Location: REMOTE and/or ONSITE
Description: Booking doctors online and providing an agenda for doctors (it’s much more than that ;) )
Tech stack: Ruby/Rails/Javascript/React for the monolith, then TypeScript for the Desktop App (Electron), React Native with a webview for native apps, PostgreSQL, AWS, Kubernetes, etc.
Contact: dorian.marie@doctolib.com, I will redirect you to the correct contact, I’m just a programmer there :)
I don’t think this is very on-topic. A blog post containing in-depth content on exactly what makes this change worthwhile would be much more appropriate for Lobsters.
Erm, congrats?
agreed, it’s going to be in “this week in rails”, that will be more appropriate to post
setting up a basic idea of my concierge service idea
I just want to say that I hate stale bots, I just want to contribute if I found a solution to the bug or see the solution others have found
should be a single table
expressions
IMHOHow do you mean?