petitviolet_blog

@petitviolet blog

2019年度振り返り

社会人5年目の振り返り。
昨年度の。

転職した

大きなイベントとしてはまずこれ。 4年ちょっと在籍したFringe81株式会社からトレジャーデータ株式会社に。

現状、何とか死なずに済んでいるという状態。 入社してしばらくは無力感に打ちひしがれていたものの9月にはバンクーバーに出張に行き、何やかやで少しずつ理解が進んできたので冬の間はお金になりそうな機能を作っていた。 やっていることもそうだし、働き方とか仕事の進め方が前職と前提から異なっている感じで、新鮮で楽しいには楽しい。 ここ最近はCOVID-19の影響でしばらくオフィスには通っていない。オフィスランチが恋しい...。

そして英語は相変わらずボトルネックだが実際どれくらいなんだろうと思ってTOEFL受けてみたら73点だったのでわりとアカンことが判明してしまった。 というか大学院入試の頃(TOEIC760くらい?)と英語力的にはほぼ変わってなさそうでもうダメだしそんな状態で面接とかするとまあ大変だった。

エンジニアとして

環境が大きく変わったけどわかりやすいところだとScala -> Ruby(Rails)。 大学院の演習でRuby勉強してRailsアプリ作ったけど、それ以来でRubyを書いている。
最初は大変だったけど少し慣れてきたかなというところではある。結局難しいのはRubyとかRailsとかじゃないし。
せっかく仕事で使っているし勉強しようということで、Rubyで習作Gemをいくつかrubygems.orgに公開したりしてみている。https://rubygems.org/profiles/petitviolet

関係ないところだと夏頃はTypeScriptとかReact.jsのhookを触って遊んでた。

そしてGatsby使ってblog.petitviolet.netを作ったり。 今後は技術的なものはあっちに移すかもしれない。

登壇ScalaMatsuri2019だけだったらしい。

技術的には全体的に底上げされている感覚はあるもののこれといった強みを持てていないという悩みは相変わらずなのでどうしようかな。

プライベート

引っ越しをした、そして色々あったが子どもは相変わらずとにかくはちゃめちゃに可愛い。

来年度に向けて

ばーんとお金を稼ぐぞ!

RstructuralにOptionとEitherを追加した

前にRstructuralというGemを作ったという記事を書いた。

それの続編としてOptionEitherを実装した、というだけの話。

Option

Scalaやってる人にはOptionHaskellとかならMaybeで伝わるはず。 「要素があるかも知れないし無いかも知れない」を表現するための型で、Rubyだとすぐにnilが出てきてあらゆるオブジェクトがnilの可能性もあり大変なのでもう少し安全に扱いたいなということで実装した。 しかしそうなるとすべての要素をOptionにする必要があり大変だが、名前の通りオプショナルな値をOptionで包んでおくと便利に扱えるのではないかなと。

Option.ofがファクトリになっていて、mapとかflat_mapが生えている。

x = Option.of(100).flat_map do |v| 
  Option.of(v * 2)
end 
#=> Option::Some(value: 200)
x.get_or_else { 100 }
#=> 200

get_or_elseの引数はデフォルト値でも良いけどブロックを渡せるようにもなっていてOption::Someの時には無駄に評価されないようになっていたりして便利。 Scalaのcall-by-nameの仕組みは便利だったなほんと。

とはいえnilを安全に扱うための言語的なサポートが無いので、Optionがあっても気を付けないと何も変わらないので面倒だしあんまり使わない気もする。

Either

値がLeftかRightのどちらかであることを表現する型。 Rubyだと失敗がnilか例外になることが多いような気がしていてもう少し明示的に扱いたいと思ったので実装した。 Either.try(&block)がファクトリになっていて例外が起きたらEither::Leftになる。

Either.try { 100 } #=> Either::Right(value: 100)
Either.try { raise "this is error" } #=> Either::Left(value: this is error)

明示的にLeftかRightかを指定してnewしたければEither.leftEither.rightが生えている。

Either.right(100)
    .flat_map { |v| Either.left(v * 2) } #=> Either::Left(value: 200)
    .right_or_else { 0 } #=> 0

Either.left(100)
    .map_left { |v| v * 2 } #=> Either::Left(value: 200)
    .left_or_else { 0 } #=> 200

