2016-04-10 22 views
2

モナドについて知りましたが、私は多くの関数をControl.Monadに実装しようとしていました。私はちょうどapに行ったが、私はそれを働かせることはできない。私は関数almostAp :: Monad m => m (a -> b) -> m a -> m (m b)を作って、私が作った別の関数flatten :: Monad m => m (m b) -> m bでそれを作ろうとしました。私はap = flatten . almostApを使用しようとすると問題が(flatten .)がGHCiのに応じてMonad m => (a -> m (m b)) -> a -> m b型を持ち、私はモナディック関数を作成しようとするとエラーが発生する

Occurs check: cannot construct the infinite type: m ~ (->) (m a) 
    Expected type: m (a -> b) -> m a -> m a -> m b 
    Actual type: m (a -> b) -> m a -> m (m b) 
In the second argument of ‘(.)’, namely ‘almostAp’ 
In the expression: (flatten . almostAp)` 

を得る。しかし、であるので、これはなぜ起こるのでしょうか?ここで

は関数定義されている(私は=<<でそれらをクリーンアップすることができます知っているとファンクタの法則):

almostAp :: Monad m => m (a -> b) -> m a -> m (m b) 
almostAp = (flip (\x -> fmap ($x))) . (fmap (flip (>>=))) . (fmap (return .)) 

flatten :: Monad m => m (m a) -> m a 
flatten = (>>= id) 
+4

あなたはコードを読みにくくするポイントフリーのスタイルに非常に敏感であるようです(想像もできませんが、書くのは難しいでしょう)。 'let'バインディングや' do'を恐れてはいけません。表記 –

+2

"ポイントフリーのスタイルの" let "を使用してください:それは理に適っていて、それをより良く使うでしょう" – Cactus

答えて

5

あなたは一引数を構成しようとしていたので、あなたがあなたのタイプのエラーを取得し、機能flatten(これに付随して、通常はjoinの名前になります)2つの引数関数almostApです。 (.) :: (b -> c) -> (a -> b) -> (a -> c)は、右側の関数が1引数の関数である場合に使用されます。残念ながら、エラーメッセージはそれほど役に立ちませんでした。

(.).(.)(発音は "ドット -dot- ドット"、または "おっぱい")は、あなたが欲しいものを行います。

ghci> let ap = ((.).(.)) flatten almostAp 
ghci> :t ap 
ap :: Monad m => m (a -> b) -> m a -> m b 

しかしapdo表記してはるかに簡単に実装することができます。あなたのバージョンは本当にこれよりも理解しやすいですか?

ap mf mx = do 
    f <- mf 
    x <- mx 
    return (f x) 

do表記は>>=のためだけ糖衣構文です。 (私はずっとdoバージョン好むが)ここでは、それはそれなしでどのように見えるかです:

ap mf mx = mf >>= \f -> fmap f mx 
+2

'(。)。(。)'は私の心の中で "フクロウの目"です。むしろ家族に優しいようです。 – dfeuer

3

しかし、(flatten .)はGHCiのに応じてMonad m => (a -> m (m b)) -> a -> m b入力したが、なぜこれが起こるのでしょうか?

実際にはあります。考えてみましょう:

flatten :: Monad m => m (m a) -> m a 

almostAp :: Monad m => m (a -> b) -> m a -> m (m b) 

したがって、(flatten .)のタイプがある:

flatten  :: Monad m => m (m a) -> m a -- renaming a to b 
          |  | | | 
          ------- --- 
          |  | 
(.)   ::    (b -> c) -> (a -> b) -> a -> c 
                |    | 
                -------   --- 
                |  |   | | 
(flatten .) :: Monad m =>     (a -> m (m b)) -> a -> m b 

しかし、あなたはalmostAp(flatten .)を適用することはできません型に互換性がないので:

almostAp :: Monad m => m (a -> b) -> m a -> m (m b) 
          |  | |   | 
          ---------- -------------- 
           |    | 
           |   ------- 
           |   |  | 
(flatten .) :: Monad m => ( a  ->  m (m b)) -> a -> m b 

あなたはこのことを期待:

almostAp :: Monad m => m (a -> b) -> m a -> m (m b) 
          |    | |  | 
          ----------------- ------- 
            |    | 
            |   ------- 
            |   |  | 
(flatten .) :: Monad m => (  a   -> m (m b)) -> a -> m b 

しかし、それはcurryingの方法ではありません。タイプa -> b -> cの機能は、a -> (b -> c)を意味し、(a -> b) -> cではありません。最初の関数a -> (b -> c)は、2つの引数,aおよびbをとり、cを返します。 2番目の関数(a -> b) -> cは1つの引数a -> bをとり、cを返します。

したがって、flattenalmostApをどのように構成しますか?

(.) :: (b -> c) -> (a -> b) -> a -> c 
     |  | |  | 
     -------- -------- 
      |   | 
     flatten  +-- almostAp can't be used because it needs two arguments 
         -- but (.) only gives it one argument (the `a` in a -> b) 

私たちは、それらを構成するために、特殊な組成のオペレータを必要とする:

(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d 
     |  | |   | 
     -------- ------------- 
      |    | 
     flatten  almostAp 

(.:) f g x y = f (g x y) 

、我々は単にflatten .: almostApを書くことができalmostApは、2つの引数が必要ですので、あなたは、通常の関数組成物を用いてそれを行うことはできません。それを書く別の方法は(flatten .) . almostApです。これは、(.:) = (.) . (.)です。詳細については、以下を読む

What does (f .) . g mean in Haskell?


実際、タイプa -> (b -> c)の関数は、1つの引数aを取り、第二引数bをとり、cを返す別の機能b -> cを返すのみ。