haskell-jp / questions #25

行けました!
deriving instance ToFormKey a => ToFormKey (Identity a)

追加したら動くました。ありがとうございます!
しかし疑問なのですが[Data\.Csv]()が Identity のインスタンスを持たず、 extensible のソースでもインスタンスを作っていないように見えるですがどういう仕組みなんでしょう
みなさんこんにちは。初歩的な質問で恐縮ですが、 モナドを使う関数の書き方について教えて下さい。

自分で高階関数( hof::a->(a->b)->b )を作ったところ、 引数となる関数に モナドを返す関数 f::a->m b を使いたくなりました。

ここで、以下のような対応でやってみたのですが、このようなやり方でよいのでしょうか?

(1) hof をコピペして、 hofM::a->(a->m b)->m b を作る。
(2) hofMテストするときは hofのテストに使った関数を再利用(?)して、Identity モナドでつつんで使う。
逆に hof を hofM で定義すると、(1)でコピペする必要がなくなってよいのではないかと思います。

一旦 Identity にくるんで runIdentity で剥がすみたいなことができないでしょうか?
hof x f = runIdentity $ hofM x (return . f)

で行けました。
hofMの実装に依りますが、型だけ見ると hof の時点で f :: a -> m b を渡せませんか?
Data.Csv.Conversionに入っているのがData.Csvにインポートされています。
https://github.com/haskell-hvr/cassava/pull/158
その場合、hofの中で最後にモナドを剥がすことになりますが、mがIOの場合はそれができないのでhofMが必要かなと考えました。
定義によりますよね
Prelude> let hof a ab = ab a
Prelude> :i hof
hof :: t1 -> (t1 -> t) -> t 
Prelude> let hof' = hof "hello" print
Prelude> hof'
"hello"
Prelude> :i hof'
hof' :: IO () 
なるほど、そういう仕組みだったのですね。ありがとうございます
まともな実装としては, hof = flip ($) ; hofM = (>>=) しかないような気がします...
どういう関数かかわかないけど、hof をそのまま使ってはダメ?
そして、仮にhofとhofMが挙動が違ったとしても、Applicativeで十分だと思う
みなさんありがとうございます。もともとのhofはこんな感じでした。

hof::Integer->(Integer->[String])->[String]
hof init f = 
    let result = f init
    in
        case length result > 5 of
            True -> result ++ (hof (init+1) f)
            False -> result
