web-dev-qa-db-ja.com

関数型プログラミングに時間関数はどのように存在することができますか?

私は関数型プログラミングについてあまり知らないことを認めなければなりません。私はあちらこちらでそれについて読みました、そして関数プログラミングでは、関数が何度呼び出されても、同じ入力に対して同じ関数を返すことを知りました。これは、関数式に含まれる入力パラメータの同じ値に対して同じ出力に評価される数学関数とまったく同じです。

たとえば、次のように考えます。

f(x,y) = x*x + y; // It is a mathematical function

何回f(10,4)を使用しても、その値は常に104になります。そのため、f(10,4)を書いた場所はどこでも、式全体の値を変えることなく、104に置き換えることができます。この性質は式の 参照透明度 と呼ばれます。

ウィキペディアが言うように( link )、

逆に、関数コードでは、関数の出力値は関数に入力された引数にのみ依存するため、引数xに同じ値を指定して関数fを2回呼び出すと、同じ結果になりますf(x)両方。

現在の時間を返す)時間関数は関数型プログラミングに存在できますか?

  • もしそうなら、それはどうやってそれが存在することができますか?それは関数型プログラミングの原則に違反しませんか?特に違反しています 参照透明性 これは関数型プログラミングの特性の1つです(私がそれを正しく理解していれば)。

  • そうでなければ、関数プログラミングの現在時刻をどのように知ることができるでしょうか。

619
Nawaz

それを説明するもう1つの方法はこれです:nofunctionは現在の時刻を取得できますが(これは変化し続けるので)、actionは現在時刻を取得できます。 getClockTimeは現在の時刻を取得するactionを表す定数(または、必要に応じてNULL関数)であるとしましょう。これはactionはいつ使用しても同じであるため、実際の定数です。

同様に、printは時間をかけてコンソールに表示する関数です。純粋な関数型言語では関数呼び出しが副作用を持つことはできないので、代わりに、それがタイムスタンプを受け取り、それをコンソールに出力するactionを返す関数であると想像します。繰り返しますが、これは実際の関数です。同じタイムスタンプを指定すると、毎回同じactionが返されるためです。

では、どうやって現在の時刻をコンソールに表示させるのですか。さて、あなたは二つの行動を組み合わせる必要があります。それでは、どうすればそれができるのでしょうか。 printはアクションではなくタイムスタンプを要求するので、getClockTimeprintに渡すことはできません。しかし、>>=という、2つのアクション、タイムスタンプを取得するアクション、および引数として1つを受け取りそれを出力するというアクションがあると想像できます。これを前述のアクションに適用すると、結果は... tadaaa ...現在の時刻を取得して表示する新しいアクションです。そしてこれは偶然にもHaskellで行われている方法とまったく同じです。

Prelude> System.Time.getClockTime >>= print
Fri Sep  2 01:13:23 東京 (標準時) 2011

したがって、概念的には、このように考えることができます。純粋な機能的プログラムは入出力を実行せず、ランタイムシステムが実行するactionを定義します。actionは毎回同じですが、実行した結果は実行時の状況によって異なります。

これが他の説明よりも明確であるかどうか私は知りませんが、それは時々私がこのようにそれを考えるのを助けます。

155
dainichi

はいといいえ。

異なる関数型プログラミング言語はそれらを異なる方法で解決します。

Haskell(非常に純粋なもの)では、このようなことはすべて I/Oモナド と呼ばれるもので行わなければなりません - こちら を参照してください。

あなたはそれをあなたの関数(世界状態)への別の入力(そして出力)を得ること、あるいは変化する時間を得ることのような「不純」が起こる場所としてより簡単であると考えることができます。

F#のような他の言語には不純さが組み込まれているだけなので、通常の命令型言語のように、同じ入力に対して異なる値を返す関数を持つことができます。

Jeffrey Burkaが彼のコメントで述べたように、これは I/Oモナドのニース紹介 Haskell wikiから直接です。

349
Carsten

Haskellでは、副作用を処理するためにmonadと呼ばれる構文を使います。モナドとは基本的に、値をコンテナにカプセル化し、値をコンテナ内の値から値に連鎖させる機能をいくつか持っているということです。私たちのコンテナが型を持っている場合:

