Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Real-world Functional Ruby
Search
Tim Riley
October 12, 2017
Technology
580
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Real-world Functional Ruby
RubyConf Malaysia 2017
Tim Riley
October 12, 2017
More Decks by Tim Riley
See All by Tim Riley
A tour of dry-schema and dry-validation 1.0
timriley
1
430
Views, from the top
timriley
0
2k
Real-world functional Ruby
timriley
0
1.2k
Reinvesting in Ruby
timriley
1
150
Next-generation Ruby web apps with dry-rb, rom-rb, and Roda
timriley
5
5.4k
Functional Ruby, Rodakase, and another world of Ruby web applications
timriley
7
1.2k
Introduction to RubyMotion
timriley
6
3.6k
Other Decks in Technology
See All in Technology
"計画的無茶振り"を通してAIネイティブ世代の新人を育てる
subroh0508
1
150
Flutter Widget Previewerで始めるUIプレビューとVRTの自動化
sakusaku3939
0
200
企画からFlutter実装まで、1つの機能を一貫して開発したプロセスと成果物
khiroyuki1003
0
140
クラウドエージェントの先駆け Devin。今も最先端を走る、他との違い
kinopeee
2
560
Content Mapper APIの応用例~CSS Modules、言語ファイル~
uhyo
2
540
Copilot Coworkで「みのるん式」やってみた
minorun365
PRO
2
1.4k
Building Audio Plugin Ecosystem for Android in 2026
atsushieno
2
130
FinOpsを導入してマルチプロダクトのコスト最適化を実現する
kworkdev
PRO
0
120
Claude Codeから見る、エンジニアの世界の変容とその先
oikon48
8
5.3k
書き手が人でもAIでも正しくなる道、SREを埋め込んだDeveloper Platform / tech drivers day8
chmikata
4
900
AIエージェント時代、Kaggleにどう取り組むか
takanao
2
480
なぜAI時代に技術記事を書くのか?どう書くのか? 〜bunの場合〜
kworkdev
PRO
0
4.6k
Featured
See All Featured
Building the Perfect Custom Keyboard
takai
2
940
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
730
Balancing Empowerment & Direction
lara
6
1.4k
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
2
3k
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
550
Music & Morning Musume
bryan
48
7.5k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
470
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
790
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
ラッコキーワード サービス紹介資料
rakko
5
5.6M
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.5k
Transcript
Real-world Functional Ruby RubyConf Malaysia 2017
@timriley
None
@icelab
!
None
@dry_rb @rom_rb
None
None
“Programmers at work maintaining a Ruby on Rails application” Eero
Järnefelt Oil on canvas, 1893
None
None
user.rb after_save accepts_nested_attributes_for
⏱ DESIGN STAMINA HYPOTHESIS
⏱ DESIGN STAMINA HYPOTHESIS
⏱ DESIGN STAMINA HYPOTHESIS
⏱ DESIGN STAMINA HYPOTHESIS No design
⏱ DESIGN STAMINA HYPOTHESIS No design
⏱ DESIGN STAMINA HYPOTHESIS No design
⏱ DESIGN STAMINA HYPOTHESIS No design Good design
⏱ DESIGN STAMINA HYPOTHESIS No design Good design
⏱ DESIGN STAMINA HYPOTHESIS No design Good design Design payoff
line
Object-oriented design
The gap.
None
None
Haskell
Haskell
Haskell
Real-world Functional Ruby RubyConf Malaysia 2017 Tim Riley // @timriley
Functional Ruby, can!
Functional Ruby, can!
FP is hot AF
None
App as series of transformations
Request → [*] → [*] → Response
App as series of transformations
App as series of functions
Functional objects
class ImportProducts def call(feed) import_from(feed) end end
class ImportProducts def call(feed) import_from(feed) end end
class ImportProducts def call(feed) import_from(feed) end end
class ImportProducts def call(feed) import_from(feed) end end
Separate data from behaviour
Immutable
class ImportProducts def call(feed) end end records = download_feed.(feed) records.each
do |attrs| products_repo.create(attrs) end
class ImportProducts def call(feed) records = download_feed.(feed) records.each do |attrs|
products_repo.create(attrs) end end end
class ImportProducts def call(feed) records = download_feed.(feed) records.each do |attrs|
product_repo.create(attrs) end end end
download_feed: product_repo:
class ImportProducts attr_reader :download_feed attr_reader :products_repo def initialize(download_feed:, product_repo:) @download_feed
= download_feed @products_repo = products_repo end def call(feed) # ... end end
class ImportProducts attr_reader :download_feed attr_reader :product_repo def initialize(download_feed:, product_repo:) @download_feed
= download_feed @product_repo = product_repo end def call(feed) # ... end end
# Initialize once import = ImportProducts.new( download_feed: download, product_repo: repo,
) # Reuse many times import.(:books_feed) import.(:dvds_feed)
# Initialize once import = ImportProducts.new( download_feed: download, product_repo: repo,
) # Call many times import.(book_feed) import.(film_feed)
Feeds::Create Feeds::Update Feeds::Delete Products::Update
Behaviour
Data
Types
Values
class Product attr_reader :title, :isbn def initialize(**attrs) @title = attrs[:title]
@isbn = attrs[:isbn] end def slug "#{to_slug(title)}-#{isbn}" end end
class Product attr_reader :title, :isbn def initialize(**attrs) @title = attrs[:title]
@isbn = attrs[:isbn] end def slug "#{to_slug(title)}-#{isbn}" end end
Values objects are first-class
Values & Functions
Values Functions Hold data Operate on data
Values Functions Hold data Operate on data
Values Functions Hold data Operate on data Inert Active
Values Functions Hold data Operate on data Inert Active Content
Pipeline
Functional design
Functional, object-oriented design
Functional Ruby yields better object-oriented Ruby!
RSpec.describe ImportProducts
download_feed product_repo
let(:download) { double(:download) } let(:repo) { double(:repo) }
subject(:import) { ImportProducts.new( download_feed: download, product_repo: repo, ) }
let(:feed) { Feed.new(…) } before do allow(download_feed) .to receive(:call) .with(feed)
.and_return(books_data) end
let(:feed) { Feed.new(…) } before do allow(download) .to receive(:call) .with(feed)
.and_return(BOOKS_DATA) end
it "creates the products" do expect(repo) .to receive(:create).with(title: "…") import.(feed)
end
Import Products download_feed product_repo
Dependencies
App as graph
None
None
Good design makes change easier
Functional design makes change easier
Real developers hate him! He’s idealistic He’s making too many
.rb files He found a #method to build better apps. Learn the one WEIRD trick to his stunning results! GET FUNCTIONAL NOW
None
A real-world functional Ruby ecosystem
dry-rb
None
Dependency management
dry-system & dry-auto_inject
# lib/my_app/import_products.rb MyApp::Container[“import_products"] #<MyApp::ImportProducts>
# lib/my_app/import_products.rb MyApp::Container["import_products"] #<MyApp::ImportProducts>
class ImportProducts include MyApp::Import[ "products.download_feed", "product_repo", ] def call(feed) #
... end end
# lib/my_app/import_products.rb MyApp::Container["import_products"] #<MyApp::ImportProducts>
# lib/my_app/import_products.rb MyApp::Container["import_products"] #<MyApp::ImportProducts download_feed=#<DownloadFeed> product_repo=#<ProductRepo>>
ImportProducts.new( download_feed: double(:download) ) #<ImportProducts download_feed=#<Double :download> product_repo=#<ProductRepo>>
ImportProducts.new( download_feed: double(:download) ) #<ImportProducts download_feed=#<Double :download> product_repo=#<ProductRepo>>
Result handling
dry-monads & dry-matcher
# Successful result Right(imported_products) # Failed result Left(error)
import_products.() do |m| m.success do |imported_products| # … end m.failure
do |error| # … end end
Data & types
dry-validation
Dry::Validation.Schema do required(:name).filled required(:url).filled(format?: /…/) required(:active).filled(:bool?) end
dry-types & dry-struct
ISBN = Strict::String .constrained(format: /…/)
class Product < Dry::Struct attribute :title, Types::Strict::String attribute :isbn, Types::ISBN
end
View rendering
dry-view
expose :product do |slug:| product_repo.by_slug(slug) end
HTTP & routing
dry-web-roda
$ dry-web-roda new my_app
. ├── Gemfile ├── README.md ├── Rakefile ├── apps │
└── main │ ├── lib │ │ └── main │ │ └── views │ │ └── welcome.rb │ ├── system │ │ ├── boot │ │ │ └── view.rb │ │ ├── boot.rb │ │ └── main │ │ ├── application.rb │ │ ├── container.rb │ │ ├── import.rb │ │ ├── transactions.rb │ │ ├── view_context.rb │ │ └── view_controller.rb
│ │ └── relations │ └── types.rb ├── log ├──
spec │ ├── app_helper.rb │ ├── db_helper.rb │ ├── spec_helper.rb │ └── support │ ├── db │ │ └── test_factories.rb │ └── test_helpers.rb └── system ├── blog │ ├── application.rb │ ├── container.rb │ ├── import.rb │ └── settings.rb ├── boot │ └── rom.rb └── boot.rb 28 directories, 37 files
Persistence
rom-rb
Dependency management Result handling Data validation & types Views Routing
& HTTP Database persistence
None
A functional Ruby movement
None
None
hanami
RailsClub 2017 // @jodosha Functional Web with Hanami
Ruby is ready
There is nothing more powerful than an idea whose time
has come. — Victor Hugo
Bridging the gap for Ruby
Real-world Functional Ruby
FP + OO = Win!
FP + OO + Community = Win!
Functional Ruby is supported
Functional Ruby is practical
Functional Ruby is worth a try!
dry-rb/dry-web-blog
Thank you! @timriley dry-rb/dry-web-blog bit.ly/rubyconfmy-fp-ruby