“ruby,一款好用的 esolang”
$><<(""<<~-~-~-~-~-~-~-~-(-~-~-~-~-~([]<=>[])<<-~-~-~-~([]<=>[]))<<-~-~-~-~-~(-~
-~-~([]<=>[])<<-~-~-~-~-~([]<=>[]))<<~-~-~-~-(-~-~-~-~-~-~-~([]<=>[])<<-~-~-~-~(
[]<=>[]))<<~-~-~-~-(-~-~-~-~-~-~-~([]<=>[])<<-~-~-~-~([]<=>[]))<<~-(-~-~-~-~-~-~
-~([]<=>[])<<-~-~-~-~([]<=>[]))<<~-~-~-~-(-~-~-~([]<=>[])<<-~-~-~-~([]<=>[]))<<(
([[]]<=>[])<<-~-~-~-~-~([]<=>[]))<<-~-~-~-~-~-~-~(-~-~-~-~-~([]<=>[])<<-~-~-~-~(
[]<=>[]))<<~-(-~-~-~-~-~-~-~([]<=>[])<<-~-~-~-~([]<=>[]))<<-~-~(-~-~-~-~-~-~-~([
]<=>[])<<-~-~-~-~([]<=>[]))<<~-~-~-~-(-~-~-~-~-~-~-~([]<=>[])<<-~-~-~-~([]<=>[])
)<<-~-~-~-~(-~-~-~([]<=>[])<<-~-~-~-~-~([]<=>[]))<<-~(([[]]<=>[])<<-~-~-~-~-~([
]<=>[]))<<-~-~-~-~-~-~-~-~-~-~([]<=>[]))
Hello, World!
Ruby 语言有很多有趣的特性:包含循环引用的数据之间可以比较是否相等,重载运算符就是定义以运算符为名称的方法,可以用 字符串 << 字符串或字符编码
向字符串追加内容,整数可以像比特组成的数组一样被索引和切片,“全局函数”实际上是 Kernel
模块的私有方法,Object
的 display
方法等价于 print
函数……
Esoteric Ruby
前些天,热爱 Esolang 的群友“预防”尝试写出不包含字母的 Ruby 程序。首先他想到了 String#<<
。
("" << 72 << 101 << 108 << 108 << 111 << 44 << 32
<< 87 << 111 << 114 << 108 << 100 << 33 << 10).display
(如果启用了 frozen_string_literal
,需将 ""
改为 +""
:一元正号运算符可以复制冻结的字符串,得到一个未冻结的副本。)
如何不使用字母调用 Object#display
呢?试试 Symbol
。
"" << 100 << 105 << 115 << 112 << 108 << 97 << 121 # => "display"
:"#{"" << 100 << 105 << 115 << 112 << 108 << 97 << 121}" # => :display
然而,如何通过方法名的 Symbol
调用方法?正常情况下……
"Hello, World!\n".send(:display)
"Hello, World!\n".method(:display).call
"Hello, World!\n".method(:display).()
"Hello, World!\n".method(:display)[]
String.instance_method(:display).bind("Hello, World!\n").call
String.instance_method(:display).bind_call("Hello, World!\n")
lambda(&:display).call("Hello, World!\n")
lambda(&:display).("Hello, World!\n")
lambda(&:display)["Hello, World!\n"]
不管用怎样刁钻的方式调用方法,都会引入新的方法/函数调用。但后来我发现:
def greet(&proc)
proc["Hello, World!\n"]
end
greet(&:display) # 输出 Hello, World!
通过定义一个接受块的函数,可以在不调用方法的情况下把 Symbol
转换成 Proc
(lambda
)。而这个 greet
函数可以写成箭头函数:
greet = -> (&proc) { proc["Hello, World!\n"] }
greet.(&:display) # 输出 Hello, World!
把箭头函数内联,改一下变量名,就得到:
(-> (&_) { _["Hello, World!\n"] }).(&:display) # 输出 Hello, World!
再把字符串和 Symbol 替换掉:
(->(&_){_[""<<72<<101<<108<<108<<111<<44<<32<<87<<111<<114<<108<<100<<33<<10]})
.(&:"#{""<<100<<105<<115<<112<<108<<97<<121}")
我们就得到了没有字母的 Hello World 程序!
然而我们随即发现全局变量 $>
相当于常量 STDOUT
。
STDOUT << "Hello, World!\n"
$> << "Hello, World!\n"
于是……
$> << (""<<72<<101<<108<<108<<111<<44<<32<<87<<111<<114<<108<<100<<33<<10)
根本不需要什么箭头函数。
RBFxck
于是我们打算提高难度:不能用数字。
如何用符号获得整数?可以用比较运算符 <=>
。数组 []
等于 []
,[[]]
大于 []
。于是:
[] <=> [] # => 0
[[]] <=> [] # => 1
[] <=> [[]] # => -1
用左移操作可以获得更多整数:
([[]] <=> []) << ([[]] <=> []) # 1 << 1 => 2
([[]] <=> []) << ([[]] <=> []) | ([[]] <=> []) # 1 << 1 | 1 => 3
([[]] <=> []) << (([[]] <=> []) << ([[]] <=> [])) # 1 << (1<<1) => 4
不过后来我们发现利用一元负号和按位取反运算更方便:
~1 # => -2
-~1 # => 2
~-~1 # => -3
-~-~1 # => 3
-~([[]] <=> []) # => 2
-~-~([[]] <=> []) # => 3
-~-~-~([[]] <=> []) # => 4
有了上次的经验,经过反复调整,我完成了这样一个整数/字符串编码程序:
def shortest(*a)
a.min_by(&:length)
end
def rbfuck_int(n)
return "([[]]<=>[])" if n == 1
return "([]<=>[[]])" if n == -1
return shortest("-#{rbfuck_int(-n)}".sub(/^--/, ""), "~#{rbfuck_int(~n)}".sub(/^~~/, "")) if n.negative?
return "#{"-~" * n}([]<=>[])" if n <= 13
threshold = 8
bitl = threshold.bit_length
if n[0, bitl] >= threshold
bitl += 1 while n.anybits?(1 << bitl)
"#{"~-" * (1 + (~n)[0, bitl])}(#{rbfuck_int((n >> bitl) + 1)}<<#{rbfuck_int(bitl)})"
else
bitl += 1 until n.anybits?(1 << bitl)
"#{"-~" * n[0, bitl]}(#{rbfuck_int(n >> bitl)}<<#{rbfuck_int(bitl)})"
end
end
def rbfuck_str(str)
'""' + str.each_codepoint.map { |c| "<<#{rbfuck_int(c)}" }.join
end
code = "$><<(#{rbfuck_str("Hello, World!\n")})"
puts code
eval code
此程序便产生了本文开头的代码和输出。
why
预防也写了他自己的版本,记录如下。我没太看懂
$_={(:!)=>{}}
$__=[[]]<=>[]
$_[:-]=(->(_,__,*___,**____){(->(&__){__.(_,*___,**____)}).(&:"#{__}")})
$_[:%]=-~((-~-~$__)**-~$__)
$><<(""<<
(~-~-$_[:%])**-~$__ + -~-~-~-~-~-~-~$__<<
($_[:%])**-~$__ + $__<<
($_[:%])**-~$__ + -~-~-~-~-~-~-~$__<<
($_[:%])**-~$__ + -~-~-~-~-~-~-~$__<<
(-~$_[:%])**-~$__ + ~-~-~-~-~-~-~-~-~-~-~-$__<<
(~-~-~-$_[:%])**-~$__ + ~-~-~-~-~-~-$__<<
(~-~-~-~-$_[:%])**-~$__ + ~-~-~-~-~-$__<<
(-~$_[:%])**-~$__ + ~-~-~-$__<<
(-~$_[:%])**-~$__ + ~-~-~-~-~-~-~-~-~-~-~-$__<<
(-~$_[:%])**-~$__ + ~-~-~-~-~-~-~-~-$__<<
($_[:%])**-~$__ + -~-~-~-~-~-~-~$__<<
($_[:%])**-~$__ + ~-$__<<
(~-~-~-~-$_[:%])**-~$__ + ~-~-~-~-$__<<
(~-~-~-~-$_[:%])**-~$__ + ~-~-~-~-~-$__
)
调试的过程中,我发现较旧版本的 Ruby 不支持对整数进行切片,遂写了如下 polyfill:
class Integer
@@native_aref = Integer.instance_method(:[])
@@omit = BasicObject.new
def [](p1, p2 = @@omit)
if @@omit.equal?(p2)
if p1.is_a?(Range)
if p1.begin.nil?
return 0 if self.nobits?(~(-2 << p1.end))
raise ArgumentError, "The beginless range for Integer#[] results in infinity"
end
return self >> p1.begin if p1.end.nil? or p1.end < p1.begin
return self[p1.begin, p1.size]
end
return @@native_aref.bind(self).(p1)
end
# 为了在类型不对时报个错我容易吗(?
@@native_aref.bind(self).(p1) unless p1.respond_to?(:to_int)
@@native_aref.bind(self).(p2) unless p2.respond_to?(:to_int)
self >> p1.to_int & ~(-1 << p2.to_int)
end
end
fin