haskell-jp / questions #93

なるほど〜
整理して考えるとたしかに!
nameenv に依存してないってだけなのに、それだけで、`AnyBot`の定義のせいで異質なメソッドになっちゃってる感じですね༼;´༎ຶ ༎ຶ༽

(ちなみにpackってなんですか?:exploding_head:)
envが多相的なのは Has Type Class Pattern とかいうしきたりを真似してみたかんじです:sob:
メソッドの呼び出し元によって具体的な`env`の型が変わり得るので多相的にしたい感じです:sob:
QuantifiedConstraintsなんて拡張があるんですね!
なんかへんなとこに`forall`ついてる:exploding_head:
調べながら読んでたんですけど今のところわけわかめです༼;´༎ຶ ༎ຶ༽×999
何がどうなってるのかさっぱりわからないです:shocked_face_with_exploding_head: :joy:
AnyBot :: forall b. (Bot b, forall env. Dep AnyBot env => BotDep b env) => b -> AnyBot

これって一体何を意味してるんですか?:exploding_head::x::one::zero::zero::zero:
> (ちなみにpackってなんですか?:exploding_head:)
AnyBot のような、ある型の値を existential type の値に変換する操作のことですね。AnyBot の適用と読み替えてもらって構わないです

> これって一体何を意味してるんですか?
GADTSyntax 使わず existential type で書き直すなら、
```data AnyBot = forall b. (Bot b, forall env. Dep AnyBot env => BotDep b env) => AnyBot b```
と同じですね。`(Bot b, forall env. Dep AnyBot env => BotDep b env)` の部分は、
* `Bot b` の制約が成り立つ
* 任意の `env` 型について、`Dep AnyBot env` 制約が成り立つならば `BotDep b env` 制約が成り立つ
の両方が成り立つみたいな意味ですね。`forall env. Dep AnyBot env => BotDep b env` の部分が `QuantifiedConstraints` 拡張により表現可能になる制約です
ウ~ン(+_+)

1. AnyBot の型パラメタに env をもたせると、`env` を必要としない name メソッドの呼び出しがえらいことになるから、`AnyBot` に env をもたせない。
2. すると reply メソッドの呼び出しに必要な Dep b env が足りなくなっちゃうから、`AnyBot` にパターンマッチしたときに使える制約に forall env. Dep b env 的なものを入れたい。
3. QuantifiedConstraints 拡張では Dep b env みたいな型族をかけないから、仕方なく BotDep 型クラスを追加して、`instance Dep AnyBot env => BotDep AnyBot env` とかやって BotDep から Dep が得られるようにしてる。
って感じのアイディアで大体あってますか?
むずすぎてむりちゃづけです༼;´༎ຶ ༎ຶ༽
`forall env. Dep AnyBot env => BotDep b env` というのは、気持ち的には `forall env. Dep AnyBot env => Dep b env` と同じですね。これは、

`Dep AnyBot env` 制約が成り立つ場合に、`Dep b env` 制約が成り立つ

つまり、`AnyBot` コンストラクタで pack できる型は、

* `Bot` 制約を満たし
* `AnyBot` の `Dep` 制約 (今回は `HasLogFunc`) だけから、`Dep` に必要な制約を導ける (今回は例えば `Dep MarkovChain env` は `HasLogFunc env` と同じなので、`Dep MarkovChain env` は `HasLogFunc env`、つまり `Dep AnyBot env` の制約から導けます。もし `Dep MarkovChain env` が `(HasLogFunc env, HasWriteRef Task env)` みたいな制約を必要としていた場合、これは `HasLogFunc env` 制約からは導けないので AnyBot コンストラクタでの pack は失敗します)

みたいな条件のものだけということになります。この条件を守っているなら、各ボットの `reply :: Dep b env => ...` は `reply :: Dep AnyBot env => ...` と書いても実装ができるはずなので、`AnyBot` の `Bot` インスタンスが実装できます。そして、上の `AnyBot` コンストラクタで pack される時の条件は `Dep` が成り立つ必要は特に要求していなくて、*もし `Dep AnyBot env` が成り立つならば* `Dep b env` が成り立つことを要求しているだけなので、`Dep` は成り立たなくても pack はでき、`name` も特に `Dep` 制約を必要としないので呼び出せるということになりますね
ああ、それと BotDep 型クラスについてですが、これは本質的なものではなくて QuantifiedConstraints の制約でこういうのを作らないといけないというだけですね。 に制約の内容がありますが、`QuantifiedConstraints` の実装上の制約で forall env. Dep AnyBot env => ...... の部分には type family は書けません。なので、`forall env. Dep AnyBot env => Dep b env` とは書けないので、代わりに Dep b env に相当する型クラス BotDep b env を作ってそれを指定しています。
わあ!そういうことか!完全に理解しました!:serval:
forall env. Dep AnyBot env => BotDep b env ってのは、左辺の Dep AnyBot env の制約が右辺の BotDep b env より厳しい、即ち、`type Dep AnyBot env = ...` で羅列されてる制約の中に、packされようとしている Bot インスタンスの Dep の制約が含まれてることを要求してるんですね!

BotDep 型クラスのあたりもいまいちどういう発想て゛そんなこと考えついたのかわかりません:sob:
go :: Bot b => BotDep b env => b -> String -> RIO env (Maybe String) の部分の => が重なっているのはなぜなのでしょうか?
@taxtu06 has joined the channel
go :: Bot b => BotDep b env => b -> String -> RIO env (Maybe String) の部分の => が重なっているのはなぜなのでしょう
ああ、すいません。癖で書いちゃいましたが、`Bot b => BotDep b env =>` は (Bot b, BotDep b env) => と同じです
takenari.shinohara
@takenari.shinohara has joined the channel
ありがとうございます:arigatougozaimasu:
そうだったんですね!文脈のところもカリー化みたいな感じなことできるんですね!

後もう一つ、`UndecidableSuperClasses` スーパークラスとして型族指定しているのも初めて見ました・・・
調べているんですけどこれもどう考えればいいのかイマイチまだつかめてないです:sob:
@mayoimaimai9 has joined the channel
@aka2tom8bo_slack has joined the channel
わあーごめんなさい:woman-bowing:
一呼吸おいて冷静に考えてみたら全部理解できました༼;´༎ຶ ༎ຶ༽
すごい勉強になりましたありがとうございます!!!🥳:star-struck:
@sevensins0410s has joined the channel
GHC 9.0.0-alpha1 は bytestring-0.10 を使っているようですが、最新の 0.11 が含まれるのは alpha2 からでしょうか?
新しくBotを追加するたびに`allBots`だけでなく`AnyBot`のインスタンス定義まで書き換えなきゃいけないのが少しイヤだったので、自分なりに作ってみたら難しくなっちゃいました:sob:

もっと上手に書くやり方ありませんか?
あと変数名や関数名などももっと良いのありますか?:exploding_head:
@yuki2501 has joined the channel
なるほど, AnyBot を constraint 持てるようにしておくと,後から変えられて確かに便利ですね.HList のところは,単に一々 SomeBotallBots の全てのボットで書くのが面倒という理由で使ってるなら,ビルダをそもそも定義すればいいだけだと思いますね.その例が上に post したやつです
@manami2622 has joined the channel
@escapegoat173 has joined the channel
HListの代わりに、BotとBotの組み合わせを表すBot (Bot a, Bot b) => Bot (a, b) を作ってみるのはどうでしょう?型クラスはこのような一般性のあるインスタンスにおいて力を発揮します(nameがリストを返すようにするなどの変更は必要かもしれませんが)
@usabarashi has joined the channel
ghcupを使ってみています.ghcupをインストールしたのち,これに付随するcabalコマンド(`~/.ghcup/bin/cabal`)で
cabal new-install pandoc

のようにすると,`~/.cabal` 以下にパッケージやコマンドがインストールされますが,これらを
cabal list --installed