data IO a = IO (RealWorld -> (a,RealWorld))

IOアクションを安全に実装できます。この型の意味は、次のとおりです。IO型のアクションは関数で、RealWorld型のトークンを受け取り、結果とともに新しいトークンを返します。

この背後にある考え方は、各IOアクションが、魔法のトークンRealWorldで表される外部状態を変更するということです。モナドを使用すると、現実世界を変化させる複数の機能を連鎖させることができます。モナドの最も重要な機能は>>=で、bindと発音されます。

(>>=) :: IO a -> (a -> IO b) -> IO b

>>=は1つのアクションと、このアクションの結果を受け取り、このアクションから新しいアクションを作成する関数を取ります。戻り型は新しいアクションです。たとえば、現在の時刻を表す文字列を返す関数now :: IO Stringがあるとしましょう。それをプリントアウトするために関数putStrLnとそれを連鎖させることができます。

now >>= putStrLn

あるいはdo-表記法で書かれています。これは命令型プログラマにはよく知られています。

do currTime <- now
   putStrLn currTime

私たちが外の世界に関する突然変異と情報をRealWorldトークンにマッピングするので、これはすべて純粋です。そのため、毎回このアクションを実行すると、もちろん異なる出力になりますが、入力は同じではありません。RealWorldトークンは異なります。

143
fuz

ほとんどの関数型プログラミング言語は純粋ではありません。すなわち、それらは関数がそれらの値に依存するだけではないようにします。これらの言語では、現在時刻を返す関数を持つことは完全に可能です。この質問にタグを付けた言語から、 Scala および F# (および ML の他のほとんどの変種)に適用されます。

純粋な HaskellClean のような言語では、状況は異なります。 Haskellでは、現在時刻は関数を通して利用可能ではないでしょうが、副作用をカプセル化するHaskellの方法である、いわゆるIOアクションです。

Cleanではそれは関数ですが、関数はその引数として世界の値を取り、その結果として(現在の時刻に加えて)新しい世界の値を返します。型システムは、各ワールド値が1回しか使用できないようにします(そして、ワールド値を消費する各関数は新しいものを生成します)。このように、time関数は毎回異なる引数で呼び出されなければならず、したがって毎回異なる時間を返すことが許されます。

69
sepp2k

「現在時刻」は関数ではありません。パラメータです。あなたのコードが現在の時間に依存しているなら、それはあなたのコードが時間によってパラメータ化されていることを意味します。

47
Vlad Patryshev

それは純粋に機能的な方法で絶対に行うことができます。それにはいくつかの方法がありますが、最も簡単なのはtime関数に時間だけでなく次の時間の測定値を得るために呼び出さなければならない関数も返すことです.

C#では、次のように実装できます。

// Exposes mutable time as immutable time (poorly, to illustrate by example)
// Although the insides are mutable, the exposed surface is immutable.
public class ClockStamp {
    public static readonly ClockStamp ProgramStartTime = new ClockStamp();
    public readonly DateTime Time;
    private ClockStamp _next;

    private ClockStamp() {
        this.Time = DateTime.Now;
    }
    public ClockStamp NextMeasurement() {
        if (this._next == null) this._next = new ClockStamp();
        return this._next;
    }
}

(これは単純で実用的ではないことを意味する例であることに注意してください。特に、リストノードはProgramStartTimeに基づいているためガベージコレクションできません。)

この「ClockStamp」クラスは不変のリンクリストのように動作しますが、実際にはノードは要求に応じて生成されるため、「現在の」時刻を含めることができます。次のように、時間を測定したいすべての関数は 'clockStamp'パラメータを持ち、その結果に最後の時間測定値も返す必要があります(したがって、呼び出し元は古い測定値を見ません)。

// Immutable. A result accompanied by a clockstamp
public struct TimeStampedValue<T> {
    public readonly ClockStamp Time;
    public readonly T Value;
    public TimeStampedValue(ClockStamp time, T value) {
        this.Time = time;
        this.Value = value;
    }
}

// Times an empty loop.
public static TimeStampedValue<TimeSpan> TimeALoop(ClockStamp lastMeasurement) {
    var start = lastMeasurement.NextMeasurement();
    for (var i = 0; i < 10000000; i++) {
    }
    var end = start.NextMeasurement();
    var duration = end.Time - start.Time;
    return new TimeStampedValue<TimeSpan>(end, duration);
}

public static void Main(String[] args) {
    var clock = ClockStamp.ProgramStartTime;
    var r = TimeALoop(clock);
    var duration = r.Value; //the result
    clock = r.Time; //must now use returned clock, to avoid seeing old measurements
}

もちろん、この最後の測定値をやりとりしなければならないのは少し不便です。特に言語設計レベルで、定型句を非表示にする方法はたくさんあります。 Haskellはこの種のトリックを使い、次にモナドを使って醜い部分を隠します。

20
Craig Gidney

答えやコメントがどれもcogebrasやcoinductionに言及していないことに驚きました。通常、無限のデータ構造を推論するときに同時誘導が言及されますが、CPUのタイムレジスタなどの無限の観測の流れにも適用できます。代数は隠れた状態をモデル化します。そして共誘導モデル観察その状態。 (通常の帰納モデル構築中状態)

これは反応的関数型プログラミングの話題です。この種のものに興味があるなら、これを読んでください: http://digitalcommons.ohsu.edu/csetech/91/ (28 pp。)

14

はい、それはパラメータとしてその時間を与えられた場合、純粋な関数が時間を返すことは可能です。異なる時間引数、異なる時間結果。それから他の時間の関数も形成し、それらを単純な語彙の関数(-of-time) - 変換(高次)関数と組み合わせます。このアプローチはステートレスであるため、ここでの時間は離散的ではなく連続的(解像度に依存しない)になり、大幅に モジュール性が高まります 。この直感が関数型反応的プログラミング(FRP)の基本です。

11
Conal

はい!あなたは正しいです! Now()、CurrentTime()、またはそのようなフレーバーのメソッドシグネチャは、ある意味では参照透過性を示していません。しかし、コンパイラへの命令によって、それはシステムクロック入力によってパラメータ化されます。

出力では、Now()は参照透明度に従わないように見えるかもしれません。しかし、システムクロックの実際の振る舞いとその上の機能は、参照透過性に準拠しています。

10
MduSenthil

はい、時間取得機能は、不純な関数型プログラミングとして知られている関数型プログラミングのわずかに修正されたバージョンを使用する関数型プログラミングに存在することができます(デフォルトまたはメインのものは純粋な関数型プログラミングです)。

時間を稼ぐ(またはファイルを読む、またはミサイルを発射する)場合、コードは仕事を終わらせるために外界と対話する必要があり、この外界は関数型プログラミングの純粋な基盤に基づいていません。純粋な関数型プログラミングの世界がこの不純な外界と対話できるようにするために、人々は不純な関数型プログラミングを導入しました。結局のところ、外の世界と対話しないソフトウェアは、いくつかの数学的計算をすること以外には有用ではありません。

