• בלוג
  • פונקציית Pipe ושרשור מתודות

פונקציית Pipe ושרשור מתודות

בתיעוד של אמזון אנחנו מוצאים את הדוגמה הבאה לשימוש ב Polly ב Java:

new SynthesizeSpeechRequest()
    .withText(text)
    .withVoiceId(voice.getId())
    .withOutputFormat(format).withEngine("neural");

התבנית הזאת נקראת Builder והיא מציעה טכניקה להתמודד עם בנאי שצריך לקבל הרבה פרמטרים. הרעיון הוא שבמקום להעביר את כל הפרמטרים בקריאה אחת בבנאי אנחנו נפעיל עוד ועוד פונקציות על האוביקט כשכל פונקציה מגדירה עוד פרמטר לבניית הדבר שאנחנו רוצים לבנות. הייתרון בתבנית ה Builder הוא שאפשר לבנות את הדבר בשלבים ואפילו לשלב באמצע תנאים או לולאות.

תבנית דומה לה נקראת Fluent Interface והיא מציעה שימוש בשרשור מתודות כדי לתאר פונקציונאליות או רצף פעולות. לדוגמה הקוד הבא מספריית jQuery:

$('#myButton')
  .click(function() {
    $(this).addClass('active');
  })
  .hover(
    function() {
      $(this).css('background-color', 'lightblue');
    },
    function() {
      $(this).css('background-color', '');
    }
  )
  .fadeOut(1000)
  .fadeIn(1000);

התבנית מתארת ממשק בצורה נוחה של קריאות בשרשרת לפונקציות השונות של האוביקט. כמו ב Builder, גם ב Fluent Interface כל פונקציה מחזירה את האוביקט שעליו אנחנו עובדים וכך אפשר לחבר עוד ועוד פעולות.

אבל הבעיה בתבנית הזאת ובכל שרשור של פונקציות היא שקשה לראות איך לחבר את זה לתנאים ולולאות שאנחנו מכירים. בדוגמה של ה jQuery אם הייתי רוצה להפעיל פעולה 10 פעמים ברצף הייתי צריך לכתוב אותה ממש 10 פעמים, או לשמור את מצב הביניים של השרשרת למשתנה כדי שאוכל להמשיך את השרשרת על המשתנה בתוך הלולאה.

טכניקה פשוטה להתמודד עם לולאות בתוך שרשראות של פונקציות היא הפונקציה tap. היא קיימת בהמון שפות ובשמות שונים ובכל מקרה אפשר תמיד לממש אותה ממש בקלות, כשהרעיון הבסיסי הוא ש tap היא מתודה שיש לכל אוביקט בשפה, היא מקבלת בתור פרמטר פונקציה כלשהי, היא תפעיל את הפונקציה ותחזיר את האוביקט (ה this). מימוש פשוט ב JavaScript של tap נראה כך:

function tap(fn) {
  fn(this);

  return this;
};

בואו ניקח דוגמה מ Ruby שם tap כבר מובנית בשפה ונראה איך להשתמש בה כדי להוסיף לולאות לשרשראות של פונקציות. אני מתחיל עם מחלקה בשם Polly שעובדת בתבנית הבנאי עם הקוד הבא:

class Polly
  attr_accessor :text, :engine
  def initialize
    @text = []
  end

  def with_text(text)
    @text.append(text)
    self
  end

  def with_engine(engine)
    @engine = engine
    self
  end

  def print
    puts "Engine: #{@engine}; Text: #{@text}"
  end
end

ועכשיו אני רוצה להפעיל את with_text בלולאה עם המחרוזות a, b ו c. אפשר כמובן להשתמש במשתנה ואז נקבל:

p = Polly.new
p.with_engine("engine")
['a', 'b', 'c'].each {|t| p.with_text(t) }
p.print

אבל אם רוצים לוותר על המשתנה אפשר להשתמש ב tap ואז נקבל:

Polly
    .new
    .with_engine("engine")
    .tap { |p| ['a', 'b', 'c'].reduce(p, &:with_text) }
    .print