2014-04-24

Pythonメモ その4

引き続いて Swaroop "A Byte of Python" 読んでいます

・引数デフォルト値。まあ今どきFortranでも出来る。
・キーワード引数。まあ、やっぱり :key=>val より key=val のほうがいい。
・可変長引数。複数の引数をタプル(イミュータブルなリスト)またはディクショナリとして受ける機能。Rubyで言うと配列とハッシュだわね。
・関数が値を返すにはreturnが必要。Rubyのメソッドでネストしたcaseが最終文の時、一体どこの行がreturn相当か考えるのもしんどいので、まあ悪いことで無いと思う。
・returnのない関数は None という値を返す。nil相当だろうね。

2014-04-22

pythonメモ その3

引き続いて Swaroop "A Byte of Python" 読んでいます

・関数を def で始めることは Ruby と同じ
・関数にかっこが必須なこと(定義、引用とも)
→関数名そのものは関数オブジェクトを指す。一見さん向きではないが綺麗な文法だろう
・何もしないpass文(制御構造をインデントするために必要)… FortranのCONTINUE文ですね。素敵な変態加減。
・docStrings: 関数冒頭に文字列だけを一文として置くと、関数オブジェクトの __doc__ メソッドとなり、help 文はそれを問うている
・関数内で使い始めた変数はローカル。明示的にglobal宣言をすると変えられる

2014-04-20

システム間で流通するデータ形式の標準にプログラム言語のバインディングを与えること

先日ね、上司が「yamlのいいところは、一発で読めてパーザがいらないところだ」っていうわけです。

もちろんそうなんだろうけど、あれっとも思ったんです。UMLモデラーが自動プログラミングを喧伝することに常日頃から私が不快感を示しているわけだけれども、データ形式とプログラム言語のバインディングという意味では同じじゃないのかと。

結論から言うと、配列とハッシュという万能データ型コンビが、カスタムの構造型とくらべて「他人から押し付けられる場合の嫌」が少ないんでないかなと思うわけです。ダックタイピング的には拡張しても別物にならないほうがいいですしね。

2014-04-18

nm(1) の出力の読みかた、あるいはFortranやCの分割コンパイルとリンクとは

なんか最近こういうのを書いておかないといけないと思うんです。どっかに良いテキストがあるんでしょうが、探すより書いちゃったほうが速いかなと。

まずもって実用的なところから。nm コマンドはオブジェクトファイルまたはライブラリを引数にとってオブジェクト内のシンボルを表示するものです。出力の各行はアドレス(あれば)、シンボルの種類、名前です。

まあちょっと例を見せたほうがいいですね:

$ cat a.c
#include <stdio.h>
int x;
int main()
{
  static int s;
  int r;
  r = printf("%d\n", x);
  return r;
}
$ cc -c a.c
$ nm a.o
00000000 T main
         U printf
00000000 b s.1780
00000004 C x

プログラム中の名前がいくつか nm の出力に現れています。種別はこんなところですね:

b: BSS領域(小文字はリンク対象ではないことを示す)
C: コモン領域
D: データ領域(上にはないけどついでに)
T: テキスト領域
U: 未定義シンボル

で、なんたら領域と言われてもわからんという人が多いと思うわけです。

ノイマン型コンピュータではメモリは命令列とデータ(演算対象)のふたとおりに使われると教えると思います。ちょっと古いです。CやFortran(90以降)の実用上は、命令列・データ・スタックの3通りに使うと理解したほうがいいと思います。

(スタックというのは、手続(Cの関数、Fortranのサブルーチンや関数)に入る時に戻り先アドレスを保存したり、手続の中だけで使う変数を確保しておく場所で、手続の呼び出し階層が深くなると一方向に伸びていくものです。これによって、手続が同じ手続を呼び出すこと(再帰呼び出し)が可能になります。いいかえると、FORTRAN77が再帰呼び出しを禁止していたのは、かつてメインフレームにスタックがなかったからです。)

データのメモリを動的に確保して開放のタイミングを自分で制御(手続が終わった後も残したり、手続の途中で開放したり)したいことがあります(Cのmalloc(3)やFortranのポインタに対するALLOCATE)が、これはヒープと呼ばれます。都合4種類になりますね:

メモリの用途
└┬命令列(静的=位置固定)
 ├スタック(動的、手続終了時に開放)
 └データ
     ├ 固定アドレスのデータ(静的)
     └ヒープ(動的、明示的に開放)

さて、大抵のOSではプログラムはファイル(executable file = 実行ファイル。メインフレーム方言ではロードモジュールという)に格納されます。そのファイルには何が書かれるかというと、だいたいは静的な(プログラム開始時にアドレスが確定する)ものです。どこに何という名前で何バイトとっておいて何を書いておけ、というようなことが並んでいるわけです。動的なものは大きさくらいしか書いてないです。

CやFortranでは分割コンパイルができます。ソースコードひとつひとつをコンパイルしたもの(オブジェクトファイル)をリンクして実行ファイルができるわけです。実のところ実行ファイルとオブジェクトファイルは共通のフォーマットになっています。リンクという操作は、オブジェクトファイル内の名前を全部調べて、仮のアドレスをつけかえてアドレスを種類別に連続して並ぶようにしたり、未定義シンボルとなっていたところを他のオブジェクトファイルの同名のシンボルのアドレスで埋めてやるというような操作です。

で、ようやく最初の nm に戻るわけですが、命令列をテキストというのはいいとして(何故ですかね。私も知りません)、固定アドレスのデータにはいくつか種類があります。おもに初期値の与え方によって分かれるわけです:

シンボルの主な種類
└┬ U 未定義シンボル
 ├ T テキスト(命令列)
 └固定アドレスのデータ
     ├ D 非零の初期値があるもの(データ領域)
     ├ B 初期値がゼロなもの(BSS領域)
     └ C 初期値が他のファイルにあるかもしれないもの(コモン領域)

まずいちばん基礎的なのがデータ領域ですわね。初期値があってゼロとか決め打ちできないわけですから、それをファイルに書いておかねばなりません。同じ名前が複数あったらリンカが duplicated symbol というエラーを吐いて教えてくれます。

次はBSS領域。これは初期値がゼロなものです。初期値がゼロな変数はしばしばあるものだし、初期値をファイルに書いておかなくてもいいですわね(あれ、昔はBSS領域を使いすぎるとファイルがでかくなるからだめだと言われたような気がするんだけどなあ…今実験してみてもそんなことはありません)。こいつも外部リンケージを持つなら重複してはいけません。

さっきの関数外 static 変数の例では nm が小文字 "b" を表示しているので、別のソースコードに同じ変数 s があったとしてもかまいません(別物としてアドレスが確保されます)。なんか gcc の場合は単なる s じゃなくて s.1780 とかいう名前になっていますが、デバッガの都合かなんかでしょうかね。よくわかりません。

最後のコモン領域というのは、特色として複数のオブジェクトファイルで同名のシンボルがあってもエラーにならず、一本化されます。初期値は指定できません(どれを選んだらいいかわかりませんね)が、同名のデータ領域のシンボルが1つだけならまとめて一本化されます。

これはC言語としては初期値のない関数外の「int x;」みたいなのでできるものですね。宣言(名前に型を指定するだけでメモリは割り付けない)なのか定義(メモリを割り付ける=大抵は同名が重複してはいけない)なのかよくわからない文で、複数のソースコードで同じ 「int x;」とやってもエラーにならない(宣言みたいだ)けれど、最終的にメモリなしになってしまうのでなく1箇所のメモリが割り当てられるわけですが、そういう性質はコモン領域のものです。

関数内の static じゃない変数 int r; は nm の出力にあらわれていないことにも注意しておきましょう。こういった変数はスタック上にとられます。関数冒頭にスタックを伸ばして r のぶんのメモリを書く命令と、関数の脱出時にスタックを同量縮める命令が入るわけですが、r そのものは固定したアドレスをもたず、関数外から参照することもできない=リンカがリンクすることもないわけです。

ところで、コモンというと Fortran の悪名高きコモンブロックが思い出されますが、実際そうです。モジュールなんかを使わない古典的な(77的な)例を下に示しますが、コモンブロックがコモン領域に、サブルーチン内のSAVEがついた変数がBSS領域に割り当てられます。

$ cat b.f
      SUBROUTINE MAIN(L)
      COMMON /BLK/ I
      INTEGER J, K
      SAVE K
      J = K
      K = L
      L = J
      PRINT *, I
      END SUBROUTINE
$ gfortran -c b.f
$ nm b.o
         U _gfortran_st_write
         U _gfortran_st_write_done
         U _gfortran_transfer_integer
00000004 C blk_
00000000 b k.695
00000000 T main_

【補遺2014-04-20】
フォロワーさんから次の本を勧められました。未見ですが挙げておきます。
CQ出版 リンカ・ローダ実践開発テクニック
    JANコード:JAN9784789838078
2010年9月1日発行
    坂井 弘亮 / 著
http://shop.cqpub.co.jp/hanbai/books/38/38071.html

2014-04-15

[Fortran] 書式なし順番探査ファイルの形式(2GB超では互換性を欠くみたい)

Fortran では書式なし順番探査ファイルはレコード内容の前後にバイナリのレ
コード長がつきます。

