2017-02-07 8 views
6

私は、この種の機能を作るために加算演算子(+)を構成したいと思います:3つの引数を受け入れる関数に加算演算子を「連鎖する」?

Num a => a -> a -> a -> a 

と同様に、本の同等:

(\a b c -> a + b + c) 

が、ラムダに頼らずに。


私はすでに

((+) . (+)) 

私は仕事に期待が、意外にもなかっただろうどれを試してみました。

+0

Iircは、1つの引数だけを受け入れる関数を必要とします。 2進演算子はそれぞれ2をとります – Carcigenicate

+0

そして '+'を使ってリストを折りたたんでみませんか? – Carcigenicate

+0

@Carcigenicate hmm、それは変です。私はそれがそうであると予想していないでしょう。それを行う方法はありますか? - 編集:折りたたみとは何ですか? – theonlygusti

答えて

11

http://pointfree.io\a b c -> a + b + cの点フリーバージョンは((+) .) . (+)となります。

非公式には、関数は引数としての関数も関数を値として返さない1次関数に対してのみ直感的に機能します。 (+)は高次関数です。タイプNum a => aの値をとり、タイプNum a => a -> aの関数を返します。あなたは素朴な方法で高階関数を構成しようとすると、結果はあなたが期待するものではありません。そして、

(+) :: Num z => z -> z -> z 
(.) :: (b -> c) -> (a -> b) -> (a -> c) 
f . g = \x -> f (g x) 

(+) . (+) == (.) (+) (+) 
      == \x -> (+) ((+) x) 

:t (+) . (+) 
(+) . (+) :: (Num a, Num (a -> a)) => a -> (a -> a) -> a -> a 

は、2つの関数の定義を考えてみましょう

curryingのために、最初の(+)の最初の引数として数値ではなく関数を渡します。


は、どのように我々はh = ((+) .) . (+)h a b c = a + b + cから入手できますか? (+)が左結合であるという事実を使用して、接頭辞式として中置式を書き直すことから始めます。

\a b c -> a + b + c 
    == \a b c -> ((+) a b) + c 
    == \a b c -> (+) ((+) a b) c 

次に、我々は交互に除去する位置に引数を移動する引数および組成物を除去するためにETAの変換を適用します。私は、組成のアプリケーションに使用する関数を特定することについて非常に明確にしようとしました。

 == \a b -> (+) ((+) a b)  -- eta conversion to eliminate c 
    == \a b -> (+) (((+) a) b) -- parentheses justified by currying 
    --   f  g   -- f = (+), g = ((+) a) 
    -- \a b -> f ( g b) 
    -- \a b -> (f . g) b  -- definition of (.) 
    == \a b -> ((+) . ((+) a)) b 
    == \a -> (+) . ((+) a)  -- eta conversion to eliminate b 
    == \a -> (.) (+) ((+) a)  -- prefix notation 
    == \a -> ((.) (+)) ((+) a) -- parentheses justified by currying 
    == \a -> ((+) .)((+) a)  -- back to a section of (.) 
    --   f  g  -- f = ((+) .), g = (+) 
    -- \a ->  f  (g a) 
    -- \a -> ( f . g) a  -- definition of (.) 
    == \a -> (((+) .) . (+)) a 
    == ((+) .) . (+)    -- eta conversion to eliminate a 
+0

'((+)。)の働きを分析することが実例となります。 (+) ' –

6

これはいくつかのノイズを紹介していますが、一時的なストアに2つ目のプラス1組での引数uncurry :: (a -> b -> c) -> (a,b) -> ccurry :: ((a,b) -> c) -> a -> b -> cを使用することができます。

curry $ (+) . uncurry (+) :: Num a => a -> a -> a -> a 

またはおそらくより意味的に読み:

curry ((+) . uncurry (+)) :: Num a => a -> a -> a -> a 

uncurryを関数(ここでは(+))をとり、関数:uncurry (+) :: Num a => (a,a) -> aに変換します。その結果、(+)はというタプルを取る関数に変換されました。

今、私たちは、最初の(+)を有する組成物を作るために(.)を使用することができます。

(+) . uncurry (+) :: Num a => (a,a) -> (a -> a) 

だから今、私たちは一つの引数(タプル(a,a))を取り、a(とる関数を生成する機能を持っています最初の(+)の第2オペランド)を計算し、合計を計算します。もちろん、問題はタプルを取り除きたいということです。関数をcurryに渡すことで、それを行うことができます。これはタプル関数((a,a) -> (a -> a))を引数を別々に取る関数に変換します(a -> (a -> (a -> a)))。

4

注関数組成オペレータの署名:それは2つの機能、1つの引数を取るたそれぞれをとり、第2の機能と同じタイプの引数を取る関数を返す

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

最初のものと同じ型を返します。

+を2つ作成しようとしましたが、+は2つの引数を取るため、ハックした/創造的な回避策はありません。

この時点では、問題に収まらないときに強制的に合成すると、あなたの人生をもっと難しくするだろうと言います。

あなたが複数の番号を総括したい場合は、あなたのような関数を書くことができます:

sum :: [Int] -> Int 
sum nums = foldl (+) 0 nums 

または、定義の後ろにnums現れるので、それは「ポイント・ツーを得、完全にドロップすることができます自由」の形式:

sum :: [Int] -> Int 
sum = foldl (+) 0 

それは/が数字のリストの上に+を折る低減します。折り目をまだ使用していない場合は、こちらをご覧ください。それらは、Haskellでループを実現するための主要な方法の1つです。これは本質的に、リストや他の何かを反復処理するときの「暗黙の再帰」です。

上に定義された機能で、あなたが好きそれを使用することができます。

sum [1, 2 3, 4, 5] 
+0

私は 'foldr(+)0 nums'(角括弧付き)と' = 'の代わりに' = 'を代入に書くべきだと思います。関数が可換性である場合、 'foldl'よりも' foldl'を使用する方がメモリ効率が高くなります。 –

+0

@WillemVanOnsem Whoops。 Idk私は等価の代わりにコロンを使用して終わった。そして、ありがとう、私は倍を変更します。 – Carcigenicate

+0

'(+)'にかっこを入れる必要があります。それ以外の場合、Haskellはこれを '(+)foldl 0'と見なします... –

8

あなたは時々.:として定義されているこの奇妙なオペレータ(.).(.)を、必要とする(3点を考える...)

でghci

この演算子は、<$$> = fmap . fmapとすることもできます。

関連する問題