├── README.md
├── func.rb
└── interpreter.rb
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # J-uby - Ruby with J-like extensions
4 |
5 | J-uby aims to augment how Ruby programming with Symbols and Procs works by monkeypatching the aforementioned classes.
6 |
7 | Firstly, Symbols are now callable without first calling `.to_proc` on them. Procs have also gained many more operators.
8 |
9 | ## Tacit programming
10 |
11 | Tacit programming is manipulating functions to create other functions without specifying their arguments. A basic example would be a function that adds one to its argument. In Ruby, this would be done with a lambda: `->(x){1+x}`. Using the equality `(:sym & x).(y) == x.sym(y)`, we can simplify this lambda to `:+ & 1`.
12 |
13 | Ruby lambdas or expressions can be converted to tacit J-uby code using the following equalities:
14 |
15 | ```ruby
16 | sym.(*args) == sym.to_proc.(*args)
17 |
18 | (F | G).(*args) == G.(F.(*args))
19 | (F & x).(*args) == F.(x, *args)
20 | (~F).(x) == F.(x,x) # or as many x's as F takes
21 | (~F).(*args) == F.(*args.reverse)
22 | (F + G).(*args) == F.(*args, &G)
23 |
24 | F ^ x == F.(x)
25 |
26 | F << x == F.(*x)
27 | F.>>(*x) == F.(x)
28 | F.-(x,y) == F.(x).(y)
29 | +F <=> :<< & F
30 |
31 | F =~ x == (F.(x) == x)
32 |
33 | F / x == x.inject(&F)
34 | F * x == x.map(&F)
35 |
36 | F % [G] == F.(&G)
37 |
38 | (F**G).(*args) == G.(*args.map(&F))
39 |
40 | (F % G).(x) == F.(x, G.(x))
41 | (F % G).(x,y) == F.(x, G.(y))
42 |
43 | (F % [G,H]).(x) == F.(G.(x), H.(x))
44 | (F % [G,H]).(x,y) == F.(G.(x), H.(y)) # if G and H accept one argument
45 | (F % [G,H]).(x,y) == F.(G.(x,y), H.(x,y)) # if G and H accept 2 arguments
46 | ```
47 |
48 | ### Iteration operators
49 | `(F+init).(n)` starts an array with init, then applies `F` to the last `init.length` entries `n` times
50 |
51 | E.g. `fibonacci = :+ + [0,1]`
52 |
53 |
54 |
55 |
56 | `(F**n).(x)` iterates `F` on `x` `n` times.
57 |
58 |
59 |
60 |
61 | `F !~ x` iterates `F` on `x` until `x == F.(x)`
62 |
63 | ### Miscellaneous
64 | `-:symbol` returns the global method by that name. (e.g. `(-:puts).("hi")` prints "hi")
65 |
66 | `-array` with one argument applies it to the procs in the array. E.g., `-[:+ & 1, :* & 2] ^ 4 == [5, 8]`.
67 |
68 | `-array` with `array.length` arguments applies each proc to its corresponding argument. E.g., `(-[:floor, :ceil]).(1.9, 2.1) == [1,3]`
69 |
70 | `n.-` is now the same as `n.-@` to save a byte; useful when using a symbol such as `:-|(...)`.
71 |
72 | `_` is the identity function; for any object `o`, `_[o] == o`.
73 |
74 | `F.& == F.to_proc` for any proc `F`.
75 |
76 | `D^F` is a version of `F` that can only be used dyadically
77 |
78 | `M^F` is a version of `F` that can only be used monadically
79 |
80 | ### Aliases
81 | * `+some_array == some_array.+ == some_array.length`
82 | * `+some_string == some_string.+ == some_string.length`
83 | * `some_number.- == -some_number`
84 | * `some_number.| == some_number.abs`
85 | * `Z[any_object] == any_object.to_i`
86 | * `Q[any_object] == any_object.to_f`
87 | * `S[any_object] == any_object.to_s`
88 | * `A[any_object] == any_object.to_a`
89 | * `H[any_objec] == Hash[any_object]`
90 | * `_[any_object] == any_object`
91 | * `int_a !~ int_b == a..b`
92 | * `some_int.+ == 1..some_int`
93 | * `some_int.* == 0...some_int`
94 | * `some_int.to_a == some_int.*`
95 | * `some_string.to_a == some_string.each_char.to_a`
96 | * `~some_string == some_string.reverse`
97 | * `~some_array == some_array.reverse`
98 |
99 | # Examples
100 |
101 | ## Join Array with Commas
102 |
103 | ```ruby
104 | ~:*&?,
105 |
106 | (~ :*) & ',' # more readable
107 | ->(a){ (~ :*).(',', a) } # turn `&` into explicit lambda
108 | ->(a){ :*.(a, ',') } # `(~F).(x,y) == F.(y,x)`
109 | ->(a){ a.*(',') } # turn symbol call into explicit method call
110 | ->(a){ a.join(',') } # Array#* is an alias for Array#join
111 | ```
112 | ## Average of an Array
113 | ```ruby
114 | :/ % [:/ & :+, :size]
115 |
116 | ->(a){ :/.((:/ & :+).(a), :size.(a)) } # expand fork to lambda
117 | ->(a){ (:+ / a) / a.size } # transform `.call`s on procs to method accesses
118 | ->(a){ a.reduce(:+) / a.size } # expand `F / x` to `x.reduce(&F)`
119 | ```
120 |
121 | ## Haskell-Style `foldr` from the existing `/`
122 | *Note: as this one is especially complicated, some intermediate steps are omitted*
123 | ```ruby
124 | :~|:& &:/|:|&:reverse
125 |
126 | (:~ | (:& & :/)) | (:| & :reverse) # readable
127 | ->(f){ (:| & :reverse).((:~ | (:& & :/)).(f)) } # transform to lambda
128 | ->(f){ :reverse | (:/ & ~f) } # reduce
129 | ->(f){ ->(a){ (:/ & ~f).(:reverse.(a)) } } # expand `|` into curried lambda
130 | ->(f){ ->(a){ ~f / a.reverse } } # simplify `.call`s
131 | ```
132 |
133 |
134 | ## Check if array is all even
135 |
136 | ```ruby
137 | :* &:even?|:all?
138 |
139 | (:* & :even?) | :all? # readable
140 | ->(a){ :all?.((:* & :even?).(a))} # expand | to lambda
141 | ->(a){ (:even? * a).all? } # simplify explicit symbol calls
142 | ->(a){ a.map(&:even?).all? } # replace Proc#* with Array#map
143 | ```
144 | ### Alternative without `map`
145 |
146 | ```ruby
147 | :all?.& &:even?
148 |
149 | ->(a){ a.all?(&:even?) }
150 | ```
151 |
--------------------------------------------------------------------------------
/func.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 | require 'optparse'
3 | $main = self
4 |
5 | def on(sym)
6 | x = $options[sym] and yield x
7 | end
8 |
9 | def main()
10 | $options = options = {eval?: true}
11 | OptionParser.new do |opts|
12 | opts.separator ''
13 |
14 | opts.on("-e [CODE]") do |code|
15 | options[:eval] = code
16 | end
17 |
18 | opts.on("-i") do # read from STDIN instead of ARGV
19 | options[:stdin?] = true
20 | end
21 |
22 | opts.on("-l") do # literal input (no eval)
23 | options[:eval?] = false
24 | end
25 |
26 | opts.on("-g") do # greedy
27 | options[:greedy?] = true
28 | end
29 |
30 | options[:args] = opts.order(*ARGV)
31 | end
32 |
33 | on :stdin? do
34 | options[:args] = []
35 | options[:args] << $_.chomp! while (print '> '; STDIN.gets)
36 | end
37 |
38 |
39 | file = options[:args].shift unless options[:stdin?] || options[:eval]
40 |
41 | on :eval? do
42 | options[:args].map! &-:eval
43 | end
44 |
45 | unless options[:stdin?] || options[:eval]
46 | result = eval File.read(file)
47 | p result.(*options[:args]) if options[:args].length > 0
48 | end
49 |
50 | on :greedy? do
51 | options[:args] = [options[:args].join(options[:stdin?] ? "\n" : " ")]
52 | end
53 |
54 | on :eval do |code|
55 | result = eval code
56 | if options[:args].length > 0
57 | p result.(*options[:args])
58 | elsif result.respond_to?(:call)
59 | p result.()
60 | else
61 | p result
62 | end
63 | end
64 |
65 | end
66 |
67 | module Func
68 |
69 | def | (other) # compose
70 | ->(*args, &block){ other.call(call(*args, &block)) }
71 | end
72 |
73 | def ** (n)
74 | ->(x){
75 | n.times do
76 | x = call(x)
77 | end
78 | x
79 | }
80 | end
81 |
82 | def ^ (*args, &block)
83 | call(*args, &block)
84 | end
85 | alias [] ^
86 |
87 | def & (*a, &b) # curry
88 | b ? ->(*args){ call(*a, *args, &b) } : a.length == 0 ? to_proc : ->(*args){ call(*a, *args) }
89 | end
90 |
91 | def - (*args)
92 | :^ / [self, *args]
93 | end
94 |
95 | def / (x) # fold
96 | x.inject(&self)
97 | end
98 |
99 | def * (x) # map
100 | if x.is_a?(Enumerable)
101 | x.map(&self)
102 | else
103 | ->(*args){
104 | x.(*args.map(&self))
105 | }
106 | end
107 | end
108 |
109 | def ~ # rev arguments
110 | ->(*args, &block){
111 | if args.size == 1
112 | i = 0
113 | loop do
114 | begin
115 | return call(*[args[0]]*i)
116 | rescue ArgumentError
117 | i += 1
118 | end
119 | end
120 | else
121 | call(*args.reverse!, &block)
122 | end
123 | }
124 | end
125 |
126 | def << (args, &block) # splat
127 | call(*args, &block)
128 | end
129 |
130 | def >> (*args, &block) # unsplat
131 | block ? call(args, block) : call(args)
132 | end
133 |
134 | def fork(u,v)
135 | ->(*args){
136 | if args.length == 2
137 | fork2(u, v, args[0], args[1])
138 | else
139 | fork1(u, v, args[0])
140 | end
141 | }
142 | end
143 |
144 | def fork2(u,v, x,y)
145 | call(u.call(x,y), v.call(x,y))
146 | rescue ArgumentError
147 | call(u.call(x), v.call(y))
148 | end
149 |
150 | def fork1(u,v, x)
151 | call(u.call(x), v.call(x))
152 | end
153 |
154 | def hook(u)
155 | ->(*args){
156 | if args.length == 2
157 | hook2(u, *args)
158 | else
159 | hook1(u, *args)
160 | end
161 | }
162 | end
163 |
164 | def hook1(u, x)
165 | call(x, u.call(x))
166 | end
167 |
168 | def hook2(u, x,y)
169 | call(x, u.call(y))
170 | end
171 |
172 | def train (arg)
173 | if arg.is_a?(Array)
174 | if arg.length == 2
175 | fork(*arg)
176 | elsif arg.length == 1
177 | self.(&arg[0])
178 | end
179 | else
180 | hook(arg)
181 | end
182 | end
183 | alias % train
184 |
185 |
186 | def !~ (x) # iterate until stable
187 | loop do
188 | last = x
189 | x = call(x)
190 | break if x == last
191 | end
192 | x
193 | end
194 |
195 | def =~ (x)
196 | call(x) == x
197 | end
198 |
199 | def + (init)
200 | if init.is_a?(Array)
201 | len = 1-init.length
202 | ->(n){
203 | out = init.dup
204 | (n+len).times do
205 | out << call(*out)
206 | out.shift()
207 | end
208 | out.last
209 | }
210 | else
211 | ->(*args){
212 | call(*args, &init)
213 | }
214 | end
215 | end
216 |
217 | def D
218 | ->(x,y){call(x,y)}
219 | end
220 |
221 | def M
222 | ->(x){call(x)}
223 | end
224 |
225 | def +@
226 | :<< & self
227 | end
228 | end
229 |
230 |
231 | class Symbol
232 |
233 | include Func
234 |
235 | def call(*args, &block)
236 | to_proc.call(*args, &block)
237 | end
238 |
239 | alias [] call
240 |
241 | def =~ (other)
242 | to_proc =~ other
243 | end
244 |
245 | def -@
246 | $main.method(self)
247 | end
248 |
249 | end
250 |
251 | class Proc
252 | include Func
253 | end
254 |
255 | class Method
256 | include Func
257 | end
258 |
259 | class Array
260 |
261 |
262 | def -@
263 | ->(*args){
264 | if args.length == length
265 | zip(args).map { |f, x| f ^ x }
266 | elsif args.length == 1
267 | map { |f| f[args[0]] }
268 | else
269 | raise 'length error'
270 | end
271 | }
272 | end
273 |
274 | alias _or |
275 | def | (x)
276 | return _or(x) if x.is_a?(Array)
277 | rotate(x)
278 | end
279 |
280 | alias +@ length
281 |
282 | alias ~ reverse
283 | end
284 |
285 | class String
286 |
287 | alias +@ length
288 |
289 | def to_a; each_char.to_a end
290 |
291 | alias ~ reverse
292 | end
293 |
294 | class Fixnum
295 | alias _old_or |
296 | def | (x=nil)
297 | x ? _old_or(x) : abs
298 | end
299 |
300 | def !~ (other)
301 | Array(self..other)
302 | end
303 |
304 | alias _old_plus +
305 | def + (other=nil)
306 | if other.nil?
307 | Array(1..self)
308 | else
309 | _old_plus (other)
310 | end
311 | end
312 |
313 |
314 |
315 | alias _old_mul *
316 | def * (other=nil)
317 | if other.nil?
318 | Array(0...self)
319 | else
320 | _old_mul(other)
321 | end
322 | end
323 |
324 | alias to_a *
325 | end
326 |
327 | def $main.__ # pair
328 | ->(x,y){ [x,y] }
329 | end
330 |
331 | Q = ->(x){ x.to_f }
332 | Z = ->(x){ x.to_i }
333 | S = ->(x){ x.to_s }
334 | A = ->(x){ x.to_a }
335 | H = ->(x){ Hash[x] }
336 | I = _ = ->(x){ x }
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 | main() if $0 == __FILE__
355 |
--------------------------------------------------------------------------------
/interpreter.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/ruby
2 | exec 'ruby -r ./func.rb ' + ARGV.join(' ')
3 |
--------------------------------------------------------------------------------