2008年7月30日水曜日

目覚まし時計プログラムをJavaFX SDK対応に書き換える


以前に投稿した、目覚まし時計プログラムをJavaFX SDK対応に書き換えてみました。
JavaFX SDKのAPI文書に、Spinnerクラスが見つからなかったので、Sliderクラスに置き換えてみました。また、パッケージ名を「timer」から「clock」に変更しています。
NetBeansで、JavaFX企画を作成し、目覚まし時計プログラムの本体JavaFXファイルと警報鳴動Javaファイルを作成し、警報音源ファイルを置いて、企画を構築して実行すると、図のような画面が表示されます。
次に、
本体の目覚まし時計JavaFXファイルを示します。

package clock;

import javafx.animation.Timeline;
import javafx.animation.KeyFrame;
import javafx.application.Frame;
import javafx.application.Stage;
import javafx.ext.swing.BorderPanel;
import javafx.ext.swing.Button;
import javafx.ext.swing.Canvas;
import javafx.ext.swing.FlowPanel;
import javafx.ext.swing.GridPanel;
import javafx.ext.swing.Label;
import javafx.ext.swing.Slider;
import javafx.ext.swing.SwingFrame;
import javafx.ext.swing.Slider;
import javafx.ext.swing.TextField;
import javafx.scene.Font;
import javafx.scene.FontStyle;
import javafx.scene.Group;
import javafx.scene.HorizontalAlignment;
import javafx.scene.VerticalAlignment;
import javafx.scene.geometry.Circle;
import javafx.scene.geometry.Line;
import javafx.scene.geometry.Rectangle;
import javafx.scene.layout.VBox;
import javafx.scene.Orientation;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.transform.Translate;
import javafx.scene.transform.Rotate;

import java.lang.Math;
import java.lang.System;
import java.util.Date;

import clock.Sound;

public class Alarm {
private attribute elapsed: Number;
public attribute seconds: Integer = 0;
public attribute minutes: Integer = 0;
public attribute hours: Integer = 0;
public attribute count: Number = 0;
public attribute startTime: Number;
public attribute elapsedTime: Number;
public attribute remainingTime: Number;
public attribute alarmEnabled: Boolean = false;
public attribute running: Boolean = false on replace {
if ((alarmEnabled == true) and (remainingTime <= 0)) {
alarmEnabled = false;
Sound.alarm();
}
};
public attribute timer: Timeline = Timeline {
repeatCount : Timeline.INDEFINITE
keyFrames : KeyFrame {
time: 1s
action: function(): Void {
var now = new Date();
elapsedTime = now.getTime() / 1000 - startTime;
remainingTime = count - elapsedTime;
seconds = remainingTime mod 60 as Integer;
minutes = (remainingTime / 60) mod 60 as Integer;
hours = (remainingTime / 60) / 60 as Integer;
if (elapsedTime >= count) {
timer.stop();
running = false;
hours = 0;
minutes = 0;
seconds = 0;
} else {
running = true;
}
}
}
};
}

SwingFrame {
var alarm = Alarm {}
title: "Alarm Clock"
width: 250
height: 350
visible: true
closeAction: function(): Void {
System.exit(0);
}
content: BorderPanel {
center: Canvas {
content: Group {
var font = Font {
name: "Dialog"
style: FontStyle.PLAIN
size: 15
};
var secs: Number = bind alarm.seconds
var mins: Number = bind alarm.minutes + secs / 60
var hrs: Number = bind alarm.hours + mins / 60
content: [
Rectangle {
width: 250, height: 215
fill: Color.color(0.9, 0.9, 0.9)
},
Circle {
centerX: 120, centerY: 90, radius: 80
fill: Color.WHITE, stroke: Color.BLACK
strokeWidth: 1},
Group {
transform: Translate {x: 120, y: 100}
content: for (i in [1..12]) Text {
var radians = Math.toRadians(30 * i - 90)
transform: Translate {x: 70 * Math.cos(radians),
y: 70 * Math.sin(radians)}
content: "{i}"
horizontalAlignment: HorizontalAlignment.CENTER
verticalAlignment: VerticalAlignment.CENTER
}
},
Group {
transform: [Translate {x: 120, y: 90}]
var hourHand = Line {
startX: 0, startY: 0, endX: 0, endY: -35,
strokeWidth: 4, stroke: Color.BLACK
transform: bind Rotate {angle: hrs * 30, x: 0, y: 0}
}
var minuteHand = Line {
startX: 0, startY: 0, endX: 0, endY: -55,
strokeWidth: 2, stroke: Color.BLUE,
transform: bind Rotate {angle: mins * 6, x: 0, y: 0}
}
var secondHand = Line {
startX: 0, startY: 0, endX: 0, endY: -75,
strokeWidth: 1, stroke: Color.RED,
transform: bind Rotate {angle: alarm.seconds * 6, x: 0, y: 0}
}
content: [hourHand, minuteHand, secondHand]
},
Circle {
centerX: 120, centerY: 90, radius: 3
fill: Color.BLACK, stroke: Color.BLACK
}
]
},
}
bottom: GridPanel {
rows: 4
columns: 1
content: [
FlowPanel {
background: Color.WHITE
content: [
Slider {
minimum: 0, maximum: 11
value: bind alarm.hours with inverse
enabled: bind not(alarm.running)
},
Label {
enabled: bind not(alarm.running)
text: "hour"
}
]
},
FlowPanel {
background: Color.WHITE
content: [
Slider {
minimum: 0, maximum: 59
value: bind alarm.minutes with inverse
enabled: bind not(alarm.running)
},
Label {
enabled: bind not(alarm.running)
text: "min "
}
]
},
FlowPanel {
background: Color.WHITE
content: [
Slider {
minimum: 0, maximum: 59
value: bind alarm.seconds with inverse
enabled: bind not(alarm.running)
},
Label {
enabled: bind not(alarm.running)
text: "sec "
}
]
},
FlowPanel {
background: Color.WHITE
hgap: 40
content: [
Button {
enabled: bind alarm.running
text: "Stop"
action: bind function(): Void {
alarm.running = false;
alarm.timer.stop();
}
},
Button {
enabled: bind not(alarm.running)
text: "Start"
action: function(): Void {
var date = new Date();
alarm.count = alarm.hours * 3600 + alarm.minutes * 60
+ alarm.seconds;
alarm.startTime = date.getTime() / 1000;
alarm.remainingTime = alarm.count;
if (alarm.count != 0) {
alarm.alarmEnabled = true;
alarm.running = true;
alarm.timer.start();
}
}
}
]
}
]
}
}
}

