100言語spedrun:エピソード100:ルビー


これは、シリーズの最終的なレギュラーエピソードですが、ティアリストとシリーズの回顧といくつかのボーナスエピソードが来ている.
Rubyはこれまでに作られた最適なプログラミング言語です.この世紀のプログラミング言語デザインの歴史は、Rubyからゆっくりと、他のプログラミング言語に新たに拡散されている.うまくいけば、いつかRubyより良い言語が作成されます、しかし、何も本当にまだ試みられません.
クリスタル、ジュリア、エリクサーのような最もエキサイティングな新しい言語の多くは、Rubyに強く触発され、他のすべてのRubyのような機能で更新され続けている.PythonやES 6のJavaScriptクラスのシステムでf文字列のような大きな特徴、または小さな機能String.prototype.matchAll 最近JavaScriptに加えられて、Rubyはプログラミング言語設計の全世界のインスピレーションの主な源であり続けます.
ルビーを使用することができますいくつかの素晴らしい方法のいくつかを探検しよう!

フィズバズ


我々は最もプレーンバージョンを行うことができます.
#!/usr/bin/env ruby

(1..100).each do |n|
  if n % 15 == 0
    puts "FizzBuzz"
  elsif n % 3 == 0
    puts "Fizz"
  elsif n % 5 == 0
    puts "Buzz"
  else
    puts n
  end
end
(1..100) 美しい範囲です1 to 100 , ここの1つの問題によってありません.
しかし、ちょっとあなたがalmost - nに範囲を望むならば、ルビーも提供します1...100 スタイル範囲1 to 100-1 ). それらの主な使用がインデックスによって配列を繰り返していて、すべての一般的な反復パターンがより良い方法を持っているので、これらはしばしば非常により役に立ちません.
また、関数や範囲、正規表現、またはカスタムマーチャーオブジェクトのようなオブジェクトを使用することもできます.
#!/usr/bin/env ruby

def divisible_by(n)
  proc{|m| m % n == 0}
end

puts (1..100).map{|n|
  case n
  when divisible_by(15)
    "FizzBuzz"
  when divisible_by(5)
    "Buzz"
  when divisible_by(3)
    "Fizz"
  else
    n
  end
}
パターンマッチングを使うこともできます.
#!/usr/bin/env ruby

puts (1..100).map{|n|
  case [n%5, n%3]
  in [0, 0]
    "FizzBuzz"
  in [0, _]
    "Buzz"
  in [_, 0]
    "Fizz"
  else
    n
  end
}
パターンマッチングは、関数型プログラミング言語ではかなり一般的な特徴ですが、一般的には、公開されたユニオン型の構造マッチングを行うことができますEmptyLisp or Cons(head, tail) ), そして、それはオブジェクト指向の世界に本当に非常に適用できません.Ruby 3XはOOP互換パターンマッチングでいくつかの革新を試みます.
あなたが間の違いが何であるかについて疑問に思っているならばdo ... end ブロックと{ ... } ブロック、それは彼らの解析の優先順位です.ルビーも別々ですand/or and &&/|| , 別の解析の優先順位をもう一度.これは少し物議を醸す選択です-それは括弧を減らすことによって明快さを増すことができます、しかし、一部の人々はその冗長性が好きでありません.

フィボナッチ


明白なものから始めましょう.
#!/usr/bin/env ruby

def fib(n)
  if n <= 2
    1
  else
    fib(n-1) + fib(n-2)
  end
end

(1..30).each do |n|
  puts "fib(#{n}) = #{fib(n)}"
end
このコードは簡単に入手できます.ものでない.無意味なreturn s .型宣言.そして、すべての最も重要な-ストリング補間.
私の知る限り、Rubyは完全な文字列補間を導入する最初の言語でした.あなたは開くことができます#{ } 文字列内の任意の式を指定し、String with .to_s そこに置かれる.Rubyの前の多くの言語で変数を補間することができました(一般的に変数が$ ), そして、Perl 5はいくつかの限られたケースでこれを支持するために騙されることができます、しかし、ルビーは新しいレベルにそれを取りました、そして、数十年の抵抗ストリング補間の後、至る所であります.そして、私がこのシリーズで発見したように、あらゆる言語がそれのためにわずかに異なる構文を選んだようです.
右、しかし、Fibonacciシーケンスに戻る.
#!/usr/bin/env ruby

