haskell-jp / questions #92

そのやり方をしたら今度は reply b msg の部分で型推論できなくてエラーになっちゃいましたorz
どうすれば・・・
data AnyBot env = forall b. (Bot b, Dep b env) => AnyBot b
instance Bot (AnyBot env) where
  type Dep (AnyBot env) a = (a ~ env)
  name (AnyBot b) = name b
  reply (AnyBot b) msg = reply b msg

これで通りますがもしかしてDep b envが足りなかったのでしょうか?
わあ!まさにその通りでした!ありがとございます!!

さっぱりどうしていいかわからなかったのですが、どのように思考したらそうなるんですか!?
たけのこは型推論の流れみたいなものを学べばこれを理解できるようになるんでしょうか?༼;´༎ຶ ༎ຶ༽
AnyBotにパターンマッチしたとき使えるのは、AnyBotの宣言で量化している制約のみで、一方replyが要求する制約はDep b envを含んでいるので、そこに食い違いがあることがわかります。一般論として、何が要求されていて、それを満たすためにはどこを変えればよいかをエラーメッセージなどなら見極めることが大切だと思います
ライブラリーを作るときに、ライブラリーで提供する型は、特に理由がなければ GenericNFData (あと、多分テストで使う都合で EqShow も)のインスタンスにしておいた方がいいでしょうか?
他にもこれはインスタンスにしておくと助かるなぁ、という型クラスがあれば教えてください!
コンストラクタを隠したいなどそうしない明確な理由がなければそうすべき、という立場です。 Proxy や、  aesonのValue型あたりが参考になると思います
確かに状況はこの通りですけど、間もなくJsaddleのアプリがWebGHC・Wasmで動作させることができると思います。例えば「」「
自分が少し関わっているパッケージにこんなpull requestが来て気になったのですが、いくつ前のGHCまでサポートするか、についてのガイドラインってどこかにありましたっけ... 以前誰かがそんなことをおっしゃっていたような...
https://github.com/krdlab/haskell-oidc-client/pull/46
最低限、major を3つですが、GHC 8 はすべてサポートする方が親切でしょうね。
ああー、kazuさんでしたか。その「majorを3つ」というのはどこかに書いてましたっけ...?
ありがとうございます!まぁ、習慣的なものでしょうし、今回は軽微なものですからきっとマージしてくれるでしょうけども、何かしら引用したくなったときのための覚えておきます。 :memo:
どこかに正式な文章があった気がするけど、今は探し出せませんでした。
ただし、それは一年に一回GHCのメジャーバージョンが出てたころの文章で、その後正式な合意があるかは分かりません。まぁ、3年分ぐらいサポートすればいいでしょう。
自分は“debian stableに入ってるghcバージョン以降“を1つの基準にしてます.
以下のURLが元の`3-Release-Policy`の定義ページだったと思いますが、TracからGitLabへのWiki移行でprime系を移行できていないようです。
https://prime.haskell.org/wiki/Libraries/3-Release-Policy
なるほど!ありがとうございます!
型族とかで頭が混乱していて存在型の制約がパターンマッチして使えるようになることとかを忘れてました:scream_cat:

今度は Bot のリストとかも Bot のインスタンスにしたいなと思って以下のようにインスタンス宣言を追加したら、またどうしていいかわからないエラーが出ちゃいました:sob::x::one::zero::zero::zero:

instance (Bot (b (env :: *)), Dep (b env) env, Functor t, Traversable t) => Bot (t (b env)) where
  type Dep (t (b env)) a = a ~ env
  name _ = "Bot Pool"
  reply bs msg = join . L.find isJust <$> traverse (`reply` msg) bs

Conflicting family instance declarations:
      Dep (AnyBot env) a = a ~ env -- Defined at src/Bot.hs:49:8
      Dep (t (b env)) a = a ~ env -- Defined at src/Bot.hs:54:

