5

この Haskell 式がどのように機能するかを理解するのに苦労しています:

import Control.Monad
import System.IO
(forM_ [stdout, stderr] . flip hPutStrLn) "hello world"

その部分は正確に何をしているの. flip hPutStrLnですか?型シグネチャは複雑に見えます:

ghci> :type flip
flip :: (a -> b -> c) -> b -> a -> c
ghci> :type (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
ghci> :type (. flip)
(. flip) :: ((b -> a -> c1) -> c) -> (a -> b -> c1) -> c
ghci> :type (. flip hPutStrLn)
(. flip hPutStrLn) :: ((Handle -> IO ()) -> c) -> String -> c

(.)式が評価されると、演算子の左右のオペランドは何になりますか?

私の質問をする別の方法は、上部の式の左側の部分が次のような型シグネチャになる方法です。

(forM_ [stdout, stderr] . flip hPutStrLn) :: String -> IO ()
4

3 に答える 3

13

の左オペランドと右オペランド(.)

forM_ [stdout, stderr]

flip hPutStrLn

それぞれ。

のタイプhPutStrLn

hPutStrLn :: Handle -> String -> IO ()

flip hPutStrLnタイプもそう

flip hPutStrLn :: String -> Handle -> IO ()

型システムからわかるように、flipは別の関数の引数の順序を入れ替えるコンビネータです。アブストラクトで指定

flip       :: (a -> b -> c) -> b -> a -> c
flip f x y =  f y x

ghciの型(. flip hPutStrLn)

ghci> :type (. flip hPutStrLn)
(. flip hPutStrLn) :: ((Handle -> IO ()) -> c) -> String -> c

反対方向から作業すると、左側のタイプは

ghci> :type forM_ [stdout, stderr]
forM_ [stdout, stderr] :: Monad m => (Handle -> m b) -> m ()

タイプがどのように組み合わされるかを観察します。

(. flip hPutStrLn)     ::            ((Handle -> IO ()) -> c   ) -> String -> c
forM_ [stdout, stderr] :: Monad m =>  (Handle -> m  b ) -> m ()

2つを組み合わせると(最初のものを2番目のものと呼ぶ)、

ghci> :type forM_ [stdout, stderr] . flip hPutStrLn
forM_ [stdout, stderr] . flip hPutStrLn :: String -> IO ()

あなたの質問では、コンポジションの結果が に適用され、Stringを生成する I/O アクションが生成されます()つまり、主に、標準出力とエラー ストリームへの書き込みの副作用に関心があります。

質問の定義のようなポイントフリー スタイルでは、プログラマーは、より複雑な関数を(.). flipコンビネータは、繰り返される部分的なアプリケーションが互いに適合するように引数を並べ替えるのに役立ちます。

于 2013-02-27T16:16:46.230 に答える
7

flip入力関数の引数を逆にします。つまり:

flip hPutStrLn == \a b -> hPutStrLn b a

.関数合成演算子 (または中置関数) であり、関数をうまく連鎖させることができます。この演算子がなければ、式は次のように書き直すことができます。

forM_ [stdout, stderr] ((flip hPutStrLn) "hello world")

これは次と同じです:

forM_ [stdout, stderr] (flip hPutStrLn "hello world")

または、アプリケーション演算子を使用して:

forM_ [stdout, stderr] $ flip hPutStrLn "hello world"

.オペランドの質問について。の型シグネチャを考えてみましょう.:

(.) :: (b -> c) -> (a -> b) -> a -> c

これを 3 つの引数からの関数として見ることができます: 関数b -> c、関数a -> b、および値a- 結果の値cへ、またカリー化により、2 つの引数からの関数として見ることができます: b -> cand a -> b- 型の結果関数へa -> c. そして、これがあなたの例で起こることです: 2 つの関数 ( forM_ [stdout, stderr]and flip hPutStrLn、それ自体がカリー化の結果です) を渡して、結果として.type の関数を取得しますString -> IO ()

于 2013-02-27T15:38:34.227 に答える
2

これは、そのタイプのやや短い派生です(Nikita Volkovの回答の2番目の部分で示唆されているように)。

知っている(.) :: (b -> c) -> (a -> b) -> a -> c(f . g) x = f (g x)

(f . g) :: a -> c      where  g :: (a -> b)  and  f :: (b -> c)

統合を実行した後、タイプを与えると、ba -> bb -> c消えます)そしてa -> c

flip hPutStrLn         ::    String -> (Handle -> IO ())             -- g
forM_ [stdout, stderr] :: (Monad m) => (Handle -> m  b ) -> m ()     -- f

(最初の型では、型が右結合Handle -> IO ()であるという事実を使用して、括弧を付けます)、(->関数合成演算子を介して)2番目と最初の型を合成する結果の型は次のようになります。

(Monad m) => String -> m ()     where  m ~ IO  and b ~ ()
                                (found by unification of
                                        Handle -> IO ()  and
                                        Handle -> m  b       )

すなわちString -> IO ()

の引数の順序には(.)少し慣れが必要です。最初に2番目の引数関数を起動し、その結果を使用して最初の引数関数を呼び出します。インポートすると、逆のような演算子を関数でControl.Arrow使用できます。>>>(.)(f . g) x == (g >>> f) x

于 2013-02-28T18:53:10.603 に答える