require "memoist"

extend Memoist

memoize def fib(n)
  if n <= 2
    1
  else
    fib(n-1) + fib(n-2)
  end
end

(200..210).each do |n|
  puts "fib(#{n}) = #{fib(n)}"
end
Rubyはかなりシンプルな言語です.あなたは本当に偉大な構文、すべてのオブジェクト、すべての式、ブロック、ブロックの多くのことを得ることができます.それはちょうどそのためのブロックを使用することができますので、Rubyは決して追加されたので、多くの機能があります.
そのような行方不明の機能の1つは@ デコレータ.最初のエピソードでは、どのようにPythonで我々が使用できるかを示したfunctools.cache 関数にメモを追加するには.Rubyでmemfiized fibonacciを実装しましょう.
がないfunctools 標準ライブラリではmemoist ジェム.extend Memoist メモ表といくつかのエクストラのような追加flush_cache 我々が中に入っているもの(通常オブジェクトであるが、我々がここですることができるすべてに)で、プログラム全体もオブジェクトであるので!
それから、我々は電話しますmemoize 私たちが記憶したい方法の名前を渡すこと.あれmemoize def fib(n) は新しい構文ではないdef fib(n) ... ちょうど他のすべてのような表現です-ちょうどその名前を返すために起こる1つ:fib ので、最初にメソッドを定義し、呼び出しmemoize :fib . これは些細なことのように思えるかもしれませんが、Ruby構文はいくつかの美しいドメイン固有の言語になるような素晴らしい相互作用に満ちています.
しかし、ほとんどのRubyユーザーがこれを行う方法はmemoist , でも||= 何か
#!/usr/bin/env ruby

def fib(n)
  @fib ||= {}
  @fib[n] ||= begin
    if n <= 2
      1
    else
      fib(n-1) + fib(n-2)
    end
  end
end

(200..210).each do |n|
  puts "fib(#{n}) = #{fib(n)}"
end
この関数は非常に一般的なパターンです.特に、関数がどんな変数も取りません.
  def dictionary
    File.readlines("wordle-answers-alphabetical.txt").map(&:chomp)
  end
memoizationでこのような方法にします
  def dictionary
    @dictionary ||= File.readlines("wordle-answers-alphabetical.txt").map(&:chomp)
  end
このように1行コードnil and false 記憶されていないが、それは非常にまれに実際に来る.
おもしろいHash コアRubyクラスの1つは、いくつかの非常に簡潔なmemoizationに使用できます.
#!/usr/bin/env ruby

fib = Hash.new{|_, n| fib[n] = fib[n-1] + fib[n-2]}
fib[1] = 1
fib[2] = 1

(200..210).each do |n|
  puts "fib(#{n}) = #{fib[n]}"
end
あなたはブロックを渡すことができますHash.new そして、そのブロックは、誰かがHash . ブロックは計算するか、計算して割り当てることができます.= 値を返します.ブロックとすべての式であるおかげで、我々はいくつかの簡潔で美しいコードを取得します.
Ruby Metaprogrammingについての重要なことは、Rubyが非常にアプローチ可能であるメタプログレッシブメソッドを使用するだけではなく、それらを作成することです.これは、他の多くのシステムとは対照的で、マクロが使いやすい様々なLISSのようなものですが、マクロによる書き込みは暗い技術です.
ちょうど我々自身を実行しましょうmemoize 方法は、私たちが欲しいものを覚えることができます!
#!/usr/bin/env ruby

def memoize(name)
  m = method(name)
  define_method(name) do |*args|
    @memo ||= {}
    @memo[name] ||= {}
    @memo[name][args] ||= m.call(*args)
  end
end

