17

Haskell での Tacit function compositionの質問のコメントで、人々は のNumインスタンスを作成すると述べたa -> rので、関数表記法を使用して乗算を表現してみようと思いました。

{-# LANGUAGE TypeFamilies #-}
import Control.Applicative

instance Show (a->r) where   -- not needed in recent GHC versions
  show f = " a function "

instance Eq (a->r) where     -- not needed in recent GHC versions
  f == g = error "sorry, Haskell, I lied, I can't really compare functions for equality"

instance (Num r,a~r) => Num (a -> r) where
  (+) = liftA2 (+)
  (-) = liftA2 (-)
  (*) = liftA2 (*)
  abs = liftA abs
  negate = liftA negate
  signum = liftA signum
  fromInteger a = (fromInteger a *)

fromInteger の定義は3 4、12 に評価されて7 (2+8)70 になるものを書くことができることを意味することに注意してください。

その後、すべてが素晴らしく、面白いほど奇妙になります! できれば、この奇妙さを説明してください。

*Main> 1 2 3
18
*Main> 1 2 4
32
*Main> 1 2 5
50
*Main> 2 2 3
36
*Main> 2 2 4
64
*Main> 2 2 5
100
*Main> (2 3) (5 2)
600

[編集: Applicative は一般的に優れているため、Monad の代わりに Applicative を使用しましたが、コードに大きな違いはありません。]

4

1 に答える 1

21

2 3 4インスタンスのような式では、2とは両方とも3関数です。つまり2、実際(2 *)には、タイプがありNum a => a -> aます。3同じです。2 3その場合、(2 *) (3 *)これはと同じ2 * (3 *)です。あなたのインスタンスでは、これはliftM2 (*) 2 (3 *) その時ですliftM2 (*) (2 *) (3 *)。これで、この式はインスタンスなしで機能します。

では、これはどういう意味ですか?さて、liftM2関数の場合は一種の二重構成です。特に、liftM2 f g hと同じ\ x -> f (g x) (h x)です。そうliftM2 (*) (2 *) (3 *)です\ x -> (*) ((2 *) x) ((3 *) x)。少し単純化すると、次のようになります\ x -> (2 * x) * (3 * x)。これで、それ2 3 4が実際にであることがわかりました(2 * 4) * (3 * 4)

では、なぜliftM2for関数がこのように機能するのでしょうか。次のモナドインスタンスを見てみましょう(ただし、タイプレベルの演算子セクションを記述できないことに(->) r注意してください)。(->) r(r ->)

instance Monad ((->) r) where  
    return x = \_ -> x  
    h >>= f = \w -> f (h w) w  

です。return_ 少し変です。これは、の観点から見た方が簡単だと思います。関数の場合、次のように機能します。const>>=joinjoin

join f = \ x -> f x x

つまり、2つの引数の関数を取り、その引数を2回使用することにより、1つの引数の関数に変換します。十分に単純です。この定義も理にかなっています。関数joinの場合、2つの引数の関数を1つの関数に変換する必要があります。これを行う唯一の合理的な方法は、その1つの引数を2回使用することです。

>>=fmap続きjoinます。関数の場合fmapは、だけ(.)です。したがって、今>>=は次のようになります。

h >>= f = join (f . h)

これはただ:

h >>= f = \ x -> (f . h) x x

今、私たちは取得するために取り除くだけです.

h >>= f = \ x -> f (h x) x

これで、どのように機能するかがわかったので>>=、を見ることができますliftM2liftM2次のように定義されます。

liftM2 f a b = a >>= \ a' -> b >>= \ b' -> return (f a' b')

これを少しずつ簡単に行うことができます。まず、return (f a' b')になり\ _ -> f a' b'ます。と組み合わせると、次の\ b' ->ようになります\ b' _ -> f a' b'。次にb >>= \ b' _ -> f a' b'、次のようになります。

 \ x -> (\ b' _ -> f a' b') (b x) x

2番目xは無視されるため、次のようになります。\ x -> (\ b' -> f a' b') (b x)これは次にになり\ x -> f a' (b x)ます。したがって、これは私たちに次のことを残します:

a >>= \ a' -> \ x -> f a' (b x)

繰り返しますが、次のように置き換え>>=ます。

\ y -> (\ a' x -> f a' (b x)) (a y) y

これは次のようになります。

 \ y -> f (a y) (b y)

これは、liftM2以前とまったく同じです。

うまくいけば、今の動作は2 3 4完全に理にかなっています。

于 2012-08-26T21:45:46.123 に答える