challenge 変数の初期値

WEB+DB 43のRecent Perl Worldを読んで知りました。

変数を初期化するに当たってPerlでは
my $var ||= 'foo';
とかきます。この不備を補うためPerlの5.10には
Defined-or演算子が実装されたそうです。
$zero //= 25;
このような変数のデフォルト設定を行う方法を各種言語ではどうかくのでしょうか。


Posted feedbacks - Nested

Flatten Hidden
1
foo ||= 'hoge'
1
zero = 25 if zero is None else zero

Squeak Smalltak では変数の初期値が nil なので、nil の場合のみブロックを実行する #ifNil: を適宜コールして初期化などを行ないます。

1
foo ifNil: [foo := 'hoge']
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
お題と同じ主旨ということであれば、Common LispというかLISP系では、
orを使うのがイディオムっぽいですが、下記の例では、既にvarに
値が設定されていないとエラーになってしまいます。

;(setq var nil)
;etc 〜

(let ((var (or var 'foo)))
  var)
;=> foo

大域変数については、defvarがあり、既に値が設定されている場合は、
値が上書きされません。

;; 初回
(defvar foo 'foo)
=> foo

;; 以降
(defvar foo 'bar)
=> foo

boundp, fboundp なのかなあ、と思ったのですが、それとは違うんでしょうか?正直なところお題の背景をあまり理解できていませんが……

局所変数だったら初期化しないと nil なので or が使えますけど、あまり見かけない気がしますね。Emacs Lisp だとコマンドの引数に使うことがあるかも。

自分もboundpもありかと思ったのですが、
シンボルの値となると、また別の話になってくる
かと思いorを使った例を書きました。
Perlは良く知らないので外しているかもしれませんが…。

確かに、用途を考えてみるとそちらの方が近いような気がしますね。

お題への答えとしては引数リストの initform, svar もアリかも。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
(defun foo (&optional (var "foo")) (if var (write var)))
(defun bar (&optional (var nil sv)) (if sv (write var)))

(dolist (form '((foo) (foo "foobar") (foo nil)
                (bar) (bar "foobar") (bar nil)))
  (format t "~&~S prints: " form)
  (eval form))

#|
実行結果:

(FOO) prints: "foo"
(FOO "foobar") prints: "foobar"
(FOO NIL) prints: 
(BAR) prints: 
(BAR "foobar") prints: "foobar"
(BAR NIL) prints: NIL
|#
すいません、defvarの返り値は、変数名になるのですが、
前の書き方だと混乱を招くので、書き直しました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
;; 初回
(defvar var 'foo)

var
;=> foo

;; 以降
(defvar var 'bar)

var
;=> foo
無理に書けばこう、なのかな?
1
2
3
4
try:
  var
except NameError:
  var = 'foo'

OCaml とか Haskell には初期化されてない変数が存在しませんね。そういう言語って他にも結構あるような。

お題は変数の「デフォルト値」をセットするコードなので、「デフォルト値」以外をセットできないHaskellでは普通にイニシャライズすればオッケイ?

それとは別に、スレッド間通信用のMVar型はnewEmptyMVarでもって、値のセットされていないインスタンスを手に入れられるので、MVarでやってみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
module Main
    where

import Control.Concurrent
import Control.Monad

uninitedMVar :: IO (MVar Int)
uninitedMVar = newEmptyMVar

initedMVar :: IO (MVar Int)
initedMVar = newMVar 4

setIfEmpty :: MVar Int -> Int -> IO ()
setIfEmpty em i = do
    fEmpty <- isEmptyMVar em
    if (fEmpty) then
        tryPutMVar em i >> return ()
        else
            return ()

setAndPrint :: MVar Int -> IO()
setAndPrint em = do
    setIfEmpty em 7
    i <- takeMVar em
    putStrLn $ show i

i = 4

main = do
    print i
    uninitedMVar >>= setAndPrint
    initedMVar >>= setAndPrint
==>
4
7
4

if文きらいです...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module Main
    where

import Control.Concurrent
import Control.Monad

uninitedMVar :: IO (MVar Int)
uninitedMVar = newEmptyMVar

initedMVar :: IO (MVar Int)
initedMVar = newMVar 4

setIfEmpty :: MVar Int -> Int -> IO ()
setIfEmpty em i = isEmptyMVar em >>= \fEmpty -> when (fEmpty) $ putMVar em i

setAndPrint :: MVar Int -> IO()
setAndPrint em = do
    setIfEmpty em 7
    takeMVar em >>= putStrLn.show

i = 4 --immutable

main = do
    print i
    uninitedMVar >>= setAndPrint
    initedMVar >>= setAndPrint
Dでも、変数はすべて初期化されます。 なお、浮動小数点型の変数はnan (not a number) で初期化され、これを「!<>=」という演算子でチェックすることができます。 以下の例ではuninitializedと出力されます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import std.stdio;

void main()
{
    float foo;

    if (foo !<>= float.nan) {
        writefln("uninitialized");
    }
    else {
        writefln("initialized to some value");
    }
}

すみません。Perlが読めません。 ”この不備を補うため”とありますが、 my $var ||= 'foo'; にはどんな不備があるのでしょうか? これは$var型の変数myを宣言し、fooを代入しているということでいいのでしょうか? 文字列型varにfooを代入しているようにも見えます。 Defined-or演算子とは何でしょうか?検索してもすぐには見付けられませんでした。 ”//”がDefined-or演算子ということでいいのでしょうか? ”変数のデフォルト設定”とは変数の初期化とどう違うのでしょうか? それとも、初期化前の値を、型ごとに決めるということでしょうか?初期化前の値を決めることに合理性が見出せないので考えにくいですが…。 お題は自然言語のみで題意を理解出来るようにして欲しいと思います。

お題投稿者ではありませんが、勝手に答えると

 my $var ||= 'foo';

というコードは$varという変数がnilのときだけ'foo'を代入するもので
「この不備」と言われているのはコード中のどこかで$varにnilが代入された場合に
上記のコード部分で望まない代入が起きることでしょう。

 $zero //= 25;

こちらのコードなら確実に一度だけの初期化が行われるようです。

このコードの使い道は、と言えば、Cにおける手続き内でのstatic変数の同等機能の実現などが
考えられます。

という解釈の元に#5950を投稿しましたが、全然外してたりして。
つまり定数を宣言すればよいということでしょうか…。
const string hoge = "piyo";
これだと宣言時に値が確定してないときには具合が悪いですね。
こんなのはどうでしょう?

わかりにくいですね…。
1
2
string hoge;
hoge = hoge == "" ? "piyo" : hoge;
すみません。これだとコンパイル出来ませんね。
1
2
string hoge = "";
hoge = hoge == "" ? "piyo" : hoge;
> my $var ||= 'foo';
>というコードは$varという変数がnilのときだけ'foo'を代入するもので

 であれば余り問題は無いのですが,このコードは以下のコードと同じ意味になります。

my $var = $var || 'foo'; #||は短絡演算子

 問題中にある様に変数の宣言と一緒にやる場合には然程問題になりませんが,perlでは0は偽として評価されるので,値が設定されていない場合に初期値を設定する意図で使用するケースで,以下の様に問題が出る場合があるのです。

my $var;
[..snip..]
$var = 0;
[..snip..]
$var ||= 1; #0が設定されている場合は0のままとするつもり
1
2
3
4
5
6
var var1 = "foo";
var var1 = var1 || 'bar';
alert(var1);

var var2 = var2 || 'bar';
alert(var2);
ある言語でサポートされている機能が,別の言語でどのようにサポートされているか
あるいはどのように代替するかは興味が尽きないですね.

ただ,言語の機能を実際にそれが必要なプログラミングの場面あるいは何故その機能
が必要なのかという理由と切り離してしまうと,プログラミングスタイルの全然違う
言語では意味をなさなくなってしまうことがあります.たとえば,「変数の初期化」
の場合Perlでdefined-or演算子が必要な理由は自明かもしれませんが,他の言語では
自明ではない場合がありそうに思います.あるいは,「変数の初期化」ということ自
体に意味がない言語,さらに,「変数」という言葉が指す概念がPerlとは違うものも
あるでしょう.

〜言語には〜というプログラミングをする場面で,〜という機能を使って,〜という
問題点を解決するけれど,他の言語ではどうしてるのか?

てな感じの問いかけになっているより幅広い言語のやり方を知ることができるのでは
ないかと思います.
Perlではmy宣言された変数はそもそも未定義のはずですから、
"my $var ||= 'foo';"というコードの意図は私もうまく解釈できません。

||=や//=(Defined-or)を使いそうなシチュエーションとしては、
可変のパラメーターを使用して変数の初期化を行いたいような場合
(パラメーターがセットされていればその値で初期化し、セットされていなければ
デフォルトの値を使用する)
なのかな?とお題を解釈しました。

sub function{
    my $var = shift;
    $var ||= 'foo';
}

とか。

「不備」というのは、$varに"0"や空文字が入っていた場合でも
論理値が偽になってしまうので「未定義」と区別が出来ない・・・
ということでいいのですよね?
(BBSなどで、名前を「0」にすると「名前を入れてください」というエラーになるものが
結構あったような)

Rでは、missing()を使って引数に値がセットされているかどうかをチェックします。
Perlみたいに1演算子で・・・という便利なものはなさそうです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
f <- function(var){
    if(missing(var)){
        var <- 'foo'
    }
    var
}

> f()
[1] "foo"
> f("")
[1] ""
> f(0)
[1] 0

同じくif文が嫌いです。

1
2
$var = $_REQUEST['var'];
$var = (empty($var))? 'bar' : $var;
1
$var = isset($_REQUEST['var']) ? $_REQUEST['var'] : 'default';
PostScript における「変数」というのが微妙なところですが、
カレント辞書に名前と値のペアを登録することだと解釈しました。

GS>/a 10 def
GS>/a 20 DefaultValue 
GS>/b 30 DefaultValue 
GS>a =
10
GS>b =
30
1
2
3
4
5
6
7
8
%!PS
/DefaultValue { % Symbol val DefaultValue -
  currentdict 2 index known {
    pop pop
  } {
    def
  } ifelse
} bind def

Perlが無かったので投稿します。 ……って、あれ?

1
2
my $var1 = (!defined $var1) ?  'foo' : $var1;
my $var2 = 'foo' unless defined $var2;
use strict;
がない場合
1
2
#my $var1 = 'foo' ;
my $var2 = $var1 || 'bar';
シェルスクリプトの場合、環境変数を unsetしてしまったり、値に空文字列をセットして
いたとしても、変数展開演算子を使用することで想定外の挙動を回避することができます。

# なお、 Solarisの /bin/shは ~を実装していないので、以下の例については注意が必要
# です。
1
rm -rf ${HOME:=~}/tmp/*
バッチでは、if文に defined条件を指定することで変数が定義されているかどうか調べる
ことができます。

使いどころは様々ですが、一例を挙げれば、Windows NTとWindows 2000以降の cmd.exeの
実装の差異を吸収することが可能です。

  e.g.
    Windows NTの場合
      C:\>echo %DATE%
      %DATE%           <- 環境変数DATEは実装されていないため、変数展開されない。

      C:\>date.bat
      2008/03/09

    Windows 2000の場合
      C:\>echo %DATE%
      2008/03/09

      C:\>date.bat
      2008/03/09
1
2
3
4
5
6
7
:: date.bat
@echo off
  setlocal
    if not defined DATE for /f %%d in ('date /t') do set DATE=%%d
    echo %DATE%
  endlocal
goto :EOF
1
var = var if 'var' in locals() else 'foo'

苦肉の策。String test; という変数宣言がある前提です。

1
test = (test == null) ? "abc" : test;
coalesce はSQL標準の関数で、リスト中の NULL じゃない初めの値を返します。
読み方は コゥァレス みたいな感じで、後ろのほうにアクセントを。
お題の意味からはちょっと外れてるかも。

SQLでは初期値は NULL です。
三値論理とかいう独特のルールを持つため、初心者が苦しみます。
1
SELECT id, COALESCE(name, '名無しさん') name FROM namelist ;

#5963 #5973 を、見た感じでは、初回起動時に初期化されて、2度と初期化しない。と解釈しました。

1
2
static char *Var = "Foo";/*書き換え不可能な文字列"Foo"がセットされる。はず。
ポインタの値は変更できます。さらに保持されます。*/
refとoptionでシミュレート。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
type 'a t = 'a option ref

let mkvar () = (ref None:'a t)
let get (x:'a t) = !x
let set_normal (x:'a t) y = (x := Some y)
let set_defor (x:'a t) y =
  match !x with
  | None -> x := Some y
  | Some _ -> ()

let (//=) = set_defor;;

(*
  let x = mkvar ();;
  x //= 1;  get x;; (* Some 1*)
  x //= 2;  get x;; (* Some 1*)
*)
XSLTでは変数は variable 要素で宣言します。
内容を指定しない場合は、指定した型によって
空の~~(空リスト、空文字列など)になります。

というわけで、変数は宣言と同時に必ず初期化されています。
というか、変数の内容を変更できないので、
宣言と同時に初期値を放り込まざるをえません。

一方、template 呼び出しなどで param 要素によるパラメタが利用できます。
パラメタは呼び出し側で指定されなかった場合のデフォルト値を指定できます。
(デフォルト値の指定がない場合は変数と同様、空の~~になります)

パラメタも変数と同様、内容の変更はできません。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  >

  <xsl:output method="text" />

  <!-- global variable -->
  <xsl:variable name="hoge" as="xs:integer" select="5" />

  <xsl:template match="/">
    <!-- local variable -->
    <xsl:variable name="hige" as="element()">
      <parent><child>baby</child></parent>
    </xsl:variable>
    
    <!-- call template without parameters -->
    <xsl:call-template name="func" />

    <!-- call template with parameters -->
    <xsl:call-template name="func">
      <xsl:with-param name="param1">6</xsl:with-param>
      <xsl:with-param name="param2" as="xs:string*">
        <xsl:sequence select="('foo','bar','baz')" />
      </xsl:with-param>
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="func">
    <!-- parameters -->
    <xsl:param name="param1" as="xs:integer">7</xsl:param>
    <xsl:param name="param2" as="xs:string*" />
  </xsl:template>

</xsl:stylesheet>

ああ、そうか

そもそもコンパイル系言語では変数が宣言されてないとコンパイル時エラーになるので「未定義の変数を操作」なんて状況自体が有り得ないわけですが、 必ず実行できるスクリプト系の言語では有り得るんですね。 それがどんな問題を引き起こすのかまでは分かりませんが、defined-or演算子が必要なのであろうことは分かります。 となるとD言語では「コンパイラが検出してくれるので必要ない」が正解かな。

ちなみにD言語では基本的に宣言した変数は全て初期化されますが、実行速度を重視する場合は初期化しないこともできます。 また、typedefで新しく型を定義するときにデフォルトの初期値を指定できます(変な言い方ですが)。 問題と似たようなケースで使えそうです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import std.stdio;

//デフォルトの初期値
typedef int myint = 100;

void main()
{
  int a;
  myint b;
  //未初期化の変数を宣言
  myint c = void;

  //"a=0 b=100 c=(不定)"
  writefln("a=%d b=%d c=%d",a,b,c);
}
1
def var ='foo'

Perl では 0 や空文字列も偽と判断されるのでこのような演算子が必要なんですね。 Scheme では R6RS で (define var) のような書き方もできるようになりましたが、これも何らかの未規定の値が設定されるだけで、それを利用したプログラムを書くことはできません。

このような場合は仮の値として #f を入れておくのをよく見掛けます。

1
2
3
(define var #f)

(some-function (or var 100))

 scalaの場合変数宣言時に値を設定する必要があるので,未初期化時に値を設定するやり方は用意されていません。Option型は用意されているので,Noneが設定されている場合に限り値を設定する処理を参考までに挙げておきます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class DefinedOr[T] {
    var    _v:Option[T] = None
    def this(n:T) = { this(); this.v = n }
    def v:Option[T] = _v
    def v_=(v:T) = { _v = Some(v) }
    def ||=(n:T) = { v = v match { case None => n; case Some(x) => x } }
}
object Main {
    def main(args:Array[String]) = {
        var    d:DefinedOr[Int] = new DefinedOr
        println(d.v)
        d ||= 0
        println(d.v)
        d ||= 1
        println(d.v)
        d = new DefinedOr(2)
        println(d.v)
        d ||= 0
        println(d.v)
    }
}

Index

Feed

Other

Link

Pathtraq

loading...