memoize def fib(n)
  if n <= 2
    1
  else
    fib(n-1) + fib(n-2)
  end
end

(200..210).each do |n|
  puts "fib(#{n}) = #{fib(n)}"
end
はい、それは簡単です.ちょうど5行です.
  • 私たちは、私たちがmethod(name)
  • その後define_method ブロックを通過する新しいもの
  • 初期化@memo and @memo[name] 現在のオブジェクトにメモ値を格納する場所
  • if @memo[name][args] が設定されていない場合は、元のメソッドを呼び出します
  • 最後に結果を示します:
    $ ./fib5.rb
    fib(200) = 280571172992510140037611932413038677189525
    fib(201) = 453973694165307953197296969697410619233826
    fib(202) = 734544867157818093234908902110449296423351
    fib(203) = 1188518561323126046432205871807859915657177
    fib(204) = 1923063428480944139667114773918309212080528
    fib(205) = 3111581989804070186099320645726169127737705
    fib(206) = 5034645418285014325766435419644478339818233
    fib(207) = 8146227408089084511865756065370647467555938
    fib(208) = 13180872826374098837632191485015125807374171
    fib(209) = 21327100234463183349497947550385773274930109
    fib(210) = 34507973060837282187130139035400899082304280
    
    Rubyは特別な注釈なしで箱から大きな整数をサポートします.私は、それをする最初の主要な言語であったかもしれないと思います.Pythonは確かにもともとはありませんでした、そして、最初に、余分に彼らを導入しましたL 接尾辞、およびPerl/JavaScriptなどはfloatに大きな整数をオーバーフローしました.今この機能はかなり一般的ですが、普遍的から.

    ライナー


    Rubyはとても簡潔です.とてもよく見える言語がコード・ゴルフ競技会でPerl 5とAPLに対抗して競争していることは、野生です.
    私は、それについて何かを知っています.
    Rubyはシェルの1つのライナーのために合理的に使用できる非常に少数の言語の一つです.PerlとRAKUは、他の唯一のものです.
    たとえば、ファイルからすべての数字を抽出して追加するには、以下のようにします.
    $ cat budget.txt
    Food $200
    Data $150
    Rent $800
    Candles $3600
    Utility $150
    $ ruby -e 'puts STDIN.read.scan(/\d+/).map(&:to_i).sum' <budget.txt
    4900
    
    あるいは、いくつかのテキストを変換したいと思います.
    $ ruby -ple '$_ = "#{$.}. #{$_}"' < budget.txt
    1. Food $200
    2. Data $150
    3. Rent $800
    4. Candles $3600
    5. Utility $150
    
    またはいくつかの猫の事実をしていないjq インストール?
    $ curl -s 'https://cat-fact.herokuapp.com/facts' | ruby -rjson -e 'JSON.parse(STDIN.read).each{|x| puts x["text"]}'
    Wikipedia has a recording of a cat meowing, because why not?
    When cats grimace, they are usually "taste-scenting." They have an extra organ that, with some breathing control, allows the cats to taste-sense the air.
    Cats make more than 100 different sounds whereas dogs make around 10.
    Most cats are lactose intolerant, and milk can cause painful stomach cramps and diarrhea. It's best to forego the milk and just give your cat the standard: clean, cool drinking water.
    Owning a cat can reduce the risk of stroke and heart attack by a third.
    

    悪化する


    このエピソードを悪夢で終わらせましょう
    #!/usr/bin/env ruby
    
    class Wordle
      def dictionary
        @dictionary ||= File.readlines("wordle-answers-alphabetical.txt").map(&:chomp)
      end
    
      def word
        @word ||= dictionary.sample
      end
    
      def report(guess)
        5.times.map{|i|
          if guess[i] == word[i]
            "🟩"
          elsif word.include?(guess[i])
            "🟨"
          else
            "🟥"
          end
        }.join
      end
    
      def play
        loop do
          print "Guess: "
          guess = gets.chomp
          puts report(guess)
          break if guess == word
        end
      end
    end
    
    Wordle.new.play
    
    ああごめんなさい、私はwordle言う?私は、我々のためにwordleをするボットを意味しました:
    #!/usr/bin/env ruby
    
    require "memoist"
    
    class WordleBot
      extend Memoist
    
      memoize def dictionary
        @dictionary ||= File.readlines("wordle-answers-alphabetical.txt").map(&:chomp)
      end
    
      memoize def report(guess, word)
        5.times.map{|i|
          if guess[i] == word[i]
            "🟩"
          elsif word.include?(guess[i])
            "🟨"
          else
            "🟥"
          end
        }.join
      end
    
      # Try to pick a guess that minimizes worst case outcome
      def score(guess)
        @candidates.group_by{|word| report(guess, word)}.values.map(&:size).max
      end
    
      def best_guess
        dictionary.min_by{|guess| score(guess)}
      end
    
      # This is quite slow, especially the first word, as we do O(N^2) checks
      # So to save some time, result of first one is pre-calculated here
      def play
        @candidates = dictionary
        guess = "arise"
        loop do
          puts guess
          result = gets.chomp
          break if result == "🟩🟩🟩🟩🟩"
          @candidates.select!{|word| report(guess, word) == result}
          guess = best_guess
        end
      end
    end
    
    WordleBot.new.play
    
    物事をシンプルに保つために、彼らはお互いに話をしないでください、それはすべてコピー&ペーストに基づいています.
    ここにゲームのビューがあります.
    $ ./wordle.rb
    Guess: arise
    🟥🟨🟥🟥🟨
    Guess: older
    🟥🟥🟥🟨🟨
    Guess: berry
    🟥🟨🟨🟩🟥
    Guess: exert
    🟩🟩🟩🟩🟩
    
    とボット表示:
    $ ./wordlebot.rb
    arise
    🟥🟨🟥🟥🟨
    older
    🟥🟥🟥🟨🟨
    berry
    🟥🟨🟨🟩🟥
    exert
    🟩🟩🟩🟩🟩
    

    Rubyを使用するか


    確かにはい!
    Rubyは非常に多くのドメインに最適です.それは1つのライナーのために驚くべきです、それは中規模のプログラムのために驚くべきです、そして、Rubyがあなたにドメイン特定の論理を表現するためにいくつかのDSLを簡単に作成することができて、それはより大きなプログラムのためにより良いです.Ruby(およびRails)は、個人と小さなチームがどのようにはるかに大きい会社とうまく競争することができたかです.
    ルビーは完璧ではない、それは他のすべての言語のちょうど非常にはるかに先です.ルビーでコーディングすることによって、あなたは他の言語で人々が何年も何十年も待つだろうことを今日、経験します.それは使用されるより一般的ではありません、しかし、多くのプログラマーはまだ無制限の省略整数なしで言語でSufdfer.scan(Regexp) , ブロックなし!あまりにも多くのまだ愚かな“行方不明セミコロン”エラーに自分のlifの貴重な時間を無駄に!幸いなことに、今日の言語は10年前の言語よりもルビーが多く、この傾向は続くように見えます.
    誰かがより良いプログラミング言語を作成しますか?私は確かにそう願っています.プログラミング言語は新しい機能で実験を続けています.しかし、今までのところ、誰もが一緒にすべての偉大なアイデアを一緒に芸術の単一の作品にもたらした.
    そして、あなたが新しいプログラミング言語を設計しているならば、ちょっと賢い人々がしたことをしてください、そして、あなたがそうすることができるようにRubyの多くとしてコピーすることから始めてください(あるいは、少なくとも何か他の良い、例えば、Pythonと言いましょう).あまりにも頻繁にプログラミング言語デザイナーは、空白のスレートポイントからこの問題にアプローチしますが、本当に、なぜ芸術の状態から始めるのか?クリスタルは、このアプローチの最高の例ですが、エリクサー、ジュリア、いくつかのあまり知られていないもののような他の言語の多くは大胆にヘッドスタートを持って作品をコピーします.

    コード


    All code examples for the series will be in this repository .
    Code for the Ruby episode is available here .