10 ポイント 投稿者 ilotoki0804 2024-06-01 | 14件のコメント | WhatsAppで共有
  • fieldenumは値を持つ(インスタンス化できる)enumです。
  • Rustのフィールド付きenumをきれいにサポートします。
  • 関数型プログラミングの純粋性とPythonにおける実用性のあいだで、バランスを取ることを目指しました。
  • デフォルトで、Noneの代替となるOptionや、例外の代替となるBoundResultをサポートしています。
  • 完全にテストされています。
  • まだ英語ドキュメントは手薄ですが、今後少しずつ充実させていく予定です。
  • Issue、PR、star など、さまざまな形での支援を歓迎します。

14件のコメント

 
savvykang 2024-06-02

dataclass の union 型のほうがより良いのでは、と思うのですが、宣言文が短いこと以外にはあまり利点が分かりません。fieldenum に特別に優れている点はあるのでしょうか?

 
ilotoki0804 2024-06-03

宣言が短く簡潔で、必要な部分だけがあることも大きな利点です。
たとえば、

from fieldenum import fieldenum, Variant, Unit  
  
  
@fieldenum  
class Message:  
    Quit = Unit  
    Move = Variant(x=int, y=int)  
    Write = Variant(str)  
    ChangeColor = Variant(int, int, int)  

上の fieldenum を dataclass で実装しようとすると、次のように書かなければなりません。

from dataclasses import dataclass  
from typing import Self  
  
  
class Message:  
    Quit = Self  
    Move = Self  
    Write = Self  
    ChangeColor = Self  
  
  
class QuitMessageClass(Message, metaclass=ParamlessSingletonMeta):  
    pass  
  
QuitMessage = QuitMessageClass()  
  
  
@dataclass(frozen=True, kw_only=True)  
class MoveMessage(Message):  
    x: int  
    y: int  
  
  
@dataclass(frozen=True)  
class WriteMessage(Message):  
    _0: str  
  
  
@dataclass(frozen=True)  
class ChangeColorMessage(Message):  
    _0: int  
    _1: int  
    _2: int  
  
  
Message.Quit = QuitMessage  
Message.Move = MoveMessage  
Message.Write = WriteMessage  
Message.ChangeColor = ChangeColorMessage  

コードは長くなって見づらくなり、ミスをする可能性も高くなりますし、コードがきれいだとは感じにくいですよね。

もちろん、このように書いたとしても fieldenum が提供するさらに多くの機能(ジェネリクス、repr、__fields__ など)は利用できません。

したがって、これらすべてを実装してまとめた fieldenum があれば、はるかに便利です。

そのほかにも、 パートの内容も参考になると思います。

 
savvykang 2024-06-03
from dataclasses import dataclass  
  
@dataclass(frozen=True) # repr True by default  
class QuitMessage:  
    pass  
  
@dataclass(frozen=True, kw_only=True) # repr True by default  
class MoveMessage:  
    x: int  
    y: int  
  
@dataclass(frozen=True) # repr True by default  
class WriteMessage:  
    _0: str  
  
@dataclass(frozen=True) # repr True by default  
class ChangeColorMessage:  
    _0: int  
    _1: int  
    _2: int  
  
Message = QuitMessage | MoveMessage | WriteMessage | ChangeColorMessage  
  1. dataclass はデフォルトで repr の実装をサポートしています
  2. dataclasses.fields はフィールド定義に関する実行時情報を提供します
  3. ジェネリクスは typing モジュールにより 3.5 から、構文糖衣は 3.12 からサポートされています
  4. Messages 名前空間の場合、モジュールとして実装できます

それでも、class 定義に必要なボイラープレートコードがない点や、enum と class をひとつのインターフェースとして使える点は利点になりそうですね。詳しい説明ありがとうございます

 
savvykang 2024-06-03

https://stackoverflow.com/a/47784683