また、次に警報鳴動Javaファイルを示します。

package clock;

import java.applet.Applet;
import java.applet.AudioClip;
import javax.swing.JOptionPane;

public class Sound {
public static void alarm() {
AudioClip ac
= Applet.newAudioClip(Sound.class.getResource("TestMusic.wav"));
ac.loop();
JOptionPane.showMessageDialog(null, "時間ですよ!", "警告!警告!",
JOptionPane.WARNING_MESSAGE);
ac.stop();
}

public static void main(String[] args) {
Sound.alarm();
}
}

上述のJavaFX API文書は、実装されているものとクラス名や属性名が異なっていたり、未実装の機能が存在します。そのため、現状では、多少の試行錯誤が必要です。
また、「The JavaFX™ Script Programming Language Reference (Draft)」の「Chapter 5. Expressions and Operators」に載っている演算子の表は、「JavaFX台本型プログラミング言語基準(草案):第5章」を投稿した後に、変更されています。演算子の剰余は「%」から「mod」に、不等価は「<>」から「!=」になりました。変更があったことについては記されておらず(
、Draft(草案)ですから、仕方ありませんが…)、同ページに載っているコード例では、前の演算子がそのまま使われているので、注意が必要です(「表の方が最新です」と言いたいのですが、「以上」の演算子「>=」が「=>」(動画の補間直定数生成演算子)になっているなど、いくつか誤記があります。…)。

2008年7月26日土曜日

基本的なマウス入力の見本


同じく、NetBeans 6.1用JavaFX追加接続(Plugin)プログラムに付属している見本を参考に、基本的なマウス入力応用プログラムの見本を作ってみました。
実行すると、図のように、マウス指示器の座標と押されたマウス ボタンの番号(一番左のボタンが1。押されていないときは0)が、マウス指示器に追随して表示されます(それ用の画像取り込み用具を使っていないので、肝心のマウス指示器の画像を取り込めていませんが、あしからず)。空色の矩形の中にマウスを移動すると、矩形が半透明で表示されます。
次に、コードを示します。

package deviceInput;

import javafx.application.Frame;
import javafx.application.Stage;
import javafx.input.MouseEvent;
import javafx.scene.Group;
import javafx.scene.geometry.Rectangle;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.TextOrigin;
import javafx.scene.transform.Translate;

import java.lang.System;

Frame {
var mouseEvent: MouseEvent;
title: "Mouse Input"
width: 400
height: 332
visible: true
closeAction: function(): Void {
System.exit(0);
}
stage: Stage {
width: 400
height: 300
content: [
Rectangle {
x: 50, y: 50
width: 300, height: 200
fill: Color.LIGHTGRAY
onMousePressed: function(ev: MouseEvent): Void {
mouseEvent = ev;
}
onMouseClicked: function(ev: MouseEvent): Void {
mouseEvent = ev;
}
onMouseReleased: function(ev: MouseEvent): Void {
mouseEvent = ev;
}
onMouseMoved: function(ev: MouseEvent): Void {
mouseEvent = ev;
}
},
Rectangle {
x: 150, y: 100
width: 100, height: 100
fill: Color.SKYBLUE
onMouseEntered: function(ev: MouseEvent): Void {
ev.node.opacity = 0.5;
}
onMouseExited: function(ev: MouseEvent): Void {
ev.node.opacity = 1.0;
}
},
Group {
transform : Translate {x: bind mouseEvent.getX() + 20,
y: bind mouseEvent.getY()}
content : [
Text {
textOrigin: TextOrigin.TOP
fill: Color.RED
content: "StageX:"
},
Text {
translateX: 45
textOrigin: TextOrigin.TOP
fill: Color.RED
content: bind "{mouseEvent.getStageX() as Integer}"
},
Text {
translateX: 80
textOrigin: TextOrigin.TOP
fill: Color.RED
content: "StageY:"
},
Text {
translateX: 125
textOrigin: TextOrigin.TOP
fill: Color.RED
content: bind "{mouseEvent.getStageY() as Integer}"
},
Text {
translateY: 15
textOrigin: TextOrigin.TOP
fill: Color.RED
content: "X:"
},
Text {
translateX: 15, translateY: 15
textOrigin: TextOrigin.TOP
fill: Color.RED
content: bind "{mouseEvent.getX() as Integer}"
},
Text {
translateX: 50, translateY: 15
textOrigin: TextOrigin.TOP
fill: Color.RED
content: "Y:"
},
Text {
translateX: 65, translateY: 15
textOrigin: TextOrigin.TOP
fill: Color.RED
content: bind "{mouseEvent.getY() as Integer}"
},
Text {
translateY: 30
textOrigin: TextOrigin.TOP
fill: Color.RED
content: "Button:"
},
Text {
translateX: 40, translateY: 30
textOrigin: TextOrigin.TOP
fill: Color.RED
content: bind "{mouseEvent.getButton()}"
}
]
}
]
}
}