最近の gfortran ではレコード長が 4 オクテット(デフォルト)または8オク
テットで選択できて、 -frecord-marker=8 オプションで変更できます。
http://gcc.gnu.org/onlinedocs/gfortran/Runtime-Options.html
インストール時のデフォルトで8オクテットに設定する事もできるようですが、
どうやるのか知りません。

対して Intel Fortran ではレコード長は常に4オクテットで、2ギガバイトを越
えると分割出力されます。レコード長が負の場合は分割を意味するようです。
https://software.intel.com/sites/products/documentation/doclib/stdxe/2013/composerxe/compiler/fortran-mac/GUID-E36C2463-1514-4E4E-B88A-769AB0326C57.htm

2014-04-07

Pythonメモ その2

引続き A Byte of Python を読みながら(前回文献書いてなかったごめん)。

●シングルクォートとダブルクォートの中では等しくバックスラッシュエスケープが処理される。文字列に r を前置するとバックスラッシュエスケープが抑止される。正規表現に勧められるという。つまり正規表現リテラルは無いのかな。まあ、思い切りが良いとは言えるかな。
●真偽値定数は True and False
●while/for ループの else: ブロックはループ脱出時に呼ばれる。確かに脱出時処理を書きたいことはあるね。
●range() の結果は配列ではない。要すれば list() に食わせてリストにする。
●range(0, 10)は10を含まない。Rubyの .. と ... えーと、どっちだっけ。忘れるくらいだから別の文法でもいい。

2014-04-02

ruby でトップレベルにコードを書かないスタイルを「インスタンスって何」と思ってしまう Fortran 使いに説明するの巻

毎年4月になるとなんかこういうことをしているので、なんとか経験を洗練したいものです。

要はこう言うと楽なのではと思いましたよ。

  • クラスというのはFortranのモジュールみたいなものです。使い始めに任意のサブルーチンじゃなくてnewを呼ばなければいけな いので、変数の初期化を普通はinitialize内でやります。
  • インスタンス変数(@name)というのはFortranのモジュール変数です。
  • インスタンスとは何かというのは、今はわからなくてもいいです。1クラスを同時に複数個newできて、その時インスタンスが複数ある という言い方をします。クラスにはコードが所属しますが、インスタンスにはデータが所属します。
  • クラス変数(@@name)というのは複数インスタンスがあるときに共有される変数です。私はほとんど使いません。
  • グローバル変数($name)というのはFortranのコモンブロックです。覚悟をもって使用してください。
ぼくは数行を超えるプログラムはほとんどこういう感じのテンプレートで書きはじめるようにしているんです。

  class App
    def initialize argv
      # 初期値設定
        @files = []
        for arg in argv   
          case arg
          when /\A-d\z/ then $DEBUG = true  
          # その他オプション処理  
          else @files.push arg 
          end 
        end
      end
      def run
        for file in @files
          File.open(file, 'r') {|fp|  fp.each{|line|  ... }}  
        end 
      end
    end
    App.new(ARGV).run if $0 == __FILE__
  
で、Ruby初めてでトップレベルで Hello World から入った人がこれをいきなり見ると「うっ」となるわけですね。悩んだ挙句「インスタンス(変数)ってなんですか」みたいに聞かれて、素直にインスタンスから説明すると時間を無駄にするんですわ。この2014年にもなってOOPを全く知らないなんて、と読者諸兄は思われるかもしれないけど、気象界は世間と隔絶したところなんで、プログラミングセンスがあってもOOPを全く知らないなんてことが平気で起こるのです。何か方便が必要で しょうと。

なんでトップレベルで書かないかというとその心は、複数メソッドが現れるならば、最初からその間で共有されるデータのスコープ管理をしておかない といけないということであって、それってFortranのモジュールでできたことと同じなんですよね。だからモジュールでたとえてしまえばいいの ではないかと。

rubyist からみた python メモ

Swaroop "A Byte of Python" 読んでいる。

まあ順不同である。

  • レキシカルな部分はあまり驚くようなことはない。
  • しいていえばヒアドキュメントの代わりに三連引用符が用いられることくらいか。これ、どっかでみたな。RELAX NGだっけ。
  • 前にも書いたが文字列が変更不可である。これはけっこうすごいことだ。
  • 文字列リテラル内の式展開(Rubyの "#{expr}")はないみたいだ。かわりに "{0}".format(expr) あるいは "{key}".format(key=...) のようにする。これはやや冗長ではあるが、評価タイミングが明確になって好ましいのではないか。
  • 演算子もむちゃくちゃ驚くものではない。整除(floor)が // で実数商が / のようだ。
  • 代入は文であって式ではないらしい。これは if a = b のような誤りを防げるからわるい話ではない。ちょっとFortranを思い出す。
  • if のあとは elif である。elsif ではない。あと、case 構文はないようだ