としても確認できません.上記コマンドでは`~/.ghcup/ghc/8.8.4/lib/ghc-8.8.4` 以下のパッケージがリストアップされます.~/.cabal 以下に入っているパッケージを確認するにはどうするとよいか,どなたかおわかりでしょうか.使用しているコマンドのバージョンは以下の通りで,OSはmacOS 10.15.7 です.
Haskell関連のファイル(`~/.cabal`, ~/.ghc, ~/.stack等)や,Homebrew等で入れたHaskell関連のパッケージはいったん全部削除してから作業しています.
$ ghcup --version
The GHCup Haskell installer, version v0.1.12
$ cabal --version
cabal-install version 3.2.0.0
compiled using version 3.2.0.0 of the Cabal library 
多分 cabal list --installed が正しくnix-style local build(いわゆる new-install インストールしたりするやつですね)でインストールしたパッケージを正しく認識していないのが原因なんだと思われます。
ただ、いずれにしてもこれで確認できなくとも pandoc コマンドは多分に問題なく使えるでしょうし、 pandoc に依存したプロジェクトを作る場合でもcabalファイルに pandoc を書けば問題なく使えるはずです(必要なバージョンがインストールされてない場合はビルド時に再度インストールされるだけ)。
なので、あまり確認する必要性が感じられていないから cabal list --installed が修正されていないのではないかと推測しています。
(でも紛らわしいので何らかの対応は欲しいですね... :cold_sweat: )
あるいは、 cabal new-install --lib pandoc であればもしかしたら結果が変わるかも知れません。どちらにしても問題なく使えるとはは思いますが
ありがとうございます.たしかに ~/.cabal 以下にインストールされたものはちゃんと使えるのですが,何となく変な感じですよね.
代替案として cabal new-exec (もしかしたら最近のcabalだともう new- は要らないかも)を使って、 cabal new-exec ghc-pkg list はいかがでしょうか?自分がインストール済みのパッケージの一覧をとるときは大抵 ghc-pkg list を使いますね。(ぶっちゃけ cabal list コマンド自体初めて見たかもしれない... :sweat_smile: )
https://haskell-jp.slack.com/archives/CR2TETE5R/p1605992173052400
この一連のスレッドに回答していて気になったんですけど、ghcって、アセンブリーを生成してからランタイムなどをリンクさせるためにgcc(と、gccがラップしているリンカーやアセンブラー)を呼んでオブジェクトコードを生成しているんですかね?
GHCでのコンパイル時には、ターゲットCPUがx86であれば、まず、GHCが直接にx86ネイティブのアセンブリコードを生成します。 (ターゲットCPUがArmなどの場合であれば、ネイティブアセンブリでなくLLVMコードを生成します。)
アセンブリコードをアセンブルしてオブジェクトコードを生成するには、OSプラットフォームのアセンブラを使用します。(通常そのアセンブラは、GCCやClangなどのコンパイラ経由で呼び出されます。)
オブジェクトコード間の最終リンクについては、OSプラットフォームごとのリンカ(goldやlld)が呼ばれます。
GHCがどういう外部ツールを使用するかは、`ghc --info` コマンドで見えます。
あと参考に、その辺の処理を担当しているのは、GHCの compiler/GHC/Driver/Pipeline.hs 付近のコードになります。
@yasufrom24 has joined the channel
ありがとうございます!なるほど!`HList`定義しなくてもビルダーで書けばかなりスッキリシンプルになりますね!
ただその代わり値からの型推論はできなくなっちゃうみたいですね…
どうしよう…
わあありがとございます!たしかに!タプルで十分できそうですね!
そういうインスタンス定義するだけで
(HogeBot,(FugaBot,PiyoBot))とかすればリストみたいにできちゃいますね〜ふむふむ…
@han has joined the channel
本質的には変わりませんが、HListにこだわるのであればHListをBotのインスタンスにするのも一興ですね
わああたしかに良さそうですね!:owl:

HList にしてるのは
allBots = MarkovChain :+: Shiritori :+: HNil

とかから型推論させてみたいからです〜
まあでもこれだと型推論はできても
allBots :: HList HasLogFunc

とか書いたときにエラーになっちゃうんですよね〜
type family (:&&:) c1 c2 :: Type -> Constraint where
  (:&&:) c1 CEmpty = c1
  (:&&:) CEmpty c2 = c2
  (:&&:) c c = c
  (:&&:) c1 c2 = c1 :&: c2

data HList :: (Type -> Constraint) -> Type where
  HNil :: HList CEmpty
  (:+:) :: (Bot b) => b -> HList c -> HList (Dep b :&&: c)

とかにすればある程度 (`HList (HasLogFunc :&: (HasLogFunc :&: CEmpty))`とか) はエラーじゃなくなるんですけど、もっとちゃんとやるには型レベルのnub関数みたいなの作らなきゃいけなくなってしんどいです・・・
manaさんの書いてくれたコードの感じぐらいに落ち着かせるのがいいんですかね〜:eyes:
@yudai.tnb has joined the channel
@sh9temp has joined the channel
ふと思ったんですけど、麻那さんのプログラムの
go :: Bot b => BotDep b env => b -> String -> RIO env (Maybe String)

ってとこ、どうして普通の Dep b env じゃ動かないんでしょうか?
github actions で CI を回している人はいますか?
Haskell CIを実行すると MIN_VERSION_conduit でひっかかるのですが、理由が分かる人はいませんか?

https://github.com/kazu-yamamoto/wai-app-file-cgi/runs/1477613399
手元では落ちないんですよね?となると、cabal のバージョンが古いor新しいとか??
https://github.com/actions/setup-haskell
cabal-version で手元と同じバージョンにすれば動作しないですかね。。。?
cabal は 3.2 で、手元も github actions も同じです。。。
これは、単に cabal v2-test で doctest がうまく動かない問題でした。CI で doctest を実行するのを止めて解決しました。
ちょっと雑なのでパスちゃんと取れる手段があるならそのほうがいいけど
build-type: Customを許容できるのであれば https://hackage.haskell.org/package/cabal-doctest がおすすめです
自分で定義した型の値を他の直積型のフィールドにする際にUNPACKプラグマをつけるかどうかで違いは出てきますか?
例えば以下のような状況です
data Foo = Foo {-# UNPACK #-} !Int {-# UNPACK #-} !Int
data Bar = Bar {-# UNPACK #-} !Int !Foo

data Foo = Foo {-# UNPACK #-} !Int {-# UNPACK #-} !Int
data Bar = Bar {-# UNPACK #-} !Int {-# UNPACK #-} !Foo

Fooがどう定義されているかによっても変わってくるのでしょうか?