マウス事象は、その
事象が発生した図式の節(graph node。見本コードでは、Rectangleオブジェクトを使用している)の、事象属性onXxxYyyに定義した呼び戻し関数を用いて取得します。マウス事象から、マウスの座標やボタン開閉器の番号などの情報が得られます。詳しいことは、JavaFX APIを参照してください。
なお、図からもわかりますが、現状(2008/07/23現在の日々構築版追加接続プログラム)では、入力される座標に、一部不具合があるようです。マウス事象が発生した節の原点に対する相対位置を返す関数getX()/getY()は、その節を含むStageの原点に関する相対位置を返すgetStageX()/getStageY()と同じ値を返してしまいます(画面上の絶対位置を返すgetScreenX()/getScreenY()は、正常に機能しています)。

2008年7月19日土曜日

基本的な動画の見本

NetBeans 6.1用JavaFX追加接続(Plugin)プログラムに付属している見本を参考に、基本的な動画応用プログラムの見本を作ってみました。
実行すると、図のような円が色を変えながら左右に往復運動します。
次に、コードを示します。

package motionPicture;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Frame;
import javafx.application.Stage;
import javafx.scene.geometry.Circle;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;

import java.lang.System;

var x: Integer = 50;
var vx: Integer = 1;
var color: Color = Color.rgb(25, 50, 0);

var timeline: Timeline = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames:
KeyFrame {
time: 10ms
action: function(): Void {
x += vx;
color = Color.rgb((x / 2), x, 0);
if (x < 50) {
vx = 1;
} else if (x > 250) {
vx = -1;
}
}
}
};

timeline.start();

Frame {
title: "Basic Motion"
width: 310
height: 130
visible: true
closeAction: function(): Void {
System.exit(0);
}
stage: Stage {
fill: Color.LIGHTGRAY
content: Circle {
centerX: bind x, centerY: 50, radius: 30
fill: bind color
}
}
}
動画は、前はdur演算子を用いて実現していましたが、JavaFX SDKでは、javafx.animation.Timelineクラスの機能を利用します。
repeatCount属性には、動画の繰り返し回数を、0以上の整数で指定します。無限に繰り返すときは、上記のように、
Timeline.INDEFINITEを指定します。
keyFrames属性には、KeyFrameの列を指定します。
そして、start()関数を呼び出すと、動画が開始します。
また、各KeyFrameのtime属性には、
Timelineの1周期における相対時刻を指定します。
action属性には、
Timelineの各周期で、time属性で指定した時間が経過したときに呼び出される関数を指定します。上記の例では、無名関数で動画処理を定義しています。
以上が、基本的な動画の記述方法の例です。
最後に、ご注意を一言。本当は、枠付き窓の表題は(「Basic Motion」ではなく)日本語で表示したかったのですが、私が利用している、
2008/07/16現在の日々構築版追加接続プログラムでは日本語表示ができません、(標準出力へも表示できません)。「MS932にマップできない」旨の誤り通報文が表示されて構築できなかったり、構築には成功しても、表示は文字化けを起こします
Javaのプログラムからは問題なく日本語表示できるので、JavaFX追加接続プログラムに関連した問題と思われますが、原因は不明です(追加接続プログラム自体の問題なのか、設定上の問題なのかも分かりません)。

2008年7月17日木曜日

NetBeans 6.1用JavaFX追加接続プログラムの更新