@ has joined the channel
あれ?そうか. hofM の引数 m a じゃないんだ.それなら @Cosmia さんがおっしゃっているとおり hof そのままでいいと思います.
ueharaTakahiro
@ueharaTakahiro has joined the channel
どこのバグなのかよくわからないんですが、extensibleを使った :point_down: のコードでなぜか型エラーになってしまいます。
{-# OPTIONS_GHC -fno-warn-simplifiable-class-constraints #-}

{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE OverloadedLabels  #-}
{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Extensible as E
import           Lens.Micro      ((^.))

hoge
    :: E.Associate "pageId" String e
    => E.Record e
    -> IO ()
hoge e = do
    let (h : _) = (e ^. #pageId)
    print (h :: Char)
エラーは次のようなものです。

test.hs:20:25: error:
    • Couldn't match type ‘E.Elaborate
                             "pageId" (E.FindAssoc 0 "pageId" e)’
                     with ‘'E.Expecting (n0 'E.:> [Char])’
        arising from the overloaded label ‘#pageId’
      The type variable ‘n0’ is ambiguous
    • In the second argument of ‘(^.)’, namely ‘#pageId’
      In the expression: (e ^. #pageId)
      In a pattern binding: (h : _) = (e ^. #pageId)
    • Relevant bindings include
        e :: E.Record e (bound at test.hs:19:6)
        hoge :: E.Record e -> IO () (bound at test.hs:19:1)
   |
20 |     let (h : _) = (e ^. #pageId)
   |                         ^^^^^^^
いちよ,予想として書いておきます(合ってるかは分からないので,もうちょっとextensibleを使ってる人の意見があったらそっちを信用してもらいたいのですが)
extensibleのキーとなるAssociateクラスやMemberクラスは,closed type familyとambiguousなcontextを使って,実現されています.つまり,今回の場合 e のどの位置に “pageId” があるかをtype familyで調べ,その位置をambiguousに型レベルの情報としてキャプチャし,それを用いてMembershipという情報を作ります.
しかし,この方法は(Overlappingを使う手法も同じ問題があるのですが),多相型(多相レコード)と非常に相性が悪いです(なぜなら多相型のままでは型レベルの位置情報を具体的に取り出せないため,GHCの型システム上では一意に決定できないからです)
なので,今回の場合Associate制約によってキャプチャされることが保証された位置情報と, (^. #pageId) (IsLabelの制約)で必要な位置情報が同じであることがGHCには分からず( e が多相であるため,位置情報を一意に決定できないため),IsLabelの制約の方が位置情報が分からない( n0 は位置情報をGHCが一時的にmono化した変数で,この変数はAssociateの情報からキャプチャできるのですが,そのような推論をGHCからできないので怒られている)
というのが原因だと思います
解決策は,明示的に Membership 情報をAssociate制約から取ってきて( association メソッドを呼び出して)それを明示的に使用するか,Associate制約を使ったユーティリティを使用することだと思います(が,extensibleそんなに使ったことがないので,他に解決策があるかもしれません)
ありがとうございます!
仮説通りの理由かどうかわかりませんが、:point_down: のように、 e ^. #pageId の結果を別の変数に束縛すると解決できました

hoge
    :: E.Associate "pageId" String e
    => E.Record e
    -> IO ()
hoge e = do
    let s :: String
        s = e ^. #pageId
        (h : _) = s
    print (h :: Char)
ちょっと不思議なのは、 (e ^. #pageId) :: String と書いても同じエラーになってしまうんですよねぇ :disappointed:
MonoLocalBindsを有効にするとできましたね.以下が通りました.ちょっとGHCのインスタンス解決の仕方の理解が間違ってたかもしれないです…こういう場合できるんですね…

hoge
    :: E.Associate "pageId" String e
    => E.Record e
    -> IO ()
hoge e = do
    let h :: Char
         (h : _) = e ^. #pageId
    print h


hoge
    :: E.Associate "pageId" String e
    => E.Record e
    -> IO ()
hoge e = do
    let (h : _) = e ^. #pageId :: String
    print h


MonoLocalBindsがない状態だと,IsLabelが解決できないようですね.
いつもお世話になります。並列処理時の設計について質問です。

実行時に決定されるリスト xs と、そのリストの要素を引数とするIOアクション f::x->IO() があり、 mapConcurrently_ で並列化しました。

ここで、f が他のリストの要素に影響与えないように縛る(?)にはどうしたら良いでしょうか? f の中ではxをキーとして複数の外部資源にアクセスしますが、例えば f のどこかで x+1 を使うというようなことを避けたい。これが守られない場合、並列で動かした時、デッドロックや外部資源上のデータの破損などが発生します。

fは規模が大きくなって、複数の開発者が携わる可能性があります。「コードレビュー時に気をつける」という以上に、完璧でなくてもいいのですが、何らかの仕組みを考えたいです。

以下、なけなしのhaskellの知識で考えた対策案ですが、よりhaskellらしい方法もあろうかと思います。よろしくお願いします。

案1)f 内で使われる外部資源にアクセスする関数(例えば g::x->y->IO(), y は更新したい内容)について、予めxを部分適用した g’ を、f に渡して、f内では直接 xに触らないようにする。この場合、fが大きくなると、部分適用した関数をあちこち取り回さないといけないので、ちょっと面倒な感じもします。

案2)Readerモナドにxを入れて、外部資源にアクセスする関数は x をモナド経由で取り出す。Readerモナドは、ネストした関数に適用するときはlocalで上書きできてしまうので、localのないReaderモナドのようなものがあればいいのかもしれませんが、いまのところ探せていません。
Operationalモナドを使ってみてはどうでしょうか?
疑問の内容がいまひとつ理解できないでいます.
「f が他のリストの要素に影響与え」てしまう具体的な例はありますか?
恣意的かつ単純な例で示していただけると理解できるかもしれません.
@hexirp Operationalモナドのチュートリアル読み始めました。プログラム内のIOアクションの抽象化という切り口ですね。これは面白そうです。
@nobsun 「他のリストの要素を引数とするIOアクションに影響を与えない」と書くべきでした。

極端な例ですと、
fx = g (x+1) "newvalue"

みたいなバグが考えられます。
「他のリストの要素を引数とするIOアクションに影響を与え」る例がやっぱり良くわかりません.

f x = g (x+1) "newvalue"


g は具体例としてはどのような定義が考えられますか?
ああ,わかりました.
f :: Int -> IO ()
f x = writeFile (show (x+1)) =<< readFile (show x)

ということですね.
はい、そんな感じです。x を取り回しているので、如何様にも変えることができてしまいます。
与り知らない別プロセスが操作できてしまうファイルなどの外部リソースを操作するプログラムを考える場合,その外部リソースのある時点での状態をそのプログラムの意味に含めようとすること自体に無理がありそうな感じですね.プログラムの型が IO () 型ですし...
ForeignObj# はいったい何だろう、ググっても、GHCのgitを全部検索しても見つからない https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/ffi-chap.html#unboxed-types
多分、古のGHC(GHC 4 とか GHC 5 とかの時代)の FFI で使われていた型かと。
@otake84 has joined the channel
いくつか質問させてください。

1. このコードで :: Int を推論してもらうことはできないのでしょうか?Intを受け取るものしか定義していないのでしてくれても良さそうに感じるのですが…
https://repl.it/repls/EnormousStimulatingCalculator

2. Maybeでラップしたものを返したいのですがどのように定義したら良いのでしょうか?
https://repl.it/repls/MassiveAssuredBootstrapping

3. そもそもこういうことができるクラスは標準で用意されていたりしないのでしょうか?RustのFromトレイトみたいなことがしたいのですが…

よろしくお願い致します。
1はなんか言語拡張があったきするな
2 は FlexibleInstance 使えば
https://repl.it/repls/CourteousSmartFlashdrives
3 は知らないや、それぞれのはあるけど ToString や FromString みたいな
あと、1はこんな感じ
https://repl.it/repls/MildGrouchyListener
お返事遅れてしまいすみません!
お二人ともありがとうございます!
とても勉強になります!
https://haskell-jp.slack.com/archives/C707P67R7/p1533525889000105 こちらの質問に答えるにあたり気になったのですが、
VS CodeでHIEを起動した場合、HIEのデバッグオプションはどうやって設定するんでしょうか...?
Neovimの場合languageclientの設定でいけるんですけど...
さっと https://github.com/alanz/vscode-hie-server/blob/master/src/extension.ts のソースを読んでもわからず。
VSCode は使っていないのでわからないですが Custom Wrapper を作るといけそう? https://github.com/alanz/vscode-hie-server#custom-wrapper
"languageServerHaskell.trace.server": "verbose" でなんか出ます。