23390


`clojure`初心者です。  
非常に初歩的な質問ですみませんが、`clojure`といえども、`var`に対する再束縛は問題なくできると考えてよいのでしょうか？

[code]

    user => (def a 100)
    user => a
    ; 100と表示
    user => (def a 200)
    user => a
    ; 200と表示
    
[/code]

`clojure`が変数(`var`のこと)の参照先オブジェクトを変更できないイミュータブルな性質を持つことは`java`等の経験から理解できます。

その一方で、関数型言語では「再代入」はできない、なんていう言葉も何度もそれとなく聞いておりまして、それはなんとなくそういうものなのかと思っていました。それゆえ、本日思わぬ形で上記の例に出会ってしまい、今までも似たようなものは見てたのでしょうが、はたと気がついて混乱しております。

私の拙い理解では、これだと再代入は「できている」、`clojure`流の言葉では「再束縛」が起きているとでも言えばいいのでしょうか、とにかく同じ`var`に別の値を結びつけることができています。

[code]

    user=> (defn xyz []                               
             (let [abc 100]                    
                  (let [abc 200] (+ abc 100))))
    user=> (xyz)
    300
    
[/code]

`let` を使っても同じことです。

変数の値の再代入は他の言語でも普通にありますし、この動作がおかしいなどと言うつもりは毛頭ないのですが、いわゆる「関数型言語に再代入はない」的な言説が何を言わんとしているのかがわからなくなりました。再代入ができないのではなく、再代入を避けやすい、という意味で使われることが多いのでしょうか。

「プログラミングclojure」という本を読みながら学習しているのですが、このことについては特に記述がないか、見つけられません。なので、しょーもない質問とは思いますが、おかしな理解をするのも良くないので、どなたか教えてくださると嬉しいです。


CC BY-SA 3.0
2016-03-23T09:42:01.687
23384
2018-04-29T02:53:02.010
9403
question
clojure
clojureで「再代入できない」と言われる意図は何なのか


`def`は、その名前空間のトップレベルのvar（≒グローバル変数）を新しく作ります。まあこれは例外ですね。現代的なプログラミング言語では、グローバル変数の変更はあまり重要ではありませんし。

`let`の入れ子の場合、「同じ名前だけど別の変数」を定義しているだけです。内側の`let`で`abc`に別の値を束縛しても、外側の`let`の`abc`は別の変数なので、影響を与えません。別の関数の仮引数の変数名が呼び出し元の変数名とカブっている場合（以下のコード片参照）とまったく同じです。

[code]

    (defn foo [abc] (+ abc 100))
    (defn xyz [] (let [abc 100] (foo 200)))
    (xyz)       ;;=> 300
    
[/code]

変数を再代入できるというのは、同じ名前というだけではなく、同じ変数の値を変更できることです。たとえば以下のRubyコード片のような書き方は、Clojureだとそのままではできません（やるにはrefなどを使う）。

[code]

    x = 3
    3.times { x *= 2 }
    x           #=> 24
    
[/code]


CC BY-SA 3.0
2016-03-23T11:12:18.347
23390
2016-03-23T11:12:18.347
4010
23384
answer


再束縛と再代入の違いは関数型を始めたころに僕も悩みました。どちらも同じじゃないかと  
しかし背景で起こっていることはかなり違います。  
何を可変性(mutable)と呼び、何を不変性(immutable)と呼んでいるのかを理解するには下記の例が参考になるかもしれません。  
Cの例で申し訳ないのですが、以下のような記述は再代入です。

[code]

    #include <stdio.h>
    int main(void) {
        char str[] = {'h','e','l','l','o','\0'};
        printf("before value: %s\n",str);
        printf("before address: %x\n", &str);
        str[4] = ' ';
        printf("after value: %s\n", str);
        printf("after address: %x\n", &str);
        scanf("%c\n", &str); // to keep displaying the console window
        return 0;
    }
    
[/code]

上記を実行してもらえばわかりますが、文字配列型変数strはstr[4]='
'という変更(破壊的代入とも言います)を行った後でもメモリアドレスを共有しています。つまり再代入というのは、変数に紐づけられたメモリアドレスが格納している値を直接変更することをいいます。  
ここで'o'という文字は値として完全に上書きされており、メモリのどこを探しても'o'は出てきません。  
一方でJavaやClojureなどの現代的な言語では文字列はimmutableとして実装されており、文字列への再代入は認められておりません。ではどのようにして上記のような変更を実現するのかというと、文字列型値"hello"とは別に文字列型値"hell
"を生成し、文字列型変数strが指し示すメモリアドレスを"hello"から"hell
"にすり替えるのです。ここでは変更前後でもはやメモリアドレスは同一のものを指しません。Javaが文字列をimmutableとして設計した理由については、おそらく下記URLのほうが詳しいので割愛いたします。  
<https://stackoverflow.com/questions/22397861/why-is-string-immutable-in-java>  
また、Javaはリストやマップなどのデータ構造をmutableとして設計しました。常識的に考えるとリストを変更するたびにオブジェクトをコピーする必要が出てきて著しく効率が悪いので、Javaはリストをimmutableとして設計しなかったのです。  
一方でclojureは、haskellなどを参考にしimmutableの要件(つまり値を上書きしないこと)を満たしながら効率的にリストの入れ替えができるようにデータ構造を設計しました。ここでいう入れ替えとは再束縛のことで、再束縛を行っても過去の値(前述の例では'o')は消滅しないのです。


CC BY-SA 3.0
2018-04-29T02:53:02.010
43645
2018-04-29T02:53:02.010
25980
23384
answer