Haskellおよび/またはラムダ計算におけるパラメーターなしのラムダ式


9

SchemeやPythonなどの熱心な言語では、パラメータなしのラムダ式を使用して、評価を遅延させることができます。たとえば、Scheme(チキンスキーム)の場合:

#;1> (define (make-thunk x) (lambda () (+ x 1)))
#;2> (define t (make-thunk 1))
#;3> (t)
2

2行目tは未評価の式にバインド(lambda () (+ 1 1))され2、3行目で評価されます。

同様に、Pythonでは:

>>> def make_thunk(x): return lambda: x + 1
... 
>>> t = make_thunk(1)
>>> t()
2

この手法を使用すると、熱心な言語で遅延評価を実装できます。

そのため、言語が既に遅延しており、遅延式を作成する必要がないため、Haskellにはパラメーターなしのラムダ式がないと予想していました。驚いたことに、Haskellではラムダ式を書くことが可能であることがわかりました

\() -> "s"

これは次の()ような値にのみ適用できます:

(\() -> "s") ()

結果を出す

"s"

この関数を()例外以外の引数に適用すると、(少なくともテスト中に確認できる限り)例外がスローされます。これは、SchemeやPythonでの遅延評価とは異なります。これは、式を評価するために引数が必要なためです。では、変数のないラムダ式(など\() -> "s")はHaskellで何を意味し、それは何のために役立つのでしょうか?

また、同様のパラメーターなしのラムダ式が(いくつかの種類の)ラムダ計算に存在するかどうかも知りたいです。


()例外をスローする以外の引数にこの関数を適用すると...」プログラムに例外がスローされますか、それともコンパイラーがコードが型チェックしないと文句を言うのですか?
Fried Brice

回答:


12

さて、他の答え\() -> "something"はHaskellでの意味をカバーしています:()引数として取る単項関数です。

  • 引数なしの関数とは何ですか?- 価値。実際、変数をその値に評価されるヌラリ関数と考えると役立つ場合があります。let引数のない関数(実際には存在しない)の-syntaxは、最終的に変数バインディングを提供します。let x = 42 in ...

  • ラムダ計算にはゼロ関数がありますか?–いいえ。すべての関数は引数を1つだけ受け取ります。ただし、この引数はリストの場合や、関数が次の引数を取る別の関数を返す場合があります。Haskellは後者のソリューションを好むため、a b c実際には2つの関数呼び出しになります((a b) c)。ヌル関数をシミュレートするには、未使用のプレースホルダー値を渡す必要があります。


1
「引数のない関数とは何か-値」の+1、および拡張により、nullary関数ビュー。関数と値がどのように関連しているかを見ることができると便利だと思います。関数は値の一般化として見ることができます。
KChaloux 2014

@amon:したがって、nullary関数は、Schemeのような言語でのみ意味があり、明示的に評価をトリガーでき、関数(プロシージャ)はその値と副作用の両方に役立ちます。
Giorgio

@Giorgio Lispは、多くの構文糖を含むラムダ計算の直接エンコーディングですが、ラムダ計算の場合、nullary関数を持つことはできません。すべてがリストであるため、関数アプリケーションの式(f)は概念的にはヘッド値が'f0でテールがnilの単一要素リストです。したがって、を使用しconsてとして記述できます(cons 'f '())。このテールは(概念的に)引数として使用されるリストであり、nilが空のリストを表すことは重要ではありません。LispとHaskellの間の違いは、後者が暗黙のカリー化を持っているということなので、表現(f)手段の異なるもの
アモン

2
しかし、はい、引数のない関数は、一般にHaskellでは役に立ちません。それは、それが遅延していて純粋だからです。厳密な言語では、引数のない関数を使用して遅延をシミュレートしたり、汎用のコールバックを使用したりできます。()(MLの場合のように)などのダミーの引数を提供する必要があるかどうか(LispまたはCのような言語の場合のように)故意に提供しないかどうかは、実際には重要ではありません。
アモン

9

あなたは()Haskellの意味を誤解している。これは値の不足ではなく、Unitタイプ(空の括弧のセットによって参照されるタイプ自体)の唯一の値です。()

ラムダ\() -> "s"はパターンマッチングを使用するように構築できるため、ラムダ式は「無名関数を作成し、()パターンに一致する入力を期待する」と明示的に言っています。それを行う意味はあまりありませんが、それは確かに許可されています。

他の方法でラムダを使用したパターンマッチングを使用することもできます。次に例を示します。

map (\(a, b) -> a + b) [(1,2), (3,4), (5,6)] -- uses pattern matching to destructured tuples

map (\(Name first _) -> first) [Name "John" "Smith", Name "Jane" "Doe"] -- matches a "Name" data type and its first field

map (\(x:_) -> x) [[1,2,3], [4,5,6]] -- matches the head of a list

それ以外Unit()Haskell でも綴られています。

@delnan私はScalaに習熟している= Pその点について少し具体的になるように答えを更新します。
KChaloux 2014

したがって、このパターンマッチングメカニズムはラムダ計算にはないHaskellの機能(?)であると思います
Giorgio

@ジョルジオ私は私の基本的なラムダ計算に精通していませんが、私の理解はあなたが正しいということです。パターンマッチングは言語機能であり、ラムダ計算に固有のものではありません。
KChaloux 2014

1
@Giorgio Soundについて 各言語が副作用をどのように扱うかについてこれをピン留めできると思います。パラメーターのない関数は、A)不変の値、またはB)副作用のあるものです。Haskellは、Python(またはSchemeの方が少ない)よりも副作用を避けようとするため、パラメーターのない関数が値であると想定しています。入力を受け取らずに同じ値を返す無名関数を提供することはあまり意味がありません(入力constを取りすべてを同じ値に変換するとは異なります)。
KChaloux 2014

1

() -> "s"1つの引数を取るラムダ関数です。

ghci> :t (\() -> "s")
(\() -> "s") :: () -> [Char]

()空のタプル(Unitと呼ばれることもあります)は、Haskellでは完全な型です。メンバーは1つだけです(_|_*は無視)。これもと記述され()ます。の定義を見てください()

data () = ()

つまり、ラムダ式の左側のフォームは、構文的には()、の唯一のメンバーであると一致するパターンマッチ()です。()型の有効なメンバーは1つしかないため、それを呼び出す有効な方法は1つしかありません(引数として指定します)()

Haskellではこのような関数はあまり有用ではありません。質問で確認したように、デフォルトでは用語はとにかく遅延評価されるためです。

より詳細な説明については、この回答を参照してください。

* _|_は「下」または「未定義」と呼ばれます。常に型チェックを行いますが、プログラムをクラッシュさせます。を取得する方法の典型的な例_|_let x = x in xです。


「このような関数はHaskellではあまり有用ではありません。質問で確認したように、デフォルトではいずれにしても遅延評価されます。」:それだけでなく、引数を指定する必要があります。たとえば、Schemeで行う必要はありません。 。
Giorgio
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.