├── 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 | --------------------------------------------------------------------------------