考えてみてもいい解決策が思いつかないです༼;´༎ຶ ༎ຶ༽
どうかお助けを༼;´༎ຶ ༎ຶ༽
前の例をコピーするのはあまり筋がよくないですね。どのような型がインスタンスになるかもう一度考えてみましょう
もしかして AnyBotBot のインスタンスにするのをやめて、`AnyBot` を FunctorTraversable にしちゃうのがいいんでしょうか…?
とすると`name` メソッドなどを`"Bot Pool"` じゃない名前にしたい場合どうしたらいいでしょうか?
:boom:༼;´༎ຶ ༎ຶ༽:boom::oil_drum:三 :oil_drum:三
「`Bot` のリストとかも Bot のインスタンスにしたい」とのことなので、具体的にどんな型をインスタンスにしたいか列挙して、それらがどう実装できるかを試してみるのが良いと思います
instance Bot b => Bot [b]
とか
instance Bot b => Bot (Vector b)
みたいな感じでしょうか?
インスタンス宣言ってあまり一般的に書かない方がいいんでしょうか?
instance Bot b => Bot [b] まさにその通りです。`instance Applicative f => MyClass f`のような宣言はあらゆる型に対してインスタンスを定義することになるので、多くの場合はアンチパターンです
なるほど〜
でもどうしてあらゆる型に対するインスタンスを定義することがアンチパターンに近づきがちになっちゃうんでしょうか?
できるだけ一般的な形で書いた方がより便利そうな気が:hatched_chick:
全ての型に対して同じ定義で済むなら、型クラスである必要がないためです。たとえば class Summable a where sum :: a -> a に対して instance (Foldable f, Num a) => Summable (f a) where sum = foldl' (+) 0 という一般的な定義をしても、それは最初から型クラスを使わずに sum = foldl' (+) 0 と定義するのと変わらないのです
わぁ!
言われてみれば確かに!
頭良すぎですか?:muscle::skin-tone-2:
もしかしてこういうのって有名な話でどこかに書いてあったりするんでしょうか!?
ありがちな誤解ではあるものの、どこかに書いてあるかというとちょっと心当たりがないです。Haskellでやるべきではないことの一覧みたいなのがあるといいですね
なるほどーいっぱい勉強になりました!
ありがとです!
@suotani has joined the channel
haskellのpreludeはデフォルト以外ではどれを使うのが良いのでしょうか?
https://haskell.e-bigmoon.com/posts/2018/05-23-extended-prelude.html
@ has joined the channel
私もほとんど(それもrioだけしか)採用したことがないんですけど、 http://hackage.haskell.org/package/rio は結構よくできてますし、人気もありますね。
だずげでぐだざい゛:sob::sob:

allBots :: HasLogFunc e => [AnyBot e]
allBots = [AnyBot MarkovChain, AnyBot Shiritori]

allBotNames :: forall e. HasLogFunc e => [String]
allBotNames = map name (allBots :: [AnyBot e]) 

という定義をしたんですけれども、
allBotNames を呼び出す方法がわからないです:sob:

どうやって呼び出せばいいんでしょうか?
それとももしかして根本的に設計がおかしかったりするんでしょうか?
恐らく欲しいのはこの機能でしょう。
https://ja.wikibooks.org/wiki/Haskell/%E5%AD%98%E5%9C%A8%E9%87%8F%E5%8C%96%E3%81%95%E3%82%8C%E3%81%9F%E5%9E%8B

https://kazu-yamamoto.hatenablog.jp/entry/20081024/1224819961 の3つめの「部分型多相(inclusion,subtyping)」が割と簡潔に使い方を紹介しています。
ただ、正直なところ型クラスを使わずにrecord of functionにしてしまった方がシンプルで柔軟になる気がします。
ここまでうっすらそう思っておきながら黙ってしまっていて恐縮ですが...
これって、`HasLogFunc` の制約は必要ですか? ちょっと今までの話を追えてないですが、
allBots :: [AnyBot e]
allBots = [AnyBot MarkovChain, AnyBot Shiritori]

allBotNames :: [String]
allBotNames = map name (allBots :: [AnyBot ()])

とかで良かったりしないですかね? (今までの話をなんとなく見てると、`reply` で初めて Dep (AnyBot env) env が成立することが必要な気がしますが)
後、`AnyBot` でやりたいことって、実は Bot のインスタンスにすることではなく、全てのボットを同じ型で管理して、名前が取れることだけだったりしないですかね?その場合、
instance Bot (AnyBot env)

は多分いらなくて、
allBots :: [AnyBot e]
allBots = [AnyBot MarkovChain, AnyBot Shiritori]

allBotNames :: [String]
allBotNames = map (\(AnyBot b) -> name b) (allBots :: [AnyBot ()])

という風に定義するだけでよいと思いますが (AnyBot 自体を bot にしたい感じなんですかね?)
ああ、`HasLogFunc` が必要になってるのは、`forall b. (Bot b, Dep b env) => AnyBot b` で存在型の pack 時に Dep b env を要求してるからか...
後は、
instance HasLogFunc Logger

みたいなインスタンスを持つ型 Logger があるなら、一応は
allBotNames :: [String]
allBotNames = map name (allBots :: [AnyBot Logger])

