haskell-jp / questions #64

こんにちは。いつもお世話になります。
関数の定義で型変数を使った場合、実際にどの型が使われたかを、その関数内部で取得することはできますか? ログ出力を目的としていて、`typeOf`を使えばよいのですが、それだと冗長になるケースがありまして、お尋ねしています。

以下のケースで`f`の中で`typeOf g`とすれば`String->IO [Integer]`が取れるのですが、関心があるのは Integer だけなので、それを取得する方法がないか探しています。

g::String->IO [Integer]
g = ...

f::(Eq a)=>String->(String->IO [a])->IO Bool
f x g = ..   -- "Integerだよ"というメッセージを表示したい。 
g を実行した後だけになっちゃいますが、 typeOf <$> g x すればよいのではないかと。
あるいは、 funResultTy やその辺りの関数を使えばできそうです(詳細確認中)。
https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Typeable.html#v:funResultTy
おそらくこっちがより正当な方法でしょうね。。。
funResultTy の第2引数はなんなんだろう、と思ったら、引数の型を表すみたいです。
GHCiで試したところ :point_down: となってました。

> import Data.Typeable
> import Data.Proxy
> funResultTy (typeRep (Proxy :: Proxy (String -> IO [Integer]))) (typeRep (Proxy :: Proxy String))
Just (IO [Integer])
@tsurutan has joined the channel
Typeableを使って良いならば、ScopedTypeVariableを使って書けます。