最近のJavaFXについては、The JavaFX™ Script Programming Language Reference (Draft)が公開されていることを、以前の投稿でご紹介しました。それと一緒に、「JavaFX API」も最新のものが公開されています。
ただ、このAPI文書はJavaFX SDKに対応したものなので、「The JavaFX™ Script Programming Language Reference (Draft)」文書のコード例で引用されている
javafx.guiパッケージなどは、もう載っていません。
私は、NetBeans 6.1にJavaFX追加接続(Plugin)プログラム(2008/06/15現在の日々構築版)を追加導入して使っていたのですが、導入時点では、JavaFX SDKには対応していませんでした。最近紹介される見本プログラムはJavaFX SDKに対応したものになっていますので、この機会に、最新の追加接続プログラムに更新することにしました。
(NetBeans 6.1とそのJavaFX追加接続プログラムの導入をお考えの方は、、「Downloading and Installing a Developmental Build of JavaFX Script Plugin for NetBeans IDE 6.1」が参考になります。ただし、JDKは、(JDK 5ではなく)JDK 6を導入しておいたほうが良いかも知れません。以前の投稿でも記しましたが、JDK 5では一括変換済みコードが実行できないことがありました。少なくとも、私自身はJDK 5で動作させたことはありません
最新の追加接続プログラム(2008/07/16現在の日々構築版)を入手し、以前行った手順と同じようにして追加接続プログラムを更新しようとしたのですが、パソコンに取り込み済みの追加接続プログラムの更新方法が分かりません(NetBeansにあまり習熟していないので)。しかたなく、すでに導入済みの追加接続プログラムを取り外してから、改めて最新版を導入しなおすことにしました。
更新が済み、NetBeansを再起動すると、作成済みの企画(Project)の名称が朱字で表示され、「参照の問題を解決せよ」なる旨の通報窓が表示されます。指示されように、
基盤(Platform)環境にJavaFX SDK(私の環境では、選択肢に「JavaFX SDK #1」がありましたので、それ)を追加すると、企画名が元の黒字表示に戻り、「参照の問題」が解決したようです

2008年7月5日土曜日

JavaFX台本型プログラミング言語基準(草案):付録A

A.鍵語と予約語
この付録は、JavaFX™台本型プログラミング言語において予約された単語を記録したものである。
表A.1  鍵語と予約語
実装済み鍵語
abstract attribute bind break
class continue delete false for
function if import init insert
new not null package
private public return
super sizeof static this throw
try true var while after
and as before catch
dur else exclusive extends
finally in bound
indexof into inverse lazy
on or replace step
with where instanceof override at
then tween

予約済み鍵語
assert by do first from
last let protected readonly typeof
lazy

JavaFX台本型プログラミング言語基準(草案):第9章

9.引き金
9.1 置換引き金
置換引き金に対する構文は次のとおりである。

"on" "replace"
[旧値]
[ "[" 最低索引値 ".." 最高索引値 "]" "=" 新要素 ]
区画

置換引き金は、引き金を取り付けた変数に対するすべての変更に続いて呼び出される。構文上の非終端子である旧値、最低索引値、最高索引値、および新要素は、その本体が区画であるところの関数に対する、事実上の仮引数である。
・旧値は変数の事前の値であり、変数と同じ型を持つ。
・最低索引値と最高索引値は、旧値において置換対象となる部分の範囲を区切る。それらの型はIntegerである。純然たる挿入に対しては、「最高索引値==最低索引値-1」である。
・新要素は、部分列「旧値[最低索引値..最高索引値]」を置き換える値の列である。その型は変数と同じである。
引数「[最低索引値..最高索引値]=新要素」は、引き金が取り付けられた変数が列型であるときのみ認められる。
旧値と新要素は非対称であることに留意せよ。すなわち、旧値は、引き金が取り付けられた変数の事前の値であるのに対し、新要素は、その変数で変更される要素のみを含んでいる新値の部分列であるということである。
置換引金の構文は、部分列の割り当てを思い起こさせるようになっている。次を想定する。

attribute x
on replace oldVal[lo..hi]=newVals { exp };
var save = x;
x[i..j] = y;

すると、oldVal はsaveに、loはiに、hiはjに、そしてnewValsはyに結びつけられた上で、expが評価される。
統合置換引き金は、部分列の割り当てとうまく機能する。たとえば、次のように結合を定義することができる。

attribute x;
attribute y = bind x;

これは次と等価である。

attribute x =
on replace [i..j]=n
{ y[i..j]=n };
attribute y = [];

次のように、yがxの写像だとする。

attribute x;
attribute y = bind for (xi in x) f(xi);

これは次と等価である。

attribute x =
on replace [i..j]=n
{ y[i..j] = for (k in n) f(k) };
attribute y = [];

列の要素のある範囲を削除または置換すること、もしくは列を一箇所に挿入することは、すべて単一の引き金呼び出しに帰着する。述語条件を満たすすべての要素の削除のような、別の演算のあるものは、複数の引き金呼び出しに分解できるかもしれない。In that case the state as seen by each trigger invocation is consistent in the sense that the programmer-visible state is as if the predicate-delete (for example) were implemented as a set of independent slice-delete operations.

陳謝:最後の文は、拙には何が言いたいのか判然としないため、原文のまま載せておきます。
m(_ _;)m

2008年7月4日金曜日

JavaFX台本型プログラミング言語基準(草案):第8章

8.データ結合
bind鍵語は、対象となる変数の値を別の変数の値に結びつける。

let x = bind someExpression;

これは、someExpressionの結果を変数xに結びつける。someExpressionの値が変わると、xの値も自動的に更新される。結び付けられる変数は、ある基本データ型の単純な値、式の結果、区画式の結果または結合関数でもよい(let鍵語は、それらに割り当てることができない変数を導入する)。

8.1 再計算して更新する
ある状況では、更新がどのように行われるか、あるいは結び付けられる値が変化するということはどういう意味なのかを厳密に知っている必要ある。結び付けられる値が変化すると、最小限の再計算が実行される。このことは、ある限られた状況において重要となる。たとえば、オブジェクトの生成を必要とする結合が実行される場合で、オブジェクトの同一性の理由から、新たなオブジェクトが実際に生成されるか否かが問題となるような状況である。

let sum = bind expr1 + expr2;

(上記の例で、)expr2が変化するとsumは再計算されるが、expr1は再計算されない。expr1に対する値はすでに格納されており、単にその値を再度取ってくるだけである。

次に実例を示す。

import java.lang.System;

var y = 3;
function ten() : Integer { 10 }
let sum = bind ten() + y;
System.out.println(sum);
y = 7;
System.out.println(sum);

このプログラムの出力は次のようになる。

13
17

yが7に設定されたとき、関数ten()は変化していないので再度呼び出されることはない。関数値は記憶されており再利用される。

8.2 結合と区画式
結合において、区画式の中に存在しうるのは変数宣言文だけである。(1増および1減を含め)値の割り当ては禁止されていることに留意せよ。したがって、結合区画式は次のような形式を取る。

bind { var a = expr; var b = expr; var c = expr; expr }

結合された式に対するいかなる変化も更新を引き起こし、その更新は最小限なので、変数が有効的に結合されていることを確認するのは容易である。while、insertおよびdeleteは文なので、結合の中に現れてはならないことに留意せよ。

8.3 結合と条件式
次の例を考えてみよ。

let x = bind if (condExpr) expr1 else expr2;

condExprが変わると、if文のどちらの分岐が評価されるのかも変わる。expr1またはexpr2に対する変化は、どちらも、もう一方の式の再計算を引き起こすことはない。

8.4 結合とfor式
次の例を考えてみよ。

let newSeq = bind for (elem in seq) expr;

たとえseqが変わっても、依然としてseqに存在し続けている要素に対応するnewSeqの要素は、再計算されない。言い換えれば、要素がseqに挿入されると、その要素にexprを適用した結果がnewSeqの相当する位置に挿入され、newSeqの他の要素は再計算されない。
例外は、exprがindexof elemを使用しているために、索引の値が変化した要素を更新する必要がある場合であるが、最小限の更新という既定には合致している。

import java.lang.System;

var min = 0;
var max = 3;
function square(x : Integer) : Integer { x*x }
let values = bind for (x in [min..max]) square(x);
System.out.println(values);
max = 5;
System.out.println(values);
min = 1;
System.out.println(values);
min = 0;
System.out.println(values);

この場合、次の再計算が実行される。
・0から3の2
を計算する。
・4と5の2乗を計算する(maxが変わっても、0から3の2
は再計算されない)。
・ほかの値は再計算せず、0の2
を削除する。
・0の2条を戻す。これは、その値の再計算を必要とする。この動作は、代わりにinsertやdeleteを用いても同様である。

8.5 結合とオブジェクト直定数
オブジェクト直定数は、演算子や非結合関数のように機能する。オブジェクト直定数の引数の一つが変化すると、オブジェクト直定数は再実行(新たな実体が生成)される。

let pt = bind Point { x: myX y: myY }

myXが変わると、JavaFX台本型プログラミング言語は、新たなPointオブジェクトを生成する。これは、不変オブジェクトに対して予期された振る舞いでもある。
新たなオブジェクトを生成せずに、xの値がmyXの値を追跡するようにするためには、結合が必要になる。

let pt = bind Point { x: bind myX y: myY }

この例に対しては、おそらくyも同様に結合した方が良いだろう。

let pt = bind Point { x: bind myX y: bind myY }

ここで、ptは常に同じPointオブジェクトの実体と結合したままである。それは、冒頭のbindがなくても同様である。

let pt = Point { x: bind myX y: bind myY }

8.6 結合と関数
非結合関数は、bound鍵語が前置されていない関数である。Javaプログラミング言語の操作やJavaFX台本型プログラミング言語の関数の呼び出しに関して、その引数のいずれかが変更されると、JavaFX台本型プログラミング言語はその関数を再度呼び出すが、関数の本体は暗箱である。関数が、その入力となる引数以外に持っている依存性のゆえに再計算されることはない。

import java.lang.System;

class Point {
attribute x : Number;
attribute y : Number;
}

var scale = 1.0;

function makePoint(x0 : Number, y0 : Number) : Point {
Point {
x: x0 * scale
y: y0 * scale
}
}

var myX = 3.0;
var myY = 3.0;
let pt = bind makePoint(myX, myY);
System.out.println(pt.x);
myX = 10.0;
System.out.println(pt.x);
scale = 2.0;
System.out.println(pt.x);

出力。

3.0
10.0
10.0

引数myXを変更すると、関数makePointは再度呼び出される。しかし、関数makePointは暗箱である。よって、scaleに割り当てられた値が変更されても、当然ではあろうが、関数は再計算されない。これこそが、結合関数が役立つことを意図された場面である。結合関数が予期された更新をもたらしてくれる。
結合関数は、その本体として、結合される区画式を持っている(それゆえに、結合区画式に関する上述の制限を有している)。結合関数に結びつけると、引数以外の変化も更新を引き起こし、かつ引数の変更も調べられる。上述の関数makePointを結合関数にすると次のようになる。

bound function makePoint(x0 : Number, y0 : Number) : Point {

今度こそ、scaleに割り当てられた値の変化は更新(20.0)を引き起こす。myXが変わるとx0 * scaleだけが再計算され、y0 * scaleは再計算されないことに留意せよ。
結合とは別のところで結合関数を呼び出すことは、非結合関数を呼び出すことと同じことである。


2008年7月3日木曜日

JavaFX台本型プログラミング言語基準(草案):第7章

7.列内包
7.1 forを用いた列の繰り返し
JavaFX台本型プログラミング言語は、for演算子を用いた列内包を支援する。列内包は、1つ以上の入力列、任意選択としてのろ過器、および式から構成される。各入力列は、変数と関連している。列内包の結果は、元となる列の要素の内でろ過器の条件を満たすものの組合せに、式を適用したものからなる新たな列である。
次のプログラムはこの構文の実例を示したもので、曲集の一覧から曲の一部が表題となっているものを特定するためにfor演算子を用いている。

class Album {
attribute title: String;
attribute artist: String;
attribute tracks: String[];
}

var albums =
[Album {
title: "A Hard Day's Night"
artist: "The Beatles"
tracks:
["A Hard Day's Night",
"I Should Have Known Better",
"If I Fell",
"I'm Happy Just To Dance With You",
"And I Love Her",
"Tell Me Why",
"Can't Buy Me Love",
"Any Time At All",
"I'll Cry Instead",
"Things We Said Today",
"When I Get Home",
"You Can't Do That"]
},
Album {
title: "Circle Of Love"
artist: "Steve Miller Band"
tracks:
["Heart Like A Wheel",
"Get On Home",
"Baby Wanna Dance",
"Circle Of Love",
"Macho City"]
}];

for (album in albums, track in album.tracks) {
if (album.title == track)
java.lang.System.out.println("TITLE TRACK = {track}")
else
java.lang.System.out.println("Track = {track}")
}

出力:

TITLE TRACK = A Hard Day's Night
Track = I Should Have Known Better
Track = If I Fell
Track = I'm Happy Just To Dance With You
Track = And I Love Her
Track = Tell Me Why
Track = Can't Buy Me Love
Track = Any Time At All
Track = I'll Cry Instead
Track = Things We Said Today
Track = When I Get Home
Track = You Can't Do That
Track = Heart Like A Wheel
Track = Get On Home
Track = Baby Wanna Dance
TITLE TRACK = Circle Of Love
Track = Macho City

次に示すのは、ろ過器を用いた別の例である。これは、数を受け取ってその因数の一覧を返す関数を定義したものである。

function factors(n:Number) {
return for (i in [1 .. n/2] where n % i == 0) i;
}

訳注:
この例では、:Number型の「因数」の列を戻すので、正確には「因数」とは呼べないが…。

forに対する内包の中で、indexof演算子を使用することができる。その構文は「indexof 名称」で、名称は繰り返し変数の名前である。その値は、基となる列における繰り返しの索引の値である。
次の例に示したように、indexof演算子は列の抽出に使用することもできる。

var nums = [1..5];
var numsExceptTheFirstTwo = nums[n|indexof n > 1]; // [ 3, 4, 5 ]を返す

7.2 列変数の変更
insertは、列に新たな要素を挿入する。

insert x into seq
insert x before seq[idx]
insert x after seq[idx]

deleteは、列から要素を取り除く。

delete seq
delete x from seq
delete seq[idx]
delete seq[a..b]

次のコードは、それぞれの例である。

// 挿入の例

var nums = [1..5];
var x = 6;
insert x into nums; // 結果は、[ 1, 2, 3, 4, 5, 6 ]
x++;
insert x before nums[0]; // 結果は、[ 7, 1, 2, 3, 4, 5, 6 ]
x++;
insert x after nums[3]; // 結果は、[ 7, 1, 2, 3, 8, 4, 5, 6 ]

// 削除の例

nums = [1..5];
delete 2 from nums; // 結果は、[ 1, 3, 4, 5 ]
delete nums[0];// 結果は、[ 3, 4, 5 ]
nums = [1..10]; // 結果は、[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
delete nums[3..7]; // 結果は、[ 1, 2, 3, 9, 10 ]
delete nums; // 結果は、[ ]
nums = [1..10];
delete nums[5..]; // 結果は、[ 1, 2, 3, 4, 5 ]
delete nums[0..<]; // 結果は、[ 5 ]

訳注:原文では、最後の「delete nums[0..<];」文が「delete nums[0..>];」と誤記されている。

列の要素を逆順に並べ替えることもできる。

var nums = [1..5];
reverse nums; // [ 5, 4, 3, 2, 1 ]を返す

訳注:2008年6月15日時点のNetBeans 6.1用JavaFX追加接続(Plugin)プログラムで動作確認したところ、「reverse nums;」だけでは、元のnums の要素の並びは変わらなかった(並び替えた結果を利用するためには、
nums = reverse nums;」のように列変数に代入しなおす必要がある。その意味では、delete演算子とは用法が異なる)。

JavaFX台本型プログラミング言語基準(草案):第6章

6.列
先に述べた5つの基本データ型に加え、JavaFX台本型プログラミング言語は、列として知られたデータ構造を提供する。列はJavaプログラミング言語の配列に似ているが、異なる点もある。
次のコードはある例を示す。

var weekDays = ["Mon","Tue","Wed","Thur","Fri"];
var days = [weekDays, ["Sat","Sun"]];

列は、オブジェクトの順序付けられた一覧を表す。しかしながら、列そのものはオブジェクトではないため、入れ子構造を取ることはできない。列は、値により等価であるとみなされる。すなわち、列の長さが等しく、それらの要素が同一であるとき、列は等しくなる。(上記のdaysの初期化におけるように)入れ子になった列を形成する式は、自動的に平準化される。

days == ["Mon","Tue","Wed","Thur","Fri","Sat","Sun"]; // trueを返す

さらに、単一のオブジェクトは、1つのオブジェクトからなる列に等価である。

1 == [1]; // trueを返す

列の型は、「[]」注釈を用いて宣言される。

var xs:Number[]; // 数の列
var strs:String[]; // 文字列の列

列の要素は共通の型を持たねばならず、その型はオブジェクトであっても良い。列は、Javaプログラミング言語の配列のように、索引付けることがきる。

var wednesday = days[2];

次のように、その要素が等差数列をなしている列のための、「..」を用いた簡便な表記法もある。

var nums = [1..100];

この簡略記法により、各要素を手入力する必要がなくなる。
[]演算子は、述語形式の選択も表す。述語は、次の形式を取る。

列[変数名| 論理式]

例:

var nums = [1,2,3,4];
var numsGreaterThanTwo = nums[n|n > 2];

上記の式は、元の列の要素の内、述部の条件を満たすものから構成された新たな列を返す。
最後に、部分列は、列の一部を利用できるようにする。

seq[a..b] // 索引aとbの間(両端含む)の列
seq[a..<b] // 索引a(含む)とb(含まない)の間の列
seq[a..] // seq[a..<sizeof seq]に同じ
seq[a..<] // 一貫性を保つため、seq[a..<sizeof seq-1] に同じ

列や部分列を、利用および/または変更するいくつもの異なる方法がある。次の章では、JavaFX台本型プログラミング言語の一覧内包の特徴について探求する。


2008年7月2日水曜日

JavaFX台本型プログラミング言語基準(草案):第5章

5.式、文、および演算子
5.1 式
Javaプログラミング言語と異なり、JavaFX台本型プログラミング言語は式言語である。すべての実行文は、0個以上の入力とそれに続く単一の出力から構成される式である。これには、条件、繰り返し、ならびにコード区画さえも含まれる。
次に実例を示す。

import java.lang.Math;
import java.lang.System;

var rand = (Math.random() * 100).intValue();
var s:String = null;
if (rand % 2 == 0) {
s = "rand is even"
} else {
s = "rand is odd"
};

System.out.println(s);

上記の例で、条件式ifのthen節とelse節は、それ自体が式、すなわち区画式である。

5.2 区画式
区画式は、中括弧({})で囲まれた、セミコロン(;)区切り文(宣言または式でもよい)の一覧から構成される。(区画内の)最後の文が式なら、その式の値が区画式の値となる。さもなければ、区画式はvoid型となる。
したがって、先の例は次のように書くこともできる。

import java.lang.Math;
import java.lang.System;

var rand = (Math.random() * 100).intValue();
var s:String =
if (rand % 2 == 0) {
"rand is even";
} else {
"rand is odd";
};

System.out.println(s);

あるいは、(次のように)中括弧を省略することができる。

import java.lang.Math;
import java.lang.System;

var rand = (Math.random() * 100).intValue();
var s:String = if (rand % 2 == 0) "rand is even" else "rand is odd";

System.out.println(s);

Javaプログラミング言語は、if文と条件式「a < b ? a : b」の両方を持っている。 区画式のおかげで、JavaFX台本型プログラミング言語のif式は、両者の代わりをしている。

5.3 値域式
値域式は、次の構文により、等差数列を形成している数値の列を定める。

[数1..数2]

値域式は、数1から数2までの整数(両端含む)からなる列を定める。 値域式の簡単な例として、次のようなものが考えられる。

import java.lang.System;

var nums = [0..3];
System.out.println(nums == [0,1,2,3]); // trueと表示する

既定では数値間の間隔は1だが、異なる値を指定することもできる。たとえば、次の式は、1から10の間の奇数からなる列を定める。

[1..10 step 2]

降順の値域を生成するためは、2つ目の値が1つ目の値より小さいことを確認し、負数の間隔を指定する。

import java.lang.System;

var nums = [3..0 step -1];
System.out.println(nums == [3,2,1,0]); // trueと表示する

次の宣言文は、実際には空の列を宣言することに留意せよ。

var nums1 = [3..0 ];
var nums2 = [3..0 step 1];

5.4 条件式
if式は、else節がさらなるif式でない限りthen節とelse節を囲む中括弧が常に必要であるという点を除いて、Java™プログラミング言語における表現と似ている。

if (condition1) {
System.out.println("Condition 1");
} else if (condition2) {
System.out.println("Condition2");
} else {
System.out.println("not Condition 1 or Condition 2");
}

5.5 繰り返し式


5.6 文
return文は、Javaプログラミング言語において見られるものと同一である。

function add(x, y) {
return x + y;
}

throw文は、(Javaプログラミング言語では)java.lang.Throwableを拡張したオブジェクトだけが投げられたり捕捉されたりできるという点を除き、Javaプログラミング言語のものと似ている。

import java.lang.Exception;

function foo() {
throw new Exception("this is a java exception");
}

function bar() {
throw "just a string";
}

tryとcatch文は、Javaプログラミング言語のものと似ているが、JavaFX台本型プログラミング言語の変数宣言の構文を用いる。

try {
throw "Hello";
} catch (s:String) {
System.out.println("caught a String: {s}");
} catch (any) {
System.out.println("caught something not a String: {any}");
} finally {
System.out.println("finally...");
}

break文とcontinue文は、Javaプログラミング言語のものと似ている。しかし、ラベルは支援されていない。break文とcontinue文は、whileまたはfor繰り返しの本体内に現れなければならない。

5.7 演算子
JavaFX台本型プログラミング言語は、Javaプログラミング言語にみられるものと同様の標準的な演算子を提供する。次の表は、Javaプログラミング言語における等価物と比較させながら、優先順位に従って演算子を一覧表にしたものである。

表 5.1 演算子の優先順位
優先度 JavaFX演算子 演算 Java演算子 結合規則
1 = 代入 = 右から左
2 += 加算代入 +=
-= 減算代入 -+
*= 乗算代入 *=
/= 除算代入 /=
%= 剰余代入 %=
3 and 論理and && 右から左
4 or 論理or || 右から左
5 instanceof 実体 instanceof 右から左
as 型変換 該当なし
sizeof 配列長 該当なし
indexof 索引位置 該当なし
new 生成 new
op() 関数呼び出し 該当なし
x.op() 要素関数呼び出し
6 == 等価 == 左から右
<> 不等価 !=
<= 以下 <=
<-
< 小なり <
> 大なり >
7 + 加算 + 右から左
- 減算、算術否定 -
8 * 乗算 * 右から左
/ 除算 /
% 剰余
=> 動画
9 ++ (前置) 1増 ++ 右から左
-- (前置) 1減 --
10 ++ (後置) 1増 ++ 右から左
-- (後置) 1減 --


import java.lang.System;
import java.lang.Math;

var x = 2;
var y = 4;
var a = true;
var b = false;
System.out.println(x == y); // falseを表示する
System.out.println(x <> y); // trueを表示する
System.out.println(x < y); // trueを表示する
System.out.println(x > y); // falseを表示する
System.out.println(x >= y); // falseを表示する
System.out.println(x <= y); // trueを表示する
System.out.println(x + y); // 6を表示する
System.out.println(x - y); // -2を表示する
System.out.println(x * y); // 8を表示する
System.out.println(x / y); // 0を表示する
System.out.println(x % y); // 2を表示する
System.out.println(a and b); // falseを表示する
System.out.println(a or b); // trueを表示する
System.out.println(not a); // falseを表示する
System.out.println(sizeof [x,y]); // 2 を表示する
System.out.println([x,y][e | indexof e == 0]); // [ 2 ]を表示する
System.out.println(if (a) x else y); // 2を表示する
System.out.println(for(q in [x, y] where q < 3) q); // [ 2 ]を表示する
System.out.println(Math.max(x, y)); // 4を表示する
System.out.println("abc".toUpperCase()); // ABCを表示する
System.out.println(x); // 2を表示する

2008年7月1日火曜日

JavaFX台本型プログラミング言語基準(草案):第4章

4.変数と基本データ型
4.1 変数
第1章で論じたように、var鍵語は、新たな変数をプログラムに導入するために用いられる。変数の型は、それを宣言する際に指定してもよいが、そうするか否かは自由である。変数の型が宣言から省略されると、その用いられ方から型が推測される。
変数宣言は、次の形式を取る。

var 変数名 : 型 = 初期化子;

例:

var num = 1; // 推測された型
var num : Number = 2;
var firstName = "John"; // 推測された型
var lastName : String = "Doe";

変数命名法の慣例は、Java™プログラミング言語に見られるものと同様である。クラス名は、各単語の最初の文字を大文字にする(MyClass)。関数名は小文字で始まるが、後に続く各単語の最初の文字は大文字にする(myFunctionName)。定数は、下線文字で区切られた単語により、すべて大文字で表されなければならない(MY_CONSTANT)。
フランス引用符<<>>で囲まれた(空白文字を含む)いかなる文字列も、識別子として扱われる。これにより、JavaFX™台本型プログラミング言語の鍵語(または、他の、通常は不正な識別子)を、クラス、変数、関数、または属性の名前として用いることができる。
例:

var <<delete>> = 100;

これは、JavaFX台本型プログラミング言語の鍵語と同じ名前の(Javaプログラミング言語で書かれた)操作を呼び出すことも可能にしている。

import javax.swing.JTextArea;

var textArea = new JTextArea();
textArea.<<insert>>("Hello", 0);

変数の存続期間は、少なくともそれを含むコード区画の存続期間と同じである。

4.2 基本データ型
JavaFX台本型プログラミング言語では、原始型(プリミティブ型)という用語は使わない。代わりに、5つの基本データ型を定義しており、応用コードで常に利用することができる。
5つの基本データ型は、次のようにJavaプログラミング言語に対応付けられる。

表4.1 基本型
JavaFX台本型プログラミング言語     Javaプログラミング言語
String java.lang.String
Boolean java.lang.Boolean
Number java.lang.Number
Integer byte,short,int,long,BigInteger
Duration 該当なし

最初の4つの型は、Javaプログラミング言語でもしばしば使われるので、ほとんどの開発者には既におなじみであろう。しかし、Duration型はこれまでになく、JavaFX台本型プログラミング言語に特有のものである。javafx.lang.Durationクラスは、時間の単位(ミリ秒、秒、分、または時間)を表す。Durationクラスを実体化するための簡略表現である、時間直定数も支援している。

5ms; // 5ミリ秒
10s; // 10秒
30m; // 30分
1h; // 1時間

整数を表す型については、Javaプログラミング言語で書かれた操作と引数または戻り値を受け渡すときに、自動的に強制型変換が行われる。
文字列に関しては、一重引用符または二重引用符を用いて文字列直定数を指定することができる。

var s1 = 'Hello';
var s2 = "Hello";

その違いは、後者(二重引用符)を用いると、中括弧({})の中に式を埋め込むことができることにある。

var name = 'Joe';
var s = "Hello {name}"; // s = 'Hello Joe'

中括弧を用いると、一括変換器は、埋め込まれた式に対する型を推測し、文字列へ強制型変換することができる。
埋め込まれた式自体が引用符で囲まれた文字列を含むことができるので、次々に、さらなる埋め込み式を含むことができる。

var answer = true;
var s = "The answer is {if (answer) "Yes" else "No"}"; // s = 'The answer is Yes'

Javaプログラミング言語と異なり、JavaFX台本型プログラミング言語の二重引用符付き文字列は、改行文字を含むことができる。
付加的な書式設定接頭辞を与えることにより、数値とデータがどのように文字列に変換されるかについても制御することができる。そうした接頭辞は、java.util.Formatterの仕様に従う。

import java.util.Date;

var hexStr = "hex of 13 is 0x{%02X 13}"; // hexStr = "hex of 13 is 0x0D"
var date = new Date(107, 10, 11);
var dateStr = "{%tc date}"; // dateStr = "日 11 11 00:00:00 JST 2007"

訳注:原文では、「var dateStr = "{%tc date}";」文末のセミコロン(;)が抜け落ちている。また、日本では、日付は上記コメントの書式で表示される。