これの続き。
概要
前回で組み込み型にメソッドチェーンを組み込むことができることを確認した。
そこで今回はどこまで何をチェーンできるのか試してみた。
また、グローバルを汚染することになるjsに対し対応策を考えてみた。
実装要件
下記のモジュールを実装し、
10
.buy()
.buy()
.puts()
.gets("> ")
.insertStr("echo> ")
.puts()
.gets()
のように処理を行う。
ExtendObject モジュール
int object.put()
インスタンスの内容を改行無しで標準出力する。
戻り値は0を返す。
int object.puts()
インスタンスの内容を改行有りで標準出力する。
戻り値は0を返す。
string string.insertStr(string)
Stringインスタンスにの手前に文字列を結合して返す。
double number.buy()
Numberインスタンスの値を2倍にして返す。
string int.gets(string)
Promise<string> int.gets(string)
標準入力を返す。
インスタンスの値は使用しない。
JavaScriptのみ戻り値の方はPromise型とする。
Promise<void> object.gets(string)
プログラムを終了させる。
JavaScriptのみ実装。
実装
D言語
import ExtendObject;
mixin ExtendObject;
void main(){
10
.buy
.buy
.puts
.gets("> ")
.insertStr("echo> ")
.puts
.gets;
}
module ExtendObject;
template ExtendObject(){
import std.stdio;
template put(T){
int put(T self){
write(self);
return 0;
}
}
template puts(T){
int puts(T self){
writeln(self);
return 0;
}
}
string insertStr(string self,string s){
return s~self;
}
template buy(T){
T buy(T self){
return self*2;
}
}
string gets(int self){
return readln();
}
string gets(int self,string s){
write(s);
return readln();
}
}
名前空間代わりにtemplate使っていること以外は至って順当な実装。
C#
using ExtendObject;
static class Program{
static void Main(){
10
.buy()
.buy()
.puts()
.gets("> ")
.insertStr("echo> ")
.puts()
.gets();
}
}
using System;
using static System.Console;
namespace ExtendObject{
static class ObjectChain{
static public int put(this object self){
Write(self);
return 0;
}
static public int puts(this object self){
WriteLine(self);
return 0;
}
static public string insertStr(this string self,string s){
return s+self;
}
static public double buy(this object self){
return Convert.ToDouble(self)*2;
}
static public string gets(this object self){
return ReadLine();
}
static public string gets(this object self,string s){
Write(s);
return ReadLine();
}
}
}
数値型をObject型としていい加減に扱っていること以外は普通。前回からそのまま拡張。
VB.Net
Option Infer On
Option Strict On
Imports project.ExtendObject
Module Program
Sub Main
Call 10 _
.buy _
.buy _
.puts _
.gets("> ") _
.insertStr("echo> ") _
.puts _
.gets
End Sub
End Module
Option Infer On
Option Strict On
Imports System.Console
Imports System.Runtime.CompilerServices
NameSpace ExtendObject
Module ObjectChain
<Extension()>
Function put(self As Object) As Integer
Write(self)
Return 0
End Function
<Extension()>
Function puts(self As Object) As Integer
WriteLine(self)
Return 0
End Function
<Extension()>
Function insertStr(self As String,s As String) As String
Return s & self
End Function
<Extension()>
Function buy(self As Object) As Double
Return CDbl(self)*2
End Function
<Extension()>
Function gets(self As Integer) As String
Return ReadLine
End Function
<Extension()>
Function gets(self As Integer,myPrompt As String) As String
Write(myPrompt)
Return ReadLine
End Function
End Module
end NameSpace
プログラムについては大体C#と一緒。
しかし、数字から書き始めると行数がどうとか怒られたのはびっくり。
あとはビルドする環境(MSBuild, .Net Core他)次第でモジュールのImports名が変わるらしいので注意。
Imports ExportObject
だったり、
Imports project.ExportObject
だったりする。
ちなみにこの時の"projectはプロジェクト(.vbploj)名(ファイル名?)を指す。
JavaScript(Node.js)
"use strict";
const ExtendObject=require("./ExtendObject");
new class Program{
constructor(){
ExtendObject.using(async()=>{
await 10.
.buy()
.buy()
.puts()
.gets("> ").then(v=>v
.insertStr("echo> ")
.puts()
.gets()).then(v=>v
.exit());
});
}
};
"use strict";
module.exports=(()=>{
const rl=require("readline").createInterface(process.stdin,process.stdout);
const util=require("util");
const ObjectChain={
put:[Object,function(){
process.stdout.write(util.format(this.valueOf()));
return 0;
}],
puts:[Object,function(){
console.log(this.valueOf());
return 0;
}],
insertStr:[String,function(s){
return s+this.valueOf();
}],
buy:[Number,function(){
return this.valueOf()*2;
}],
gets:[Number,(myPrompt="")=>new Promise(resolve=>{
rl.setPrompt(myPrompt);
rl.prompt();
rl.once("line",resolve);
})],
exit:[Object,()=>{
process.exit();
return Promise.resolve();
}]
};
return class{
static open(){
for(var v in ObjectChain){
if(ObjectChain[v][0].prototype[v]!=null){
throw new RangeError("Property already exists.");
}
ObjectChain[v][0].prototype[v]=ObjectChain[v][1];
}
}
static close(){
for(var v in ObjectChain){
delete ObjectChain[v][0].prototype[v];
}
}
static using(fn){
if(typeof fn=="function"){
this.open();
const fnCall=fn();
if(toString.call(fnCall)=="[object Promise]"){
return fnCall.then(()=>
this.close()
);
}
else this.close();
}
else throw new TypeError("Type requires function.");
}
};
})();
前回から最も手を加えたのがこの実装。
結局、グローバルオブジェクトの汚染は防げないものの、
拡張メソッドの存在確認と処理後のメソッド破棄で安全にしたつもり。
usingメソッドにコールバック関数投げ込むかopen・closeメソッドの間で使用可能。
getsメソッドの戻り値はNode.jsのため致し方なくPromiseを返却。
Python3
from ExtendObject import *
class Program:
@staticmethod
def main():
(
f_(10)
.buy()
.buy()
.puts()
.gets("> ")
.insertStr("echo> ")
.puts()
.gets()
)
if __name__=="__main__":
Program.main()
class o_:
def put(self):
print(self,end="")
return i_(0)
def puts(self):
print(self)
return i_(0)
class s_(str,o_):
def insertStr(self,s):
return s_(s+self)
class n_(o_):
def buy(self):
return f_(self*2)
class f_(float,n_):
pass
class i_(int,n_):
def gets(self,myPrompt=None):
return s_(
input()
if myPrompt is None else
input(myPrompt)
)
前回同様にプリミティブ型を継承した拡張型を使用。
値を返す時は必ず拡張型のコンストラクタを使用することだけ注意した。
ここからは余り関係ないことだけど、setup.pyでプロジェクトを作成したとき、
from ExtendObject import *
じゃなくて、
from .ExtendObject import *
としなきゃいけなかったのはなかなかハマってしまった。
以下、おまけ。
Ruby
class ExtendObject
private
module ObjectChain
def put
print self
return 0
end
def _puts
puts self
return 0
end
end
module StringChain
def insertStr(s)
return s+self
end
end
module NumericChain
def buy
return self*2
end
end
module IntegerChain
def _gets(myPrompt="")
print myPrompt
return gets
end
end
def self.exclude(obj,mod)
mod.instance_methods(false).each{|v|
obj.class_eval{undef_method v}
}
end
public
def self.open
Object.include ObjectChain
String.include StringChain
Numeric.include NumericChain
Integer.include IntegerChain
end
def self.close
exclude Object,ObjectChain
exclude String,StringChain
exclude Numeric,NumericChain
exclude Integer,IntegerChain
end
def self.using(fn)
open
fn.call
close
end
end
Class.new{
def initialize
ExtendObject.using lambda{
10
.buy
.buy
._puts
._gets("> ")
.insertStr("echo> ")
._puts
._gets
}
end
}.new()
クラスベースかプロトタイプベースかの違いはあるけどアプローチはJavaScriptと一緒。
それゆえにJavaScriptと同様の懸念がありそう。
追記:
コメントに@raccyさんよりご意見頂きました。
Rubyにはrefineという機能があり、これを使うことで安全にオブジェクトの拡張ができるそうです。
Clojure
(defn put[self]
(print self)
0
)
(defn puts[self]
(println self)
0
)
(defn insertStr[self,s]
(str s self)
)
(defn buy[self]
(* self 2)
)
(defn gets
([self]
(read-line)
)
([self s]
(print s)
(read-line)
)
)
((fn[]
(->
10
(buy)
(buy)
(puts)
(gets "> ")
(insertStr "echo> ")
(puts)
(gets)
)
))
Lisp系言語でチェーンはどうするのかなぁと思っていたけどこんな解決策があるんだね。
面白い。こういう機能がデフォルトで組み込まれているのもCommonLispやSchemeに比べて新しい言語ならではって感じ。
以上、ここまで。