To get started, we the example class we start with is a Tweet class.
require 'values'
class Tweet < Value.new(:tid, :username, :text)
def initialize(tid, username, text)
super(tid.to_i, username, text)
end
def line_count
lines.count
end
def lines
text.split("\n")
end
end
# Another file
class Timeline
attr_reader :tweets
def initialize(tweets)
@tweets = tweets
end
def add(tweets)
existing_ids = @tweets.map(&:tid).to_set
new_tweets = tweets.reject { |t| existing_ids.include?(t.tid) }
Timeline.new(new_tweets + @tweets)
end
endThere is an even more Cursor class that is explained in the video, but in the video he is happy that Cursor is immutable.
As a last example of the functional core, there is a TweetRenderer class.
They are all examples of the "functional core". The only difference is the re-binding of local variables.
The imperative shell will be encapsulated in a single file (mostly at least) that manages the work like updating the database, etc. This is a file that deals with side-effects.
An example of an imperative shell:
class Toot
def initialize(screen)
@screen = screen
end
def run
TwittlerLib.authenticate
database = Database.default
timeline = Timeline.new(database.timeline)
timeline_queue = SelectableQueue.new
# start_timeline_stream(timeline, timeline_queue)
world = World.new(timeline)
view = TimelineView.new(@screen)
EventLoop.new(database, world, timeline_queue, view).run
end
def start_timeline_stream(timeline, timeline_queue)
timeline_stream = TimelineStream.new(timeline, timeline_queue)
Thread.new { timeline_stream.run }
end
endNote: the entire outer shell contains NO tests. He also mentions that he cowboy codes a little bit in the outer shell, and once he knows what he wants to do then he will move the source code into unit-tested files using TDD.