Techioz Blog

Sorbet を使用して同じ構造体を定義するにはどうすればよいですか?

概要

全て! 汎用の成功タイプと汎用の失敗タイプを結合した汎用の結果データ型を定義したいと考えています。 TS では同じことが次のようになります。

type Success<T> = {
    value: T
}

type Failure<E> = {
    error: E
}

type Result<T, E> = Success<T> | Failure<E>

または Rust では次のようになります。

enum Result<T, E> {
   Ok(T),
   Err(E),
}

しかし、残念ながら、Sorbet タイプの注釈を使用してそれを行う方法は見つかりませんでした。 それは可能ですか?

どうもありがとうございます。

私が見つけた最も近いものは gem dry-monad の型定義でしたが、Success クラスと Failure クラスの両方が両方の type_members を再定義する必要があるため、ハックのように見えるので、実際には私が望んでいることではありません。

説明

この例を見てみましょう: https://translate.google.com/translate?hl=ja&sl=en&tl=ja&u=https://gist.github.com/woarewe/f4f3ee502f35c4c0d097695a52031b14 私の目標は、次のような署名を定義することです。

sig { params(value: Integer).returns(Core::Type::Result[Integer, String]) }

しかし、あるクラスから別のクラスにジェネリック型を渡す方法がないように見えるため、それは不可能のようです。

私が見つけた唯一の回避策は、関数定義内で特定の型を使用して共用体を構築することです。

sig { params(value: Integer).returns(T.any(Core::Type::Success[Integer], Core::Type::Failure[String])) }
def zero?(value)
  if value.zero?
    Core::Type::Success.new(value)
  else
    Core::Type::Failure.new("It is not zero")
  end
end

最終的な解決策は次のようになります

# typed: strict
# frozen_string_literal: true

module Core
  module Type
    class Success
      extend T::Sig
      extend T::Generic

      ValueType = type_member

      sig { returns(ValueType) }
      attr_reader :value

      sig { params(value: ValueType).void }
      def initialize(value)
        @value = value
      end
    end

    class Failure
      extend T::Sig
      extend T::Generic

      ErrorType = type_member

      sig { returns(ErrorType) }
      attr_reader :error

      sig { params(error: ErrorType).void }
      def initialize(error)
        @error = error
      end
    end
  end
end

extend T::Sig

sig { params(value: Integer).returns(T.any(Core::Type::Success[Integer], Core::Type::Failure[String])) }
def zero?(value)
  if value.zero?
    Core::Type::Success.new(value)
  else
    Core::Type::Failure.new("It is not zero")
  end
end

result = zero?(0)
case result
when Core::Type::Success
  p result.value
when Core::Type::Failure
  p result.error
end

解決策

私は最近、あなたが探しているものを正確に実装する gem の開発を手伝いました。 https://github.com/maxveldink/sorbet-result

この gem を使用すると、コードを次のように書き換えることができます。

sig { params(value: Integer).returns(Typed::Result[Integer, String]) }
def zero?(value)
  if value.zero?
    Typed::Success.new(value)
  else
    Typed::Failure.new("It is not zero")
  end
end

result = zero?(0)
if result.success?
  p result.payload
else
  p result.error
end

この gem は、チェーンやその他の優れた機能もサポートしています。