とかでもいける気がしますね
まあ、igrep さんの言う通り、最終的にしたいことは普通のデータ型でも実現できそうな気がしますね。多分拡張性を考えて、型クラスを使いたいという動機はあると思いますが、ひとまず bot を幾つか作ってみてそれに見合うような抽象化を考えて、置き換えていった方がスムーズに進むと思いますね。今回のケースだと、
data Bot = Bot
  { name :: String
  , reply :: HasLogFunc e => String -> RIO env (Maybe String)
  }

markovChain :: Bot
markovChain = Bot { ... }

shiritori :: Bot
shiritori = Bot { ... }

anyBots :: [Bot]
anyBots = [markovChain, shiritori]

allBotNames :: [String]
allBotNames = map name allBots

みたいな感じですかね
実は一番初めはそうやってレコード型に関数を持たせて実装してました:woman-girl-girl:
だけどそれだとexpression problemがあって嫌だし、それにいつもそういう方針の実装ばっかなので、あえて今回は型クラスを使った方針にしました༼;´༎ຶ ༎ຶ༽
なのでレコード型に関数持たせる方針じゃなくて型クラス使いたいです༼;´༎ຶ ༎ຶ༽
おっしゃるとおり、
instance HasLogFunc LogFunc

というのが定義されていました!
なので
allBotNames :: [String]
allBotNames = map name (allBots :: [AnyBot LogFunc])

とできました!:star2:

できたはいいけど、全然使っていない謎の具体的な型 LogFunc が出てくるのはなんかすごく変な感じします:sob:
やはり設計がおかしいんでしょうか・・・
allBots :: [AnyBot e]
allBots = [AnyBot MarkovChain, AnyBot Shiritori]

って方法だと AnyBot MarkovChain のところで必要な HasLogFunc がなくてエラーになっちゃいます:sob:
どうすれば:sob:
同じリストに入れたいのであれば、 e も存在型で包んでしまう、ですかねぇ(本当にそれで問題ないのか、他に問題が発生しないか自信がないです
すべてのインスタンスを使ってallBotsを定義するのであれば、当然あらゆるインスタンスに対応したenvが必要になり、それを回避することに大きなメリットはないと思います。一応元の質問についてですが、forall a. Tのような型を持つ値は、TypeApplications拡張を使えば呼び出せます
多分,元々は name を呼び出すのに env に関する制約が必要なかったのに,`AnyBot` に関しては必要になるところが問題なんですかね?元々のやり方を踏襲するなら,基本的には
data AnyBot env = forall b. (Bot b, Dep b env) => AnyBot b
instance Bot (AnyBot env) where
  type Dep (AnyBot env) a = (a ~ env)
  name (AnyBot b) = name b
  reply (AnyBot b) msg = reply b msg

AnyBot の定義とインスタンス定義が問題なんだと思いますね.`Bot` クラスの Dep は本来,`reply` に必要な env の制約を表してたんだと思いますが,それが AnyBot の pack 時に押し付けられて,`AnyBot` に対する Dep は型合わせのための制約しか入ってないのが問題なんだと思います
そもそもですが,おそらく MarkovChain / Shiritori などはユーザが定義するものですが,`AnyBot` はその定義されたボットを使う側,つまりボットサーバの実装側が定義するものだと思うので,別に env が多相的になっていなくても問題ないと思いますね.なので,実装側で使う AppEnv のような型に対して,
data AppBot = forall b. (Bot b, Dep b AppEnv) => AppBot b

instance Bot AppBot where
  type Dep AppBot env = env ~ AppEnv
  name (AppBot b) = name b
  reply (AppBot b) msg = reply b msg

みたいなのが本来欲しいものだったりしないですかね?
それはともかくとして,元々のものを無理やり実現したいなら,`QuantifiedConstraints` を使えばできることはできると思いますね(変なボイラープレートが増えますが)
わぁーありがとございます:heart_eyes_cat:
e も存在型にってどうするんでしょう
考えてたら頭爆発しました:exploding_head::skull_and_crossbones:
たしかに!
allBotsを定義するときには [AnyBot MarkovChain, AnyBot Shiritori] などと列挙するので、それらが必要としてる`env`の制約を書くのを回避する必要はあまりなさそうですね:owl:

TypeApplications ならできるんですね!
なんかでもその関数を呼び出してるとろでも明示的に型適用して、それを呼び出してるところでも明示的に型適用して、…ってなって大変そうな気が:jack_o_lantern:
そういうものなのでしょうか…?