やはりmapflat_mapが生えていて、さらにright-biasedになっていて便利! さらにパターンマッチと組み合わせるとこういう雰囲気

case Either.try { do_something() }
in Either::Right[value]
    value * 2
in Either::Left[error]
    logger.error("error: #{error}")
    0
end

結果の成功失敗をラップする型があるとわりと便利かなと思うこともあり、Optionよりは使い勝手良さそうな気もする。

感想

こういうのがあるとなるべくifとかtryを使わずにパターンマッチだけで実装できて気持ちが良い。

リッチなデータ構造を扱うためのGemを作ってみた

以前、StructをRubyで実装するというのをやった。

じゃあ次は、ということでEnumとAlgebraic Data Type(ADT)に相当するようなものが欲しいな〜と思ってGemとして作ってみた。

EnumRuby言語標準としては用意されていないが、RailsにはActiveRecord::Enumというものがあり、カラムをenum的に管理しつつ便利メソッドを山程生やしてくれるものがあるが、そういうのじゃなくてデータ構造としてenum欲しいんだよねという気持ち。 そうなるといわゆるADTみたいなやつ、Scalaでいうところのsealed trait + case class(object)のあれがわりと好きなので欲しくなる。

といっても静的にパターンマッチの網羅性チェックとか出来ているわけでもないしどれくらい嬉しいかは不明。 EnumEnumとして、ADTをADTとして明示できるようにしたかっただけかも知れない。

使い方

READMEに書いてあるがここにも書く。

まずrequireしておく。

require 'rstructural'

Struct

まずStruct。名前をずらすためにRstructとしてある。 これは前回の記事のやつ。

module StructSample
  Value = Rstruct.new(:value)

  puts Value.name #=> 'StructSample::Value'
  puts value = Value.new(100) #=> 'StructSample::Value(value: 100)
  puts value == Value.new(100) #=> true
  puts value.value == 100 #=> true

  Point = Rstruct.new(:x, :y) do
    def message
      "Point: [#{x}, #{y}]"
    end
  end
  p = Point.new(1, 2)
  puts p #=> StructSample::Point(x: 1, y: 2)
  puts p.message #=> Point: [1, 2]

  case Point.new(1, 2)
    in Point[x, 2]# pattern match (Ruby 2.7~)
    puts "here! x: #{x}" #=> here! x: 1
  else
    raise
  end
end 

0個以上の属性を持つデータ構造を定義できる。 0個の属性を許す、という点でRuby標準のStructとは異なる、はず。

EOF = Rstruct.new
puts EOF #=> EOF
puts EOF.new #=> EOF
puts EOF.new == EOF.new #=> true

to_s(inspect)した結果が同じなので分かりづらいがnewするとインスタンスにはなっている。

Enum

例としてHTTPステータスコードenumとして定義してみる。 extend Enumでこれはenumですよという表明になり、enumメソッドが生える。

module EnumSample
  module Status
    extend Enum

    OK = enum 200
    NotFound = enum 404
    InternalServerError = enum 500 do
      def message
        "Something wrong"
      end
    end
  end

  puts Status::OK #=> EnumSample::Status::OK(value: 200)
  puts Status::OK.value #=> 200
  puts Status::InternalServerError.message  #=> Something wrong

  case Status.of(404)
  in Status::NotFound
    puts "NotFound!!!" #=> NotFound!!!
  else
    raise
  end
end

enum <Value>という雰囲気で列挙するとクラス変数としてenumを保持しているのでStatus.of(value)で取れる、というところが少し便利。 見つからなかったらnil

一応、値の重複排除をやっていて

module Status
  extend Enum
  OK = enum 1
  NG = enum 2
  KO = enum 1
end

みたいに同じ値でenumを定義しようとすると、

Traceback (most recent call last):
        3: from sample.rb:36:in `<main>'
        2: from sample.rb:59:in `<module:EnumSample>'
        1: from sample.rb:63:in `<module:Status>'
/path/to/enum.rb:12:in `enum': Enum '1' already defined in EnumSample::Status::OK (ArgumentError)

という感じのエラーがraiseされて安心!

ADT

続いては代数的データ構造。 extend ADTでこれはADTですよという表明になり、constdataメソッドが生える。 constは属性を持たない単一オブジェクトで、dataは1つ以上のフィールドを持つデータ型。 Scalaでいうところのobjectcase classの使い分け。

