haskell-jp / questions #65

--write-ghc-environment-files=alwaysでいけました。ありがとうございました。
おそらく無理だろうと思うのですが、何か妙案や回避策があったら教えてください :pray:
↓みたいな定義の型の値を、IOをなるべく行わないテスト用に作りたくなったとします。

data Client = Client {
    clientConnection :: SomeConnectionToExternalService
  , clientState :: IORef SomeState
}


このうち、 clientState の変化についてテストするために Client の値を作ろうとしたのですが、
clientConnection については、 IO を伴わずに生成するのが難しいので、 error 関数を使って無効な値で埋めておき、テスト時には一切参照しない、ということにしました。

しかし、この clientConnection フィールドが StrictData 拡張などを使って Client 定義時に正格に評価されるフィールドとして定義されていた場合、必ず error 関数が評価され、テストが実行できません。

こういう場合において、 ClientSomeConnectionToExternalService型定義を変えずに clientConnectionundefined などの簡単に作れる値で埋めつつテストを実行することはできないでしょうか?

あるいは、そもそもそうならないようにうまいこと設計するのがいいんでしょうけども...
頭の中でうまく一般化できません。
よくあるオブジェクト指向言語で、 stub や mock を作る問題と本質的に同じ問題だと思うのですが、Haskellでどうそれを達成したものか...
何もしない SomeConnectionToExternalService をテスト時にIO伴う形で作ってそれを入れておくというのはどうでしょう。 SomeConnectionToExternalService がどんな型なのかわからないとなんとも言えませんが
確かに。 SomeConnectionToExternalService の側を作りやすいように変えるのが一番応用が利いて良さそうですね... 現行の実装だと
- withSomeConnectionToExternalService みたいないわゆるwith系の関数を伴わないと作れない
- 作成時にバックグラウンドで動くスレッドを作る

といった重い部分があるので避けていたのですが、そっちから変えましょう。
unsafeCoerceで誤魔化すと上手く行ったりしないでしょうか(試してないので分かりませんが)
(Haskeller型大好きな癖に型検査器を騙すことばっか考えてるな)
data Client h = Client (h Connection) (h (IORef SomeState)) のようなHKDにして、`h` にテスト用の構造に変化させる型をはめるのとかはいかがでしょう?
それもそれでいろいろ応用が利いて面白そうですが、型定義を変える手間を思うとStrictDataやめた方が簡単そうですね…:sweat:
少なくとも今回に関してはどうせ数個しか作らない値なので、リークしたところで全く問題にならないはずだし…
unsafeCoerce, どうせ error と大して変わらんやろ、と思いつつ試してみたらイケそうですね... :open_mouth:
https://ideone.com/MYims5