{-# LANGUAGE ScopedTypeVariables #-}
...
f :: forall a. (Typeable a, Eq a) => String -> (String->IO [a]) -> IO Bool
f _ _ =
  do
    print $ typeOf (undefined :: a)
    return True
undefinedはダサいので typeRep (Proxy :: Proxy a) としたり、さらにTypeApplicationsを有効にして typeRep (Proxy @a) にするともっと良さそうです
いずれにせよ Typeable a は必要ですね。 Typeable 制約のない型変数から型の名前を取得することはできないので。
f が普通の関数であれば定義を変えれば良いだけですが、型クラスのメソッドだったりするとちょっと厄介かもしれません。
ScopedTypeVariablesを使わない方法としては
fnToProxy :: (String -> IO [a]) -> Proxy a
fnToProxy _ = Proxy

という補助関数を用意して typeRep (fnToProxy g) とするという手があります。
とんでもなく邪悪ですが、RULESプラグマを使って型制約の追加を回避する方法があったり……

printType :: Proxy a -> IO ()
printType _ = putStrLn "<unknown>"
{-# NOINLINE printType #-}

printTypeT :: Typeable a => Proxy a -> IO ()
printTypeT p = print $ typeRep p

{-# RULES
"T-String" forall (p::Proxy String). printType p = printTypeT p
"T-Integer" forall (p::Proxy Integer). printType p = printTypeT p
#-}
{-以下来そうな型を列挙-}

f :: forall a. Eq a => String -> (String->IO [a]) -> IO Bool
f _ _ =
 do
   printType (Proxy :: Proxy a)
   return True
この件、まだ試せてないのですが思いつきました。
Template Haskellをコンパイル時ではなく実行時に実行すればできるはずです。 Q モナドは runQ 関数で IO に変換できるので。
プロファイリングもできるはず。
みなさん、ありがとうございいました。

今回は、 @as_capabl さんの、 typeRep (Proxy :: Proxy a) の方法で対応することにしました。

typeOf (undefined :: a) は、hlint 2.1では何もいわれなかったのですが、2.2に上げたら、 typeRep (Proxy :: Proxy a) をサジェスチョンしてきました。
@lkjl has joined the channel
@ has joined the channel
@ has joined the channel
日本人主語抜かしがち
@o3o has joined the channel
@gksato has joined the channel
やはり多相な型は書けなかった。
ただし、単相な型を複数書くことが許されてた。
でも、孤児プラグマはダメだった。
Single クラスとインスタンスの Only をパッケージ分けようとしてたのにな。
{-# COMPLETE Single :: Only #-}
{-# COMPLETE Single :: Identity #-}
@addokoda has joined the channel
@cojna has joined the channel
みなさんこんにちは。
以下のようなコードで、 “process” 関数の型を変えずに “server”関数の結果をキャッシュするような方法はありますか?
IOモナドが登場するのは “server”関数のが原因なので、どうせなら “server”関数内部でキャッシュを実現したい。
[CachedIO]()というパッケージをみましたが、これは cachedIO でIOアクションを包んでキャッシュを実現するもので、 cachedIO そのものを、関数呼び出しをまたがって、誰かがが保持していないといけないので、ちょっと違いました。

module Main where

import           Control.Monad (forM_)

main :: IO ()
main = forM_ [1..] $ \_ ->
  do putStrLn "[Enter your key]"
     key <- getLine
     process key >>= putStrLn

process::String->IO String
process key = do
  ret <- server key
  return $ if ret then "Success" else "Failure"

server::String->IO Bool
-- ^このIOアクションの引数ー結果をキャッシュしたい。
server key
  | key == "ABC" = putStrLn "Accessing heavy resource." >> return True
  | otherwise = putStrLn "Yet another heavy resource access." >> return False
可変なハッシュテーブルでいけそうかと思いましたけど、それを渡さないといけませんね……
unsafe な関数でグローバルな可変参照作れば何とかなるでしょうが、おすすめしません。
おっしゃるとおり unsafePerformIO を使わない限り無理かと思います。
そういう、「すべての関数が意識するべきでない値」は、いわゆるReaderT IOパターンで隠すのが得策かと。

IORef (HashMap String Bool) みたいな型を Env に含める形になるでしょう。
更新時は atomicModifyIORef' を使うのを忘れずに!
最近そういうことしたかったのでファイルにシリアライズしてぶちこみました
https://github.com/ncaq/dic-nico-intersection-pixiv/blob/d5f240c3f4bdcb657c5c852b77b42d3a292c1abe/src/Main.hs#L94
@ さんの例をみて思いついたのですが、私の場合はキャッシュさせたいデータが少量なので、`setEnv` で環境変数を使ってキャッシュさせる方法もありそうです。使い方として筋悪な気がする一方、もともとキャッシュさせたいデータが、環境変数の外部化みたいなものなので、意味的にそれほど遠いわけでもない。
StateVar 使うとかどうなんでしょうかね。並列アクセスは。明記されてないのでなんとも言えませんが少なくとも unsafePeformIO を直接使うよりは安全そう?? (とはいえ私も IORef を使うとは思いますが)
@solzard has joined the channel
これ最外型構築子を書くような気がしてきた
@ has joined the channel
@ysawc has joined the channel
@ has joined the channel
既出だったらすみません
cabal v2 testでghcの環境変数のセットアップがうまくいかなくてパッケージが読み込めないエラーがでて
doctestが使えないですがどうされてますか?
本家にもissueはあがってますが
はやく使いたいのです
そもそもdoctestは人気がないのでしょうか
自分が半年前に cabal new build でプロジェクトを作ったときは doctest は動いていたのですが最近動かなくなったのでしょうか。 https://github.com/syocy/xorshift-plus/blob/master/tests/doctest/Main.hs
わからないですね
一度でもv1で動かすと状態が残って通るばあいもあってよくわからないですね
cabal-installのバージョン等がわからないのでなんとも言えませんが --write-ghc-environment-files=never がデフォルトになった影響かもしれません。
あとは常にcabal test経由でいいのであれば http://hackage.haskell.org/package/cabal-doctest を使うという手もあります。cabal的にはcustom setupは非推奨っぽいのでネイティブでdoctestに対応してくれると良いのですが。。。
私は「そもそもアプリケーションだからドキュメントテストなんて必要なかった」と気がついてテストをhspecに移動させてしまいましたね
@hatoo has joined the channel
@statiolake has joined the channel
ありがとうございます
確認します
@aximov has joined the channel
takumi_watanabe
@takumi_watanabe has joined the channel
@tos has joined the channel
質問なのですが cabal で ghc-optionsをコマンドライン引数で渡す方法ってありますかね? といいますのも。 私はよく-fno-codeを使うのですが、これを渡す方法がなくcabal ファイルにフラグとしてNoCodeみたいなのを用意してそれが有効なときに ghc-options に加えるみたいなことをしているのですが全く同じライブラリが二重でインストールされるのは辛いです。 確かに生成される型情報が違う可能性は0ではないですが。。。
cabal new-build --ghc-options="-fno-code" のようにして渡せるので、それが一番手っ取り早いと思います
知りませんでした。。。