このように構造体を表現しようとする試みはいろいろありましたが、結局のところ、これはPythonの限界であり欠点だと言える気がします。ADT(algebraic data type)には学校の授業でOCamlを通じて初めて触れましたが、仕事ではこのように真似をするしかないというのは少し残念でもあります。

ilotokiさんが作られたライブラリは、ADTに最も近い事例と見てよさそうです。いつか標準ライブラリに含まれ、広く使われるようになるとよいですね。

 
ilotoki0804 2024-06-03

Message の実装を Union で行うと、メソッド継承を利用できません。たとえば

from fieldenum import fieldenum, Variant, Unit  
  
  
@fieldenum  
class Message:  
    Quit = Unit  
    Move = Variant(x=int, y=int)  
    Write = Variant(str)  
    ChangeColor = Variant(int, int, int)  
  
    def process(self):  
        ...  

上のように .process メソッドを追加すると、すべてのバリアントで .process() メソッドを使えます。

# Message.process() メソッドを各バリアントで利用可能  
Message.Quit.process()  
Message.Move(x=123, y=456).process()  
Message.Write("hello, world").process()  
Message.ChangeColor(123, 000, 89).process()  

また、私が説明した repr は「その enum のバリアントとしての repr」を意味しています。
たとえば fieldenum が repr をラップして呼び出すと、次のように実行されます。

print(repr(Message.Move(x=123, y=456)))  # Message.Move(x=123, y=456)  

カスタム __repr__ がないと、Message enum の下位バリアントであることが表現されません。

Quit はユニットバリアントなので、呼び出しなしで使います。

Message.Quit  # 別途呼び出し(例: `Message.Quit()`)なしで利用可能  

また、呼び出しが必要なバリアント種別である fieldless バリアントの場合は、シングルトンとして is 演算子で確認できます。

from fieldenum import fieldenum, Variant, Unit  
  
class WithFieldless:  
    Fieldless = Variant()  
  
assert WithFieldless.Fieldless() is WithFieldless.Fieldless()  

fieldenum を使うと、このように見落としやすいさまざまな実装ディテールを自動的に面倒見てくれます。

 
wyatt216 2024-06-02

もしPyCon Koreaで発表していただけたらどうかと、ご提案してみます。とても面白く拝見したので、制作の過程であったお話や説明を直接お聞きしたいです!

 
ilotoki0804 2024-06-02

PyConで発表できたら本当に光栄だと思います。自分がやりたいと言ったからといって実現できるかは分かりませんが(^^;)、考えてみます。

 
kayws426 2024-06-01

それと、英語のREADMEでもOptionの例が説明されているとよいですね。
Optionは理解しやすく、親しみやすくアプローチできるはずです。ドキュメント上の説明順でも、Optionを先に説明したほうがよりよい気もします。

 
ilotoki0804 2024-06-01

英語のドキュメントはまだ準備中のため、少し内容が薄いです……。韓国語ドキュメントが十分に成熟したら、英語に翻訳しようと思っています。あるいは、関連するPRも歓迎します!
私もまず Option を先に紹介するほうがよさそうだと思います。修正します。

 
kayws426 2024-06-01

おお、すごいですね!!
リンクしていただいた韓国語ドキュメントのサンプルコードに修正点があります。

from fieldenum import fieldenum, Variant, Unit, unreachable  
from fieldenum.enums import Option  
  
def hello() -> Option:  # GOOD  
    return Option.Some("hello")  
  
def print_hello(option: Option):  # GOOD  
    print(value.unwrap()) #!!!!! ここは value ではなく option であるべきに見えます !!!!!#  
  
value = hello()  
print_hello(value)  
 
ilotoki0804 2024-06-01

お知らせいただきありがとうございます。修正しました!

 
ilotoki0804 2024-06-01

GNとして投稿すべきでしたが、間違えて通常投稿にしてしまいました;;

 
moderator 2024-06-01

修正しておきました。

 
ilotoki0804 2024-06-01

ありがとうございます〜