1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

組込型にメソッドチェーンっぽいものを繋げてみるその2(D, C#, VB.Net, JavaScript, Python3 + Clojure, Ruby)

Last updated at Posted at 2017-10-28

これの続き。

概要

前回で組み込み型にメソッドチェーンを組み込むことができることを確認した。
そこで今回はどこまで何をチェーンできるのか試してみた。
また、グローバルを汚染することになる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言語

Program.d
import ExtendObject;
mixin ExtendObject;

void main(){
	10
	.buy
	.buy
	.puts
	.gets("> ")
	.insertStr("echo> ")
	.puts
	.gets;
}
ExtendObject
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#

Program.cs
using ExtendObject;

static class Program{
	static void Main(){
		10
		.buy()
		.buy()
		.puts()
		.gets("> ")
		.insertStr("echo> ")
		.puts()
		.gets();
	}
}
ExtendObject
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

Program
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
ExtendObject
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)

Program.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());
		});
	}
};
ExtendObject
"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

Program.py
from ExtendObject import *

class Program:
	@staticmethod
	def main():
		(
			f_(10)
			.buy()
			.buy()
			.puts()
			.gets("> ")
			.insertStr("echo> ")
			.puts()
			.gets()
		)

if __name__=="__main__":
	Program.main()
ExtendObject.py
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に比べて新しい言語ならではって感じ。

以上、ここまで。

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?