質問

現実世界のハスケル, 、彼らはこのようなコンビネータを記述します:

Haskellでは、他の関数を引数として取り、新しい関数をコンビネータとして返す関数を参照します。

そして後で彼らはそれを述べます maybeIO 関数はコンビネータであり、その型シグネチャは次のようになります:

maybeIO :: IO a -> IO (Maybe a)

しかし、私が見ることができるのは maybeIO IOモナドでラップされた値を受け取り、IOモナドで値を返す関数です。では、この関数はどのようにしてコンビネータになるのでしょうか?

役に立ちましたか?

解決

私たちが組み合わせを言うとき、私たちが意味できる2つのことがあります。単語は少し過負荷です。

  1. 私たちは通常、物事を「組み合わせる」機能を意味します。たとえば、関数はIO値を取り入れて、より複雑な値を構築します。これらの「コンビネータ」を使用すると、比較的少数のプリミティブ関数から新しい複素数IO値を組み合わせて作成できます。

    たとえば、10個のファイルを読み取る機能を作成するのではなく、IOを使用します。ここで、コンビネータは、値を組み合わせて構築するために使用する関数

  2. です。

  3. 厳格なコンピュータ科学用語は、「空き変数のない関数」です。 so

     -- The primitive combinators from a famous calculus, SKI calculus.
     id a         = a -- Not technically primitive, genApp const const
     const a b    = a
     genApp x y z = x z (y z)
    
    .

    これは、「組み合わせ論理」と呼ばれるgranterフィールドの一部であり、それを基本的に自由変数を排除し、それをコンビネータといくつかのプリミティブ機能と交換します。

  4. TLDR:通常、コンビネータと言うと、一握りのプリミティブ関数と、より複雑な値を構築するための多くのユーザー定義関数がある「コンビネータパターン」と呼ばれるより一般的な概念を参照しています。

他のヒント

コンビネータの厳密な定義はないので、その意味では実際には何も意味しません。しかし、Haskellでは、より複雑な関数や値をより単純なものから構築することが非常に一般的であり、関数が完全に適合しないことがあるため、接着剤を使それを行うために使用する接着剤のビットは、コンビネータと呼ばれます。

たとえば、最も近い整数に丸められた数値の平方根を計算する場合は、その関数を次のように書くことができます

approxSqrt x = round (sqrt x)

また、ここで実際に行っていることは、2つの関数を取り、それらをビルディングブロックとして使用してより複雑な関数を構築することです。私たちは、しかし、それらを一緒に置くために接着剤のいくつかの種類が必要であり、その接着剤は (.):

approxSqrt = round . sqrt

したがって、関数合成演算子は関数のコンビネータであり、関数を組み合わせて新しい関数を作成します。別の例は、おそらくファイルの各行をリストに読み込みたいということです。あなたはこれを明白な方法で行うことができます:

do
  contents <- readFile "my_file.txt"
  let messages = lines contents
  ...

しかし!ファイルを読み取り、その内容を文字列として返す関数があればどうしますか?それから私達はすることができます

do
  messages <- readFileLines "my_file.txt"
  ...

結局のところ、ファイルを読み取る関数があり、大きな文字列を取り、その中の行のリストを返す関数があります。これらの2つの機能を意味のある方法で一緒に貼り付けるための接着剤しかない場合は、次のように構築できます readFileLines!しかし、もちろん、これはHaskellであり、その接着剤は容易に入手可能です。

readFileLines = fmap lines . readFile

ここでは2つのコンビネータを使用しています。私達は使用します (.) 前から、そして fmap 実際には非常に便利なコンビネータです。私たちは純粋な計算をIOモナドに「持ち上げる」と言いますが、私たちが本当に意味するのはそれです lines 型シグネチャを持っています

lines :: String -> [String]

しかし、 fmap lines 署名を持っています

fmap lines :: IO String -> IO [String]

だから fmap 純粋な計算とIO計算を組み合わせたい場合に便利です。


これらはちょうど2つの非常に簡単な例でした。より多くのHaskellを学ぶにつれて、あなたは自分自身のためにますます多くのcombinator関数を必要としている(そして発明している)ことに気付くでしょう。Haskellは、関数を取り出して変換し、結合し、裏返しにしてから一緒に貼り付けることができる方法で非常に強力です。私たちがそれをするときに時々必要とする接着剤のビット、私たちがコンビネータと呼ぶそれらのビット。

「Combinator」は、Haskellでの使用で正確に定義されているわけではありません。他の関数を引数として取る関数を参照するためにそれを使用するのが最も正しいです コンビネータ微積分 しかし、Haskellの用語では、「変更」または「組み合わせ」機能、特に Functor または Monad.この場合、コンビネータは「コンテキストで何らかのアクションまたは値を取得し、コンテキストで新しい変更されたアクションまたは値を返す」関数で

あなたの例, maybeIO しばしば呼ばれます optional

optional :: Alternative f => f a -> f (Maybe a)
optional fa = (Just <$> fa) <|> pure Nothing

そして、それは計算を取るので、コンビネータのような性質を持っています f a そして、その値に失敗を反映するように一般的にそれを変更します。

これらがコンビネータと呼ばれる理由は、それらがどのように使用されているかにも関係しています。見るべき典型的な場所 optional (そして確かに, Alternative 一般的に)はパーサーコンビネータライブラリにあります。ここでは、simpleを使用して基本的なパーサーを構築する傾向があります Parsers様

satisfy :: (Char -> Bool) -> Parser Char
anyChar    = satisfy (const True)
whitespace = satisfy isSpace
number     = satisfy isNumeric

そして、「コンビネータ」を使用してそれらの動作を「変更」します。

-- the many and some combinators
many :: Alternative f => f a -> f [a] -- zero or more successes
some :: Alternative f => f a -> f [a] -- one  or more successes

many f = some f <|> pure []
some f = (:) <$> f <*> many f

-- the void combinator forgets what's inside the functor
void :: Functor f => f a -> f ()
void f = const () <$> f

-- from the external point of view, this is another "basic" Parser
-- ... but we know it's actually built from an even more basic one
-- and the judicious application of a few "combinators"
blankSpace = Parser ()
blankSpace = void (many whitespace)

word :: Parser String
word = many (satisfy $ not . isSpace)

多くの場合、複数の関数を組み合わせた関数を呼び出すこともあります/Functors/Monads 「コンビネータ」も、おそらくニーモニックに意味があります

-- the combine combinator
combine :: Applicative f => f a -> f b -> f (a, b)
combine fa fb = (,) <$> fa <*> fb

-- the ignore-what's-next combinator
(<*) :: Applicative f => f a -> f b -> f a
fa <* fb = const <$> fa <*> fb

-- the do-me-then-forget-me combinator
(*>) :: Applicative f => f a -> f b -> f b
fa *> fb = flip const <$> fa <*> fb

line = Parser String
line = many (satisfy $ \c -> c /= '\n') <* satisfy (=='\n')

しかし、最終的には、コンビネータは厳密な表現よりもAPIの意図と使用に関するものです。関数や関数のような"基本的な部分"から構築されたライブラリを頻繁に見ることができます satisfy 次に、変更され、一連の「コンビネータ」と組み合わされます。ザ- Parser 上記の例は典型的な例ですが、これは非常に一般的なHaskellパターンです。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top