Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
if式 / if文 の条件節で、左辺に定数を書くべき言語はあるか? @ajiyoshi.gist

if式 / if文 の条件節で、左辺に定数を書くべき言語はあるか? @ajiyoshi.gist

twitterからながれてきたこの話題。昔のCコンパイラは、if文の条件節で代入を書いても文句を言わなかったので、このようなコードに何の警告も出なかった。

#include<stdio.h>

int main() {
  int x = 0;
  /* おそらく意図と違う。 x == 1 と書くべきであった
   これでは常に実行されてしまう */
  if ( x = 1 ) {
    puts("残念");
  }
}

「これをこのように書けば、コンパイルエラーになり、ある種の誤りをコンパイラに見つけさせることができる」というのが、「老害」とされる人の主張である。

  /* これはコンパイルエラーになる */
  if ( 1 = x ) {
    puts("残念");
  }

もし使っている環境が「コンパイラや処理系が前者のコードに文句を付けない」のであればこの意見も一理ある。しかし、本来この問題は機械が解決すべきである(コンパイラの警告やlintなど)。今現在、そのような環境はどのくらいあるのだろうか?

以下の言語について調べてみた。そんなに真剣に使ってない言語については、macに最初から入っていたバージョンだったりするので「他のバージョンでは違うよ」といった指摘を歓迎する。また、オプションの類いは基本的にデフォルト(何も指定しない)である。

  • Python (2.7.5)
  • Perl (5.16.2)
  • Ruby (2.0.0p481)
  • C (clang Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn)
  • PHP (5.4.30)
  • Java (1.6.0_65)
  • Scheme (gauche 0.9.3.3)
  • Haskell (ghc 7.6.3)
  • Erlang (17)

python

構文エラーになって実行できない。

i = 0

#syntax error
if i = 1 :
    print "hoge"

perl

構文エラーになって実行できない。

use strict;
use warnings;

my $i = 0;

#Found = in conditional, should be == at hoge.pl line 7.
if ( $i = 1 ) {
    print "hoge\n";
}

ruby

実行時警告になる。

i = 0

#hoge.rb:4: warning: found = in conditional, should be ==
if i = 1
    puts "hoge"
end

C

(clangでは)コンパイル時警告になる(警告があったらコンパイル失敗する設定になっていればコンパイル失敗であるが、デフォルトはそうではない)

#include<stdio.h>

int main(){
    int i = 0;
    /* warning: using the result of an assignment as a condition without parentheses [-Wparentheses] */
    if ( i = 1 ){
        puts("hoge");
    }
}

休憩

さて、ここまで見てきた言語であれば、コンパイラや処理系が誤りを見つけてくれる。警告を無視したりしているのでなければ我々はこの種の誤りを見つけることができる。 もし「老害」に先のようなイディオムを強制されたとしても、「もし間違っても警告が出ます。警告を無視するのやめましょう。警告潰ししていきましょう」と前向きな提案に変えることができる。

PHP

貫禄のPHPは、なんの警告もつけないし、黙って実行する。いいね?

そもそも君はテンプレートエンジンを宝島か何かと思っているのかね?PHPはかつてクラウドにあり全webを支配した恐怖の帝国だったのだ。

<?php

$i = 0;
if ( $i = 1 ) {
    echo "abort";
}

java

javaにはこの問題はないと思った?残念。javaは条件節に真理値がないと怒るが、真理値でありさえすれば代入が書いてあっても文句を付けない。

public class hoge {
    public static void main(String[] args){
        boolean b = false;
        if ( b = true ){
            System.out.println("abort");
        }
    }
}

休憩2

ここから先の言語達は、そもそも副作用がないとか、変数を再束縛できないとか、代入と比較が間違えるような構文ではないという理由でこの種の誤りは起きない

scheme

再代入と比較を間違うような構文ではない。

(define x 0)
(print (if (eq? x 1)
           "hoge"
           "fuga"))

haskell

副作用がないし、パターンマッチがあるのでこんな用途で if など書かない

hoge :: Integer -> String
hoge 1 = "hoge"
hoge _ = "fuga"

main :: IO ()
main = print $ hoge 0

erlang

再束縛がないし、パターンマッチがあるのでこんな用途で if など書かない。

#!/usr/bin/env escript

main(_) ->
    io:fwrite(hoge(0)).

hoge(1) -> hoge;
hoge(_) -> fuga.

総論

  • Python、Perl、Ruby、C(clang)では、「老害」の意見には反発しよう
    • 機械が見つけてくれるか、そもそも書けない。
  • Java、PHP、および昔のCなどの環境では、残念なことに「老害」の意見には一理ある。
  • 世代間闘争に帰着するよりも、lintなどの静的解析を提案しよう。
    • このようなコーディングスタイルを勧めてくるくせに静的解析を拒否する人は本当に老害である。退職の準備をしよう。

monmon commented Dec 6, 2014

JavaScript

"use strict";

var i = 0;

if ( i = 1 ) {
    console.log("hoge\n"); //=> hoge
}

楽しかったです。
定数を左辺に書く慣習を Yoda 記法というそうです。( Powerful, you are ...というYodaの語法に由来 )

C#

class Program
{
    static void Main(string[] args)
    {
        var b = false;
        if (b = true) // 警告 | 条件式の割り当ては常に定数です。== を使用するつもりで = を使用しましたか?
        {
            System.Console.WriteLine("hoge");
        }
    }
}

fasl commented Dec 8, 2014

各言語の実行バージョンも記載しておくと参考となるかと思います。

koher commented Dec 8, 2014

僕は別の理由からよく左右を反転して次のように書きます。

# Ruby
string = ...
if "abc" == string
    ...
end

これは、 string== メソッドをオーバーライドしたインスタンスが代入されて、危険な動作が引き起こされる可能性がないようにです。

例えば、次の String2 のインスタンスを代入すれば if を必ず突破できます。

class String2 < String
    def initialize(string)
        super(string)
    end

    def ==(rhs)
        true
    end
end

string = String2.new("xyz")

if string == "abc"
    # ここが実行される
end

if "abc" == string
    # ここは実行されない
end

また、 Java のように null に対するメソッドコードが許されない言語では、stringnull チェックが必要ないという理由もあります( Java では String を継承できないからオーバーライドの心配はありません)。

// Java
String string = ...;
if ("abc".equals(string)) { // string が null でも問題ない
    ...
}

Ruby を例に挙げたのは String を継承できて演算子オーバーロードもあるため都合がよかったからで、僕は Ruby をほとんど書いたことがないので上記のように書くのが本当に良いのかはわかりません。しかし、 Ruby に限らない一般論として

  • null チェックが必要ない
  • == がオーバーライドされているおそれがない

という利点はあるのではないかと思います。

msmhrt commented Dec 8, 2014

if 文の話からはちょっと外れますが、Ruby の Test::Unit::Assertionsassert_equal() の引数が expected, actual の順になっているのは、@koher さんが説明してくださったような理由があるからなのかもしれませんね。

kwatch commented Dec 22, 2014

Rubyでは次のコードで警告が出ないけど、それでも老害認定します?警告がでるのはif文だけでいい?

x = 1
puts x = nil ? "Y" : "N"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment