Tallman

技術とか読書とかいろいろ

SpotifyAPIとRuby2.7の新機能パターンマッチングで人気のある曲だけをフリーワードから検索して出力するスクリプトを書いた

Rubykaigi2019にてパターンマッチングについての発表がありました。

speakerdeck.com スライドにもある通りAPIからもらったJSONを扱うのに便利と感じました。
そこで実際に解析するスクリプトを書いてみたのがこちら。 github.com

スタンドの名前つけるの楽しいしモチベ上がるのでおすすめです。

Echoesのやること

Spotifyは内部で曲やアーティストにpopularityというスコアを100点満点でつけています。
Echoesはフリーワードで公開されているplaylistを検索して、その中に含まれているpopularityが90点以上のスコアを持ってる曲を出力してくれます。

f:id:sasa5740:20190522223424g:plain SpotifyAPIで提供されている検索機能を利用しています。
入力されたワードで検索して、受け取ったJson形式のレスポンスをパターンマッチングを使って解析しています。

playlists.each do |playlist|
  tracks = spotify_client.get_tracks_from_playlist(playlist)
  tracks[:items].each do |item|
    100.downto(90).each do |popularity|
      case item
      in { track:  { name: name, popularity: ^popularity } }
        result[name] = popularity
      else
        next
      end
    end
  end
end

具体的にパターンマッチング使ってるのはこの部分

    100.downto(90).each do |popularity|
      case item
      in { track:  { name: name, popularity: ^popularity } }
        result[name] = popularity
      else
        next
      end
    end

^popularityの部分は既に定義されている変数をパターンマッチに使いたいときに使う記法です。今回は100から90までのIntegerインスタンスを突っ込んでます。
elseも書かないとNoMatchingPatternErrorという例外が起きてしまうので注意。(このErrorいい感じになってほしい)
スコアがpopularity点だった場合にresultハッシュに突っ込んでいくことをやっているのですが、パターンマッチングを使うと変数に値を格納するのも直感的にかけます。
これがパターンマッチング使わないと

    100.downto(91).each do |popularity|
      if item[:track][:popularity] == popularity
        name = item[:track][:name]
        result[name] = popularity
      end
    end

という感じになります。今回はそれほどでもないですが、条件追加するごとにどんどん条件分岐が深くなっていくやつですね。

終わりに

パターンマッチングを使うと条件分岐もパターンでシンプルに設定し、かつ変数に値も入れてくれます。
こんな感じの単純な使い方以外にもクラスに構造を追加してパターンマッチングしたり色々遊べそうです。
実際遊んでる記事を上げておきます。

tech.medpeer.co.jp

developer.feedforce.jp

Ruby 2.7 楽しみですね

追記

@k_tsjさんにこう書くといいよアドバイスいただきました。

playlists.each do |playlist|
  tracks = spotify_client.get_tracks_from_playlist(playlist)
  tracks[:items].each do |item|
    case item
    in { track:  { name: name, popularity: 90..100 => popularity } }
      result[name] = popularity
    else
      next
    end
  end
end

rangeオブジェクトとASパターンを組み合わせるともっとわかりやすい:eye: すごいクールになりました。ありがとう御座います。