いい例が思えないが点と円と長方形を表現してみる。

module AdtSample
  module Shape
    extend ADT

    Point = const
    Circle = data :radius do
      def scale(i)
        Circle.new(radius * i)
      end
      def area
        3.14 * radius * radius
      end
    end
    Rectangle = data :width, :height do |mod|
      def area
        width * height
      end
    end

  end

  puts Shape::Point #=> AdtSample::Shape::Point

  puts Shape::Rectangle.new(3, 4) #=> AdtSample::Shape::Rectangle(width: 3, height: 4)
  puts Shape::Rectangle.new(3, 4).area #=> 12
  puts Shape::Circle.new(5).scale(2).area #=> 314.0

  case Shape::Rectangle.new(1, 2)
  in Shape::Rectangle[1, Integer => j] if j % 2 == 0
    puts "here! rectangle 1, #{j}" #=> here! rectangle 1, 2
  else
    raise
  end
end

なんかそれっぽい感じで定義出来ていそう。 ただ、ADTはADTとして共通のインタフェース(Enum#ofのような)ものが特に無いためStructでいいじゃん、というのはその通り。

じゃあ共通のインタフェースを持てるようにしよう、ということでやってみた。

module Option
  extend ADT
  None = const
  Some = data :value

  interface do
    def map(&f)
      case self
      in None
        None
      in Some[value]
        Some.new(f.call(value))
      end
    end
  end
end

puts Option::None.map { |i| i * 2 } #=> None
puts Option::Some.new(10).map { |i| i * 2 } #=> Option::Some(value: 20)

interface(&block)で共通のメソッドを定義出来るようにした。
とはいえ実装ではパターンマッチするなりが必要になるので別々に実装するでも十分な気もするが、定義が分散しないというメリットもあるので使えなくはなさそう。 ここまで来るとようやく単なるStructを使うだけではないメリットが...あるかもしれない。

実装について

パターンマッチを使いたかったのでRuby2.7.0で実装している。 Gemのrequirementもそう。

前の記事で実装したRstructural::StructEnumADT両方で使いまわしているので新規でなにかテクニックを用いたかというとそうでもない。

まず、EnumやADTの各クラスにおいて独自のメソッドを生やしたいというのは容易に想像がつくのでblockを渡してメソッド定義できるようにした。 そのためにもまたblockが与えられていたらRstructが生成したClassオブジェクトに対してclass_evalするということをしている。 共通インタフェースをinterface(&block)で定義できるようにしてあるやつも結局class_evalしているだけではある。

またenumdataメソッド経由でRstruct.newを実行しているためcallerでクラス名を決定するところが壊れるので__callerキーワードで雑にバイパス出来るようにしている。

ADTについて実装をはりつけてみるとこんな感じ。

module Rstructural::ADT
  def self.extended(klass)
    klass.class_variable_set(:@@adt_types, [])
  end

  def const(value = nil, &block)
    if value
      Rstructural::Struct.new(:value, __caller: caller, &block).new(value)
    else
      Rstructural::Struct.new(__caller: caller, &block).new
    end.tap do |k|
      self.class_variable_get(:@@adt_types) << k
      def k.name
        self.class.name
      end
    end
  end

  def data(*fields, &block)
    Rstructural::Struct.new(*fields, __caller: caller, &block).tap do |k| 
      self.class_variable_get(:@@adt_types) << k 
    end
  end

  def interface(&block)
    self.class_variable_get(:@@adt_types).each do |t|
      case t
      in Class
        t.class_eval(&block)
      else
        t.class.class_eval(&block)
      end
    end
  end
end

constは値を持たせることも出来るし、必須ではない。内部ではそれぞれごとにClassオブジェクトを生成しているので==で別のconst同士を比較するとちゃんとfalseになるので実質singleton object的に扱える。

dataは任意の個数のフィールドを持つタプルを生成する。こちらは同じ型でかつ全フィールドが==trueなら、==trueになるという実装をRstructural::Structでしてある。

感想

Rubyには静的型チェックがなくデータコンテナとしてのClassを定義するのも面倒だしHashがインタフェースになりがちだけど少しでもインタフェースに対して制約をつけるためにはこういうのがあると便利かなとは改めて思った。