どのコードが不純でどのコードが純粋であるか(F#など)を区別することは容易ではないため、このような不潔な機能が組み込まれた関数型プログラミングプログラミング言語はほとんどありません。そのコードは、Haskellのように、純粋なコードと比べて明らかに際立っています。

これを理解するためのもう一つの興味深い方法は、関数型プログラミングのget time関数が、時間、世界に住む人々の数など、世界の現在の状態を持つ "world"オブジェクトを取得することです。オブジェクトは常に純粋です。つまり、同じ世界を通過すると、常に同じ時間が得られます。

10
Ankur

あなたの質問は、コンピュータ言語の2つの関連した尺度、すなわち機能的/命令的および純粋/不純的を組み合わせたものです。

関数型言語は関数の入出力間の関係を定義し、命令型言語は実行するために特定の順序で特定の操作を記述します。

純粋な言語は副作用を生み出すことも依存することもなく、不純な言語はそれを至る所で使用します。

100パーセント純粋なプログラムは基本的に役に立ちません。それらは興味深い計算を実行するかもしれませんが、それらは副作用を持つことができないのでそれらが入力も出力も持たないのであなたは彼らが計算したものを決して知りません。

まったく有用であるためには、プログラムは少なくとも卑劣な不純でなければなりません。純粋なプログラムを便利にする1つの方法は、それを薄い不純なラッパーの中に入れることです。この未テストのHaskellプログラムのように:

-- this is a pure function, written in functional style.
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

-- This is an impure wrapper around the pure function, written in imperative style
-- It depends on inputs and produces outputs.
main = do
    putStrLn "Please enter the input parameter"
    inputStr <- readLine
    putStrLn "Starting time:"
    getCurrentTime >>= print
    let inputInt = read inputStr    -- this line is pure
    let result = fib inputInt       -- this is also pure
    putStrLn "Result:"
    print result
    putStrLn "Ending time:"
    getCurrentTime >>= print
7
NovaDenizen

あなたは関数型プログラミング、つまりI/Oの実行において非常に重要なテーマをブローチングしています。多くの純粋な言語が取り組む方法は、埋め込まれたドメイン固有の言語を使うことです。例えば、それがエンコードすることであるサブ言語のようなものですアクション、結果を得ることができます。

たとえばHaskellランタイムは、私のプログラムを構成するすべてのアクションで構成されるmainというアクションを定義することを期待しています。その後、ランタイムはこのアクションを実行します。ほとんどの場合、そうすることで、純粋なコードを実行します。時々、ランタイムは計算されたデータを使用してI/Oを実行し、データを純粋なコードにフィードバックします。

これは不正行為のように思えるかもしれませんが、ある意味では、アクションを定義してランタイムがそれらを実行することを期待することで、プログラマーは通常のプログラムでできることすべてを実行できます。しかしHaskellの強い型システムはプログラムの純粋な部分と「不純な」部分との間に強い障壁を作り出します。たとえば、現在のCPU時間に2秒を加えてそれを印刷することはできません。 CPU時間、および2秒を追加して結果を表示する別のアクションに結果を渡します。しかし、たくさんのプログラムを書くことは悪いスタイルと見なされます。なぜなら、すべてのことを教えてくれるHaskell型と比較して、どの効果が引き起こされるのかを推測するのが難しいからです値とは何かを知ることができます。

例:Cのclock_t c = time(NULL); printf("%d\n", c + 2);とHaskellのmain = getCPUTime >>= \c -> print (c + 2*1000*1000*1000*1000)。演算子>>=はアクションを構成するために使用され、最初の結果を2番目のアクションをもたらす関数に渡します。この非常に難解なHaskellコンパイラは構文糖をサポートしているので、後者のコードを次のように書くことができます。

type Clock = Integer -- To make it more similar to the C code

-- An action that returns nothing, but might do something
main :: IO ()
main = do
    -- An action that returns an Integer, which we view as CPU Clock values
    c <- getCPUTime :: IO Clock
    -- An action that prints data, but returns nothing
    print (c + 2*1000*1000*1000*1000) :: IO ()

後者は非常に必須のようですね。

2
MauganRa

もしそうなら、それはどうやってそれが存在することができますか?それは関数型プログラミングの原則に違反しませんか?特に参照の透明性に違反しています

純粋に機能的な意味では存在しません。

そうでなければ、関数プログラミングの現在時刻をどのように知ることができるでしょうか。

最初にコンピュータ上で時刻がどのように取得されるのかを知っておくと便利です。基本的には、時間を追跡する回路が内蔵されています(これが、コンピュータが通常小型の電池を必要とする理由です)。それから、あるメモリレジスタに時間の値を設定する内部プロセスがあるかもしれません。これは基本的に、CPUによって取得できる値になります。


Haskellには、いくつかのIOプロセスを実行するために作成できる型を表す 'IOアクション'という概念があります。そのため、time値を参照する代わりに、IO Time値を参照します。これはすべて純粋に機能的なものです。 timeを参照しているのではなく、'タイムレジスタの値を読む'の行に沿って何かを参照しています。

Haskellプログラムを実際に実行すると、IOアクションが実際に行われます。

1