haskell-jp / questions #102 at 2022-09-23 21:07:20 +0900

現在作ろうとしているツールの一環として、「一定の文字列候補の中から、入力された文字列に一致する文字列をフィルタリングする」機能を作ろうとしています。
(最終的に作りたいものはrofiやdmenuと似たようなものです、というか具体的にはrofiクローンのようなツールを作ろうとしています)

このツールでは「文字列の一致」の判定方法をFuzzyやGlob、regex等複数から選べるようにしたいため、ツール全体のステートを維持する型の中に判定用の関数(matcherと呼ぶことにします)を含めたいです。

実際に今考えていたのは以下のような型です:

data AppModel = AppModel { _typing :: Text
           , _candidates :: Zipper Candidate
           , _configFile :: FilePath
           , _matcher :: (T.Text -> T.Text -> Bool) -- ^ ここのこと
           } deriving (Eq)

しかし、 AppModel はUIライブラリ()の都合で Eq インスタンスを持つ必要があるのですが、 Eq (a -> a) のインスタンスが存在しない故に定義が出来なくなってしまっています。

実装の仕方を自分なりに複数考えてみたのですが、どれも微妙でどれを選ぶべきなのか、またより良い方法は無いのかで悩んでいます。
何かしら定石みたいなものがあれば教えていただければ幸いです :pray:
(自分で考えたものはスレッドに書き込みます)
【 1. matcherが同一であるかを気にしない Eq のインスタンスを手書きする】

一番単純かつ簡単な方法だと思います。しかし、この時の Eqのインスタンスは AppModelの変更を感知するために必要であると思われるので、中途半端なインスタンスを書くのは良くないような気がしています。

 haskell
instance Eq AppModel where
  m1 == m2 = all [ m1^.typing ==  m2^.typing
       , m1^.candidates == m2^.candidates
       , m1^.configFile == m2^.configFile
       ]

【 2. matcherにラッパー型を用意し、識別子を付けてそれで区別する】

Matcherの名前と実際の関数を含む Matcher 型を用意し、その Eqインスタンスは名前のみ一致を見るようにします。

一番無難そうだなとは思うのですが、矢張り MatcherEqインスタンスが中途半端なのと名前と関数の紐付けが緩い(同じ名前で別の関数とかも作れてしまう)ので、本当にこれで良いのだろうか…と思っています。

data Matcher = Matcher { _matcherName :: T.Text
             , _matcher :: T.Text -> T.Text -> Bool
             }
makeLenses ''Matcher

instance Eq Matcher where
  m1 == m2 = (m1^.matcherName) == (m2^.matcherName)

-- @match prefixMatcher "search term" "Candidate"@ のようなイメージ
match :: Matcher -> (T.Text -> T.Text -> Bool)
match = view matcher

prefixMatcher :: Matcher
prefixMatcher = Matcher "prefix" T.isPrefixOf

data AppModel = AppModel { _typing :: Text
           , _candidates :: Zipper Candidate
           , _configFile :: FilePath
           , _matcher :: Matcher
           } deriving (Eq)

拡張性が減ってしまいますが私なら
data Mathcer = Fuzzy | Glob | Regex

みたいな型を作って、実際にmatchするときはそれに対するinterpreter( Matcher -> T.Text -> T.Text -> Bool のような型の関数)を作るような方法を採りますね。
成程確かに...!!
Matcherに拡張性持たせる必要もあまりないと思うので、それが一番良さそうですね。
関数として捉えすぎてました...難しく考えすぎていた...
ありがとうございます!