単項マイナスと構文解析
単項マイナスとは 単項マイナスと括弧 括弧なし単項マイナスを許容する場合のBNF calcの場合
ブロック付きメソッドの作り方を説明します。
ブロック付きメソッドの代表格であるeachが何をしているかを考えてみます。
[10,20,30].each do |x|
print x, "\n"
end
これを実行すると
10
20
30
と表示されます。 メソッドeachは何をしているのでしょうか
x
に代入してブロックを実行するx
に代入してブロックを実行するx
に代入してブロックを実行するブロックはメソッドのようなものですから、順に10,20,30を引数にブロックを呼び出していることになります。 eachの動作をプログラムにすると、およそ次のようなものになります。
x = 10
while x <= 30
print x, "\n" # iを引数にブロックを実行
x += 10
end
あるいは、whileループを使わなくても
x=10; print x, "\n" # 10を引数にブロックを実行
x=20; print x, "\n" # 20を引数にブロックを実行
x=30; print x, "\n" # 30を引数にブロックを実行
でもeachの動作を表すことができます。
「xを引数にブロックを実行」という命令は、Rubyではyield(x)
と書きます。
つまり、「yieldはブロックを呼び出す命令」です。
yieldにはパラメータをつけることができます。
ここでは、ユーザデータのオブジェクトを考えてみます。 そのオブジェクトには
を記録することにします。 ユーザ番号はオブジェクト生成時に一意になるような番号を自動的に振ることにし、書きかえはできないようにします。 その他のデータは書き換え可能にします。
class User
@@count = -1
attr_reader :id
attr_accessor :name, :email, :birth_date
def initialize
@id = @@count += 1
end
def each
yield("id", @id)
yield("name", @name)
yield("email", @email)
yield("birth_date", @birth_date)
end
end
user = User.new
user.name = "Toshio Sekiya"
user.email = "abcdefg@example.com"
user.birth_date = "YYYY/MM/DD"
user.each{|k,v| print "#{k}: #{v}\n"}
attr_reader :id
は読み出しのみ可能なインスタンス変数@idを定義する(前回の記事で説明済み)attr_accessor :name, :email, :birth_date
は読み書き可能な変数@name、@email、@birth_dateを定義する(後述)。attr_accessor
は次のプログラムと同等の働きをします。
def name
@name
end
def name=(s)
@name = s
end
... ... ...
以下emailとbirth_dateも同様
最後の行でeachを呼び出し、呼ばれたeachの中でyieldがブロックが呼ぶ、という複雑さは慣れないとわかりにくいと思います。 繰り返し流れを追って、理解してください。
なお、例からわかるように、yieldのパラメータの数とブロックのパラメータの数は一致していなければなりません。
eachメソッドから様々なメソッドを作り出すことができます。 例えば、Userクラスにmapメソッドを定義するには次のようにします。
# eachからmapを作る例
class User
def map
a = []
each do |k, v|
a << yield(k, v)
end
a
end
end
p user.map{|k,v| [k,v]}.to_h
class User〜endでUserクラスの定義を追加しています。 このように、クラス定義は何度でもできます(このことから既存のクラスにもメソッド追加が可能です)。
mapの定義ではeachメソッドだけを使っていることがわかります。 最後の1行では、mapを使って「項目名とその値の配列」の配列を作り、更にハッシュに変換しています。 実行すると次のようにハッシュの中身が表示されます。
{"id"=>0, "name"=>"Toshio Sekiya", "email"=>"abcdefg@example.com", "birth_date"=>"YYYY/MM/DD"}
map以外にも
<=>
が定義されていることが必要など様々なメソッドがeachだけから作成可能です。 このようなメソッドを集めたモジュールがEnumerableです。
UserクラスがEnumerableをインクルードすれば、mapなどを定義しなくても使えるようになります。
UserクラスがEnumerableをインクルードしていなくても、mapなどを使えるようにする別の方法があります。 それはEnumeratorというラッパークラスを使う方法です。
user.to_enum
によって、userオブジェクトを元にしたEnumeratorオブジェクトを作ることができます。
なお、to_enum
はObjectクラスで定義されたインスタンス・メソッドなので、すべてのクラスはObjectの子孫ですから、to_enum
を持っています。
中身はuserなのですが、EnumeratorオブジェクトはEnumerableモジュールをインクルードしているので、mapなどのメソッドを使うことができます。
p user.to_enum.map{|k,v| [k,v]}.to_h
実行すると
{"id"=>0, "name"=>"Toshio Sekiya", "email"=>"abcdefg@example.com", "birth_date"=>"YYYY/MM/DD"}
さきほどと同じハッシュが表示されます。 まとめると、eachを定義してあるクラスには
to_enum
でEnumeratorオブジェクトにしてもmapなどの様々なメソッドが使えるようになるということです。
Userクラスをリファクターしましょう。 最も問題なのはyieldを@idから@birth_dateまで個別に行っていることです。 もしUserの項目を追加したり削除したりすると、この部分も変更しなければなりません。 そこで、attr_accessorの引数にする項目名(変数名)を配列で保持し、その配列を使ってyieldするようします。 これにより、項目名の変更は配列の変更だけで済みます。
もうひとつは、eachメソッドが引数なしで呼ばれた時にEnumeratorオブジェクトを返すオプションをつけます。
class User
include Enumerable
@@count = -1
@@accessors = ["name", "email", "birth_date"]
attr_reader :id
attr_accessor *@@accessors
def initialize
@id = @@count += 1
end
def each
if block_given?
yield("id", @id)
@@accessors.each do |a|
yield(a, eval("@#{a}"))
end
else
self.to_enum
end
end
def to_h
map {|k, v| [k.to_sym, v]}.to_h
end
end
user = User.new
user.name = "Toshio Sekiya"
user.email = "abcdefg@example.com"
user.birth_date = "YYYY/MM/DD"
user.each{|k,v| print "#{k}: #{v}\n"}
user1 = User.new
user1.name = "Jeaou Robinson"
user1.email = "xyz@example.co.uk"
user1.birth_date = "yyyy/mm/dd"
user1.each{|k,v| print "#{k}: #{v}\n"}
p user.to_h
p user1.each
*
)をつける。
一般にメソッド呼び出しm(a,b,c)
とm(*[a,b,c])
は同じになるこのプログラムを実行すると
id: 0
name: Toshio Sekiya
email: abcdefg@example.com
birth_date: YYYY/MM/DD
id: 1
name: Jeaou Robinson
email: xyz@example.co.uk
birth_date: yyyy/mm/dd
{:id=>0, :name=>"Toshio Sekiya", :email=>"abcdefg@example.com", :birth_date=>"YYYY/MM/DD"}
#<Enumerator: #<User:0x00007f1f7a737be8 @id=1, @name="Jeaou Robinson", @email="xyz@example.co.uk", @birth_date="yyyy/mm/dd">:each>
と表示されます。 最後の2行でto_hメソッドと引数なしeachメソッドが期待通りに動いていることが確認できます。
ユーザを表現するオブジェクトはインスタンス変数で各項目を表す方法(この記事のUserクラスのように)とハッシュを使う方法が考えられます。 ハッシュを使う場合は
toshio = {}
toshio[:id] = 0
toshio[:name] = "Toshio Sekiya"
toshio[:email] = "abcdefg@example.com"
toshio[:birth_date] = "YYYY/MM/DD"
p toshio #=> {:id=>0, :name=>"Toshio Sekiya", :email=>"abcdefg@example.com", :birth_date=>"YYYY/MM/DD"}
となります。
これらの違いは何でしょうか?
ハッシュは実行しているプログラムの扱う対象です
これらと同等のことはC言語でも行うことができます。
struct hash { char *key; char *value; };
この構造体をリストでつなげてRubyのハッシュと同等のデータ構造を実現し、それをコントロールする関数を定義すれば良いのです。 実装が面倒ですが、可能だということです。
これに対して変数名は基本的に実行しているプログラムの扱う対象ではありません。
C言語を例に取ると、変数の管理はコンパイラがシンボルテーブルで行うのであって、実行プログラムは管理しません。 したがって、上記のような操作、特に変数の追加と削除(これはシンボルテーブルへの追加と削除を意味する)は実行プログラムでは不可能です。
Rubyではどうでしょうか? Rubyにはevalなどがあるので実行プログラムからこれらを行うことができます。
このように、Rubyでは実行プログラムがRubyの状態(変数のシンボルテーブルなど)を知ることができ、アクセスもできます。 これを「リフレクション」といいます。 レフレクションを使って更にUserクラスのプログラムを書き直してみましょう。
class User
include Enumerable
@@count = -1
attr_reader :id
attr_accessor :name, :email, :birth_date
def initialize
@id = @@count += 1
end
def each
if block_given?
instance_variables.each do |iv|
yield(iv.to_s.slice(1..-1), eval(iv.to_s))
end
else
self.to_enum
end
end
def to_h
map {|k, v| [k.to_sym, v]}.to_h
end
def show
each do |k, v|
print "#{k}: #{v}\n"
end
end
end
user = User.new
user.name = "Toshio Sekiya"
user.email = "abcdefg@example.com"
user.birth_date = "YYYY/MM/DD"
user1 = User.new
user1.name = "Jeaou Robinson"
user1.email = "xyz@example.co.uk"
user1.birth_date = "yyyy/mm/dd"
User.attr_accessor(:location)
user.location = "Japan"
user.show
user1.show
User.attr_accessor(:location)
は@locationの参照と代入のメソッドを定義しているだけで、@location自身を定義しているのではない。
@locationは初めて代入されるときに同時に定義される。
例えば、userオブジェクトで@locationが定義されたのは、user.location = "Japan"
が実行されたときである。
user1では@locationの代入は行われていないので未定義である実行すると次のようになります。
id: 0
name: Toshio Sekiya
email: abcdefg@example.com
birth_date: YYYY/MM/DD
location: Japan
id: 1
name: Jeaou Robinson
email: xyz@example.co.uk
birth_date: yyyy/mm/dd
Userクラスはいじると面白いのですが、実用上はどうなのでしょうか? ハッシュを使うほうがプログラマーにとって易しいので、保守性も高いような気がします。 わざわざ難しくするのもどうなのか? ただ、アクセサーの構文(user.location = “Japan”など)は読みやすく分かりやすいですね。 一長一短かもしれません。
リフレクションについて書いておいてこういうのも何ですが、
リフレクションを使い過ぎて難しくしてはいけません
プログラムはそもそもやっかいで面倒なもの。 余計な難しさは余計な時間を費やすことになります。
単項マイナスとは 単項マイナスと括弧 括弧なし単項マイナスを許容する場合のBNF calcの場合
パーサ・ジェネレータとは 少し複雑な文法 四則(加減乗除)計算のBNF Racc で実装 クラス定義、BNFの記述部分 ヘッダー、インナー、フッター コンパイルと実行 演算子の優先順位と結合における左右の優先順位 まとめ
StrScanライブラリのドキュメント 字句解析とは StrScanライブラリ StrScanライブラリを使った字句解析 実例
lbtというgemを作って公開してみた lbtはどんなgemか ファイルの配置 lbt.gemspec Rakefile gemのビルド RubyGems.orgへのアップロード 補足・・rake/gempackagetaskサブライブラリについて
文字列のエンコーディングに頭を悩ませることはほとんどなくなりました。 なぜなら、どのアプリ、システムもUTF-8を使うようになったからです。 Rubyでもエンコーディングの問題が起こることはまず無いでしょう。 ですが、今回はエンコーディングの考え方を整理してみたいと思います。
Fiberを書いたときから、次はスレッドを書こうと思っていましたが、時間がかかってしまいました。 その理由は、期待したとおりのスレッドの効果がなかったためです。 今回はそのことを書きますが、これはRubyのスレッドの抱えている問題なのか、自分のやり方が悪いのかははっきりしていません。
Fiberは「ノンプリエンプティブな軽量スレッド」とRubyのマニュアルに記載されています。
今回はRubyプログラムから自動的にドキュメントを作成するRDocについて書きたいと思います。 私はこのことについて、エキスパートではありません。 この記事も、初心者の体験談だと考えてください。
Ruby/Gtkの記事を先日書いたときに、「これはかなり使える」という手応えを感じたので、WordBook(Railsで作った単語帳プログラム)のGTK 4版を作りました。 プログラムは「徒然なるままにRuby」のGitHubレポジトリに置いてあります。 レポジトリをダウンロードし、ディレクトリ_example/...
今回はGTK 3とGTK 4をRubyで使うライブラリについて書きたいと思います。
今回もRubyとGUIのトピックです。 Glimmerを取り上げます。
Rubyはグラフィックについて弱い印象があります。 しかし、グラフィックはデバイスに関することなので、言語そのものには直接の関係はないはずで、あるとすればライブラリです。 今後グラフィック関係のgemが開発されることに期待しましょう。
Rails7におけるシステムテストについて書きます。
前回作ったWordbook(リソースフル)のテストを書いてみます。 RailsのテストはminitestをRails用に拡張したものです。
今回はRailsの慣例に沿った形でWordbookを作り直します。
今回はWordBookの検索と削除についてです。
今回はRailsにおけるデータの作成と保存、そして変更について説明します。 そのベースになるモデルとデータベースの話から始め、appendとchangeの動作について詳しく説明します。
一般に、HTMLは文書の構造を表し、CSSはその体裁(見栄え)を表します。 Railsは最終的にCSSを含むHTML文書を出力するので、この2つについての理解が必須です。 この記事ではとくにCSSの人気ライブラリであるBootstrapを紹介します。 BootstrapはJavascriptも含んでいます。
Rubyの最も人気のあるアプリケーションであるRuby on Railsを取り上げようと思い、書き始めました。 予想してはいましたが、相当な分量になってしまいました。 そのため、何回かに分けて記事にすることにします。 また、対象となる読者のレベルをどうしようかと考えましたが、「徒然Ruby」が基礎的な内容から始ま...
Rubyのライブラリ管理システムのRubygemsとコマンドgemおよびbundlerについて説明します。
minitestについて連続して2回書いてきました。 「minitestはドキュメントが少ない」という人がいますが、私も同感です。 例えば、モックとスタブの説明も少ないです。 そこで、今回はmock.rbのソースコードを参考に、モックの私的ドキュメントを書いてみました。 あくまで私個人の考えであり、minites...
今回もminitestの話です。 mockとstubに焦点をあて説明します。
アプリ作成の記事でminitestを使いました。 今回はminitestについて、また一般にテストについて、私の考えを書こうと思います。
今回はメソッドの呼び出し制限ついて説明します。 呼び出し制限にはpublic、private、protectedの3つがあります。
今回は特異メソッド、特異クラス定義、名前空間、モジュール関数について説明します。
2023/10/29 追記:この記事は新しく書き直しました。 古い記事で使っていたGitHubのCalcが大幅にアップデートされたためです。 そこで、この記事に合うようなプログラムsimple_calcを新たに作りました。 このプログラムは本レポジトリの_example/simple_calcにあります。
if〜elsif〜・・・〜else〜endは皆さん良く使うでしょうか? これは場合分けで良く使われる方法です。 これと同様の制御構造にcase文があります。 Cのswitch文に似ていますが、より強力な機能を持っています。 if-else-endよりも高い能力があるといえます。
Procオブジェクトを生成するメソッドlambdaについて説明します。
今回はブロックを一般化したオブジェクトProcを説明します。
ブロック付きメソッドの作り方を説明します。
モジュールには名前空間とミックスイン(Mix-in)の2つの機能があります。 ここではミックスインについて説明します。
クラスの親子関係
Rubyの演算子とその再定義について書きます。
今回からクラスとインスタンスを定義、生成する方法を説明します
Kernelモジュールのメソッドはどこでも使うことができます。 そのメソッドの中には便利で有用なものが多いです。
ここでは私が便利だと思ったメソッドを紹介します。
実数
今回はシンボルとハッシュについて説明します。
文字列は最も使うオブジェクトのひとつです。 特にウェブ・アプリケーションでは、コンテンツだけでなくHTMLのタグやCSSを含めすべてが文字列です。 Rubyは文字列オブジェクトのメソッドが充実しており、またパターンマッチのための正規表現も充実しています。
配列は、どのプログラミング言語にもあると思います。 複数の要素を一括して扱うことができるのが配列です。 Rubyの配列はメソッドが充実しているので、プログラムを効率的、機能的に書くのに役立ちます。
今回の目標はインスタンスです。 インスタンスを説明するために、ローカル変数と文字列オブジェクトを事前に扱います。
今回はメソッド定義です。 メソッド定義はRubyの核心ですが、今回はトップレベルに限って説明します。 この限定によって、内容はかなり易しくなっています。
ブロックはRubyの特長です。 ブロックのおかげで記述が非常にすっきりと分かりやすくなります。 今回はブロックをイテレータの本体として使う方法を説明します。
ここではRubyの最も基本的なオブジェクトである整数について説明します。
「徒然なるままに」をネットで調べてみると、「することもなく、手持無沙汰なのにまかせてという意味」とありました。 まさに、自分の現状を言い当てた言葉。 しかも、ブログに書くネタもなかなか思いつかない日々。