{-# LANGUAGE StrictData #-}

import Unsafe.Coerce

data Hoge = Hoge
  { foo1 :: Foo
  , foo2 :: Foo
  }

data Foo = Foo Int Int deriving Show

main :: IO ()
main = print . foo2 $ Hoge (unsafeCoerce ()) (Foo 1 2)
結局最後まで騙しきれない.と思うから.
@moss has joined the channel
@tmym has joined the channel
@hongminhee has joined the channel
Takatoshi Ichikawa
@Takatoshi Ichikawa has joined the channel
GHC.TypeLits を使ってNat上の型レベルかけ算をしようとして,失敗してます.
なにか勘違いしてますかねぇ.
% stack exec -- ghci
GHCi, version 8.6.5:   :? for help
Loaded GHCi configuration from /home/nobsun/.ghci
> :set -XDataKinds
> :set -XTypeOperator
> :kind! 2 + 3
2 + 3 :: Nat
= 5
> :kind! 2 * 3

<interactive>:1:1: error:
    • Expected kind ‘* -> Nat -> k0’, but ‘2’ has kind ‘Nat’
    • In the type ‘2 * 3’
GHC 8.6で型レベル * を演算子として使うには NoStarIsType 拡張が必要です。
> :set -XNoStarIsType
> :kind! 2 * 3
2 * 3 :: Nat
= 6

ありがとうございます.
@ has joined the channel
Data.List.cycleってなんでこんな風に定義されてるんでしょう.
Data.List.cycleってGHC Library(GHC.List)だとこんな感じに定義されてて,Rewrite Ruleも持ってないんですけど,cycle2みたいに定義することもできるんですよね.
cycle2みたいに定義するかまたはこの形に書き換えるようRewrite Ruleを設定すると,外側のbuildがcycle2を食ったconsumerとfoldr/build fusionできて美味しいはずで,逆にData.List.cycleの定義のままだと再帰が外側に露出しているせいでfoldr/build ruleがfireしないんですよね.なんでこんな定義になってるんでしょう.
(ツク)ヨミ
@(ツク)ヨミ has joined the channel
@karika has joined the channel
cycle2の方がとても美味しい具体例ってどんなものがあるでしょうか.
Petri Kivikangas
@Petri Kivikangas has joined the channel
例えば, len :: Int の時,
last $ take len $ cycle [1..100::Int]

cycle=cycle2 ならば二つの Int# に関する単純なループに展開されます. cycle=Data.List.cycle ならば take lencycle で出てきたリストを食いつぶす(consして直ちにパターンマッチで消費する) ヒープ上に存在し続ける cycle [1..100] を舐め回すコードになりますね.
実行速度とかアロケーション量に大きな差がでますか?
このコードを
$ ghc -O2 CycleTest.hs
$ time ./CycleTest > dev/null

で実行すると,手元環境(MacBook Pro 2017, 8GB memory)で
real	0m12.438s
user	0m12.189s
sys	0m0.021s

になりますが,myCycleをcycleに書き換えて実行すると
real	0m35.823s
user	0m34.861s
sys	0m0.087s

になります.
多分 rep = 1, cycleLen = 107 では,いくら takenLen を大きくしても, cycle のresultがGCが必要なほど長くならず,固定されたcircular listをunconsしてるだけになるので,そこまで遅くはなりません.allocation量はそこまで大きくならないだろうと想像します.
ここでは rep を大きくすることで,allocationが増大し,GCをtriggerして時間がかかるようになったんだと思います.(memory usage analysisをやってないので,正確なことはわかりませんが)
まあ,早くなるかあんまり改善しないだけなんだったら,気づいてないんだろうから普通にpull-req投げちゃおうかな,と思っているんですが,パフォーマンスを悪化させるケースがあったら怖いんですよねえ….
上の返信の内容を訂正しました.cycleによって構成されるリストがfoldr/build fusionによる生成抑制を受けないというのは,ポインタレベルでcircularなリストがヒープに生成されて,使用されている間中保存され続けるということですので、適当なことを言っていました.
@kanimum has joined the channel
Alternativeのあの演算子 <|> ってなんて発音しますか?
演算子の発音と言えばおなじみの https://wiki.haskell.org/Pronunciation には載ってませんでした... :cry:
すみません、ちょっと検索方法を変えてみたら https://stackoverflow.com/questions/7746894/are-there-pronounceable-names-for-common-haskell-operators という回答がありました。
"or" か "alternative", と。確かに違和感ない。
私はただ「or」って読んでましたが、気になりますね
演算子だけを敢えて口にするときは 「less than pipe greater than」と言ってましたが。。。
http://hackage.haskell.org/package/stm-2.5.0.0/docs/Control-Monad-STM.html#v:orElse stmにはorElseというシノニム関数があるので、or elseと呼んでいます
自分もor elseって呼んでますね…
念の為。
https://haskell-jp.slack.com/archives/C5666B6BB/p1563870405011200?thread_ts=1563870287.011100&cid=C5666B6BB のとおりでございます。
(回答は原則スレッド機能を使いましょう :bow: )
https://wiki.haskell.org/Pronunciation も更新しておきました。
or else は左への偏りを感じますね。そういう実装も少くないですが、リスト上の``<|>``を or else というのは微かにもにょもにょします。:expressionless:
リストも“とりえる可能性の列挙“なのでまぁゴニョゴニョ
またまた、教えて君です。
GHC.TypeLits の Natカインド上の加法 + が結合的であることを実装(証明)しようとして、どうやるんだっけ?となってます。
:disappointed_relieved:
AgdaとかCoqでやるような方法は使えません.GHC.TypeLitsのNatはペアノ数になっていない構造不明のマジカル自然数なので帰納法は回せません.自分で普通に定義したNatを使って証明しておいても,そのNatとGHC.TypeLitsのNatとの対応はunsafe云々による決め付け抜きには取れないので,その決め付けを受け入れるかあきらめましょう.
GHC.TypeLitsのNatはペアノ数になっていない構造不明のマジカル自然数なので帰納法は回せません.

ああ.そういうことなんですね.GHC.TypeLits はあきらめることにします.
@sumitek has joined the channel
ところで「帰納法を回す」っていい表現ですね
あれ?あんまり一般的じゃなかったりします?
何度か見たことはありますが、久しぶりに見て趣深いなと。本題に戻ると、型族を使ってペアノ流の型と相互変換することはできます 
data Nat' = Z | S Nat'
type family ConvertNat n :: Nat' where
  ConvertNat 0 = 'Z
  ConvertNat n = 'S (ConvertNat (n - 1))