galaaz 0.5.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/LICENSE +0 -0
  4. data/README.md +1416 -667
  5. data/Rakefile +68 -41
  6. data/bin/galaaz-bootstrap +137 -0
  7. data/bin/galaaz-jruby +11 -0
  8. data/bin/galaaz-ruby +16 -0
  9. data/bin/galaaz_jruby_env.inc.sh +6 -0
  10. data/bin/galaaz_ruby_env.inc.sh +36 -0
  11. data/bin/gbookdown +63 -0
  12. data/bin/gknit +83 -13
  13. data/bin/gknit-draft.rb +0 -0
  14. data/bin/gstudio +5 -3
  15. data/bin/gstudio_irb.rb +0 -0
  16. data/bin/gstudio_pry.rb +0 -0
  17. data/bin/install-tinytex +6 -0
  18. data/bin/run_all_rspec +44 -0
  19. data/bin/run_example +17 -0
  20. data/bin/run_old_rspec +20 -0
  21. data/bin/run_rspec +24 -0
  22. data/bin/run_rspec_subset +38 -0
  23. data/bin/run_slow_rspec +20 -0
  24. data/blogs/R-on-Rails-Planning-Document.md +940 -0
  25. data/blogs/README.md +100 -0
  26. data/blogs/galaaz_ggplot/galaaz_ggplot.Rmd +38 -66
  27. data/blogs/galaaz_ggplot/galaaz_ggplot.log +754 -0
  28. data/blogs/galaaz_ggplot/galaaz_ggplot.md +115 -155
  29. data/blogs/galaaz_ggplot/galaaz_ggplot.tex +607 -0
  30. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-gfm/midwest_rb.png +0 -0
  31. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-gfm/scatter_plot_rb.png +0 -0
  32. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/midwest_rb.png +0 -0
  33. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/scatter_plot_rb.png +0 -0
  34. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/midwest_rb.png +0 -0
  35. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/scatter_plot_rb.png +0 -0
  36. data/blogs/galaaz_ggplot/midwest.Rmd +3 -3
  37. data/blogs/galaaz_ggplot/midwest_external_png +0 -0
  38. data/blogs/gknit/gknit.Rmd +47 -52
  39. data/blogs/gknit/gknit.md +1430 -0
  40. data/blogs/gknit/gknit_files/figure-gfm/bubble-1.png +0 -0
  41. data/blogs/gknit/gknit_files/figure-gfm/diverging_bar.png +0 -0
  42. data/blogs/gknit/gknit_files/figure-html/bubble-1.png +0 -0
  43. data/blogs/gknit/gknit_files/figure-html/diverging_bar.png +0 -0
  44. data/blogs/gknit/lst.rds +0 -0
  45. data/blogs/gknit/model.rb +1 -1
  46. data/blogs/gknit/stats.bib +0 -0
  47. data/blogs/manual/include_model_local_repro.Rmd +14 -0
  48. data/blogs/manual/include_model_local_repro.md +75 -0
  49. data/blogs/manual/lst.rds +0 -0
  50. data/blogs/manual/manual.Rmd +855 -239
  51. data/blogs/manual/manual.log +1786 -0
  52. data/blogs/manual/manual.md +1416 -667
  53. data/blogs/manual/manual.tex +1883 -1161
  54. data/blogs/manual/manual_files/figure-html/bubble-1.png +0 -0
  55. data/blogs/manual/manual_files/figure-html/diverging_bar.png +0 -0
  56. data/blogs/manual/manual_files/figure-latex/bubble-1.png +0 -0
  57. data/blogs/manual/model.rb +1 -1
  58. data/blogs/nse_dplyr/nse_dplyr.Rmd +84 -111
  59. data/blogs/nse_dplyr/nse_dplyr.log +928 -0
  60. data/blogs/nse_dplyr/nse_dplyr.md +198 -229
  61. data/blogs/oh_my/not_so.rb +0 -0
  62. data/blogs/oh_my/oh_my.Rmd +1234 -25
  63. data/blogs/oh_my/oh_my.log +804 -0
  64. data/blogs/oh_my/oh_my.md +1663 -86
  65. data/blogs/oh_my/oh_my.tex +821 -0
  66. data/blogs/oh_my/old.Rmd +15 -14
  67. data/blogs/ruby_plot/ruby_plot.Rmd +58 -82
  68. data/blogs/ruby_plot/ruby_plot.log +885 -0
  69. data/blogs/ruby_plot/ruby_plot.md +71 -102
  70. data/blogs/ruby_plot/ruby_plot.tex +940 -0
  71. data/blogs/ruby_plot/ruby_plot_files/figure-gfm/dose_len.png +0 -0
  72. data/blogs/ruby_plot/ruby_plot_files/figure-gfm/facet_by_delivery.png +0 -0
  73. data/blogs/ruby_plot/ruby_plot_files/figure-gfm/facet_by_dose.png +0 -0
  74. data/blogs/ruby_plot/ruby_plot_files/figure-gfm/facets_by_delivery_color.png +0 -0
  75. data/blogs/ruby_plot/ruby_plot_files/figure-gfm/facets_by_delivery_color2.png +0 -0
  76. data/blogs/ruby_plot/ruby_plot_files/figure-gfm/facets_with_decorations.png +0 -0
  77. data/blogs/ruby_plot/ruby_plot_files/figure-gfm/facets_with_jitter.png +0 -0
  78. data/blogs/ruby_plot/ruby_plot_files/figure-gfm/facets_with_points.png +0 -0
  79. data/blogs/ruby_plot/ruby_plot_files/figure-gfm/final_box_plot.png +0 -0
  80. data/blogs/ruby_plot/ruby_plot_files/figure-gfm/final_violin_plot.png +0 -0
  81. data/blogs/ruby_plot/ruby_plot_files/figure-gfm/violin_with_jitter.png +0 -0
  82. data/blogs/ruby_plot/ruby_plot_files/figure-html/dose_len.png +0 -0
  83. data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_delivery.png +0 -0
  84. data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_dose.png +0 -0
  85. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color.png +0 -0
  86. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color2.png +0 -0
  87. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_decorations.png +0 -0
  88. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_jitter.png +0 -0
  89. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_points.png +0 -0
  90. data/blogs/ruby_plot/ruby_plot_files/figure-html/final_box_plot.png +0 -0
  91. data/blogs/ruby_plot/ruby_plot_files/figure-html/final_violin_plot.png +0 -0
  92. data/blogs/ruby_plot/ruby_plot_files/figure-html/violin_with_jitter.png +0 -0
  93. data/blogs/ruby_plot/ruby_plot_files/figure-latex/dose_len.png +0 -0
  94. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_delivery.png +0 -0
  95. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_dose.png +0 -0
  96. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color.png +0 -0
  97. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color2.png +0 -0
  98. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_decorations.png +0 -0
  99. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_jitter.png +0 -0
  100. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_points.png +0 -0
  101. data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_box_plot.png +0 -0
  102. data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_violin_plot.png +0 -0
  103. data/blogs/ruby_plot/ruby_plot_files/figure-latex/violin_with_jitter.png +0 -0
  104. data/blogs/test/test.Rmd +14 -0
  105. data/blogs/test/test.md +10 -0
  106. data/examples/50Plots_MasterList/Images/midwest-scatterplot.PNG +0 -0
  107. data/examples/50Plots_MasterList/ScatterPlot.rb +2 -1
  108. data/examples/50Plots_MasterList/scatter_plot.rb +1 -0
  109. data/examples/Bibliography/master.bib +0 -0
  110. data/examples/Bibliography/stats.bib +0 -0
  111. data/examples/R/calc.R +0 -0
  112. data/examples/R/java_interop.R +0 -0
  113. data/examples/bioconductor_deseq2_airway/Documentation/DESeq2-airway-walkthrough.md +56 -0
  114. data/examples/bioconductor_deseq2_airway/bench_galaaz_three_same_process.rb +54 -0
  115. data/examples/bioconductor_deseq2_airway/bench_r_three_same_process.R +34 -0
  116. data/examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb +34 -0
  117. data/examples/bioconductor_deseq2_airway/deseq2_airway_galaaz_optimized.rb +35 -0
  118. data/examples/bioconductor_deseq2_airway/deseq2_airway_minimal.R +30 -0
  119. data/examples/bioconductor_deseq2_airway/deseq2_airway_pipeline_for_bench.R +36 -0
  120. data/examples/islr/all.rb +14 -0
  121. data/examples/islr/ch2.spec.rb +38 -7
  122. data/examples/islr/ch3.spec.rb +12 -2
  123. data/examples/islr/ch3_boston.rb +28 -0
  124. data/examples/islr/ch3_multiple_regression.rb +1 -0
  125. data/examples/islr/ch6.spec.rb +25 -1
  126. data/examples/islr/x_y_rnorm.jpg +0 -0
  127. data/examples/latex_templates/Test-acm_article/acm_proc_article-sp.cls +0 -0
  128. data/examples/latex_templates/Test-acm_article/sigproc.bib +0 -0
  129. data/examples/latex_templates/Test-acs_article/acs-Test-acs_article.bib +0 -0
  130. data/examples/latex_templates/Test-acs_article/acs-my_output.bib +0 -0
  131. data/examples/latex_templates/Test-aea_article/BibFile.bib +0 -0
  132. data/examples/latex_templates/Test-aea_article/Test-aea_article.Rmd +0 -0
  133. data/examples/latex_templates/Test-aea_article/references.bib +0 -0
  134. data/examples/latex_templates/Test-amq_article/Test-amq_article.Rmd +0 -0
  135. data/examples/latex_templates/Test-amq_article/Test-amq_article.pdfsync +0 -0
  136. data/examples/latex_templates/Test-ieee_article/IEEEtran.bst +0 -0
  137. data/examples/latex_templates/Test-ieee_article/mybibfile.bib +0 -0
  138. data/examples/latex_templates/Test-rjournal_article/RJournal.sty +0 -0
  139. data/examples/latex_templates/Test-rjournal_article/RJreferences.bib +0 -0
  140. data/examples/latex_templates/Test-rjournal_article/Test-rjournal_article.Rmd +0 -0
  141. data/examples/misc/baseball.csv +0 -0
  142. data/examples/misc/ggplot.rb +5 -3
  143. data/examples/misc/moneyball.rb +1 -0
  144. data/examples/misc/subsetting.rb +1 -0
  145. data/examples/multithread_shards_to_r/shards_to_r.rb +68 -0
  146. data/examples/rmarkdown/svm-rmarkdown-anon-ms-example/svm-rmarkdown-anon-ms-example.Rmd +0 -0
  147. data/examples/rmarkdown/svm-rmarkdown-article-example/svm-rmarkdown-article-example.Rmd +0 -0
  148. data/examples/rmarkdown/svm-rmarkdown-beamer-example/svm-rmarkdown-beamer-example.Rmd +0 -0
  149. data/examples/rmarkdown/svm-rmarkdown-cv/svm-rmarkdown-cv.Rmd +0 -0
  150. data/examples/rmarkdown/svm-rmarkdown-syllabus-example/attend-grade-relationships.csv +0 -0
  151. data/examples/rmarkdown/svm-rmarkdown-syllabus-example/svm-rmarkdown-syllabus-example.Rmd +0 -0
  152. data/examples/rmarkdown/svm-xaringan-example/svm-xaringan-example.Rmd +0 -0
  153. data/examples/sthda_ggplot/README.md +0 -0
  154. data/examples/sthda_ggplot/RUN.md +41 -0
  155. data/examples/sthda_ggplot/all.rb +1 -0
  156. data/examples/sthda_ggplot/one_variable_continuous/density_gg.rb +1 -0
  157. data/examples/sthda_ggplot/one_variable_continuous/geom_area.rb +1 -0
  158. data/examples/sthda_ggplot/one_variable_continuous/geom_density.rb +3 -0
  159. data/examples/sthda_ggplot/one_variable_continuous/geom_dotplot.rb +1 -0
  160. data/examples/sthda_ggplot/one_variable_continuous/geom_freqpoly.rb +1 -0
  161. data/examples/sthda_ggplot/one_variable_continuous/geom_histogram.rb +1 -0
  162. data/examples/sthda_ggplot/one_variable_continuous/histogram_density.rb +1 -0
  163. data/examples/sthda_ggplot/one_variable_continuous/stat.rb +1 -0
  164. data/examples/sthda_ggplot/one_variable_discrete/bar.rb +1 -0
  165. data/examples/sthda_ggplot/qplots/box_violin_dot.rb +1 -0
  166. data/examples/sthda_ggplot/qplots/scatter_plots.rb +1 -0
  167. data/examples/sthda_ggplot/scatter_gg.rb +1 -0
  168. data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_bin2d.rb +1 -0
  169. data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_density2d.rb +1 -0
  170. data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_hex.rb +1 -0
  171. data/examples/sthda_ggplot/two_variables_cont_cont/geom_point.rb +1 -0
  172. data/examples/sthda_ggplot/two_variables_cont_cont/geom_smooth.rb +1 -0
  173. data/examples/sthda_ggplot/two_variables_cont_cont/misc.rb +1 -0
  174. data/examples/sthda_ggplot/two_variables_cont_function/geom_area.rb +5 -3
  175. data/examples/sthda_ggplot/two_variables_disc_cont/geom_bar.rb +1 -0
  176. data/examples/sthda_ggplot/two_variables_disc_cont/geom_boxplot.rb +1 -0
  177. data/examples/sthda_ggplot/two_variables_disc_cont/geom_dotplot.rb +1 -0
  178. data/examples/sthda_ggplot/two_variables_disc_cont/geom_jitter.rb +1 -0
  179. data/examples/sthda_ggplot/two_variables_disc_cont/geom_line.rb +1 -0
  180. data/examples/sthda_ggplot/two_variables_disc_cont/geom_violin.rb +1 -0
  181. data/examples/sthda_ggplot/two_variables_disc_disc/geom_jitter.rb +1 -0
  182. data/examples/sthda_ggplot/two_variables_error/geom_crossbar.rb +1 -0
  183. data/ext/new_bridge/Makefile +46 -0
  184. data/ext/new_bridge/galaaz_gatekeeper_phase0.cpp +12 -0
  185. data/ext/new_bridge/galaaz_gatekeeper_phase1.cpp +1639 -0
  186. data/lib/R_interface/galaaz_device.R +20 -0
  187. data/lib/R_interface/include_engine.R +109 -0
  188. data/lib/R_interface/new_bridge_adapter.rb +824 -0
  189. data/lib/R_interface/r.rb +177 -25
  190. data/lib/R_interface/r_arrow.rb +113 -0
  191. data/lib/R_interface/r_libs.R +3 -3
  192. data/lib/R_interface/r_methods.rb +13 -126
  193. data/lib/R_interface/r_module_s.rb +0 -0
  194. data/lib/R_interface/rbinary_operators.rb +20 -2
  195. data/lib/R_interface/rclosure.rb +5 -1
  196. data/lib/R_interface/rdata_frame.rb +34 -70
  197. data/lib/R_interface/rdevice.rb +125 -0
  198. data/lib/R_interface/rdevices.R +0 -0
  199. data/lib/R_interface/renvironment.rb +10 -4
  200. data/lib/R_interface/rexpression.rb +5 -1
  201. data/lib/R_interface/rindexed_object.rb +41 -13
  202. data/lib/R_interface/rlanguage.rb +20 -62
  203. data/lib/R_interface/rlist.rb +115 -25
  204. data/lib/R_interface/rlogical_operators.rb +0 -0
  205. data/lib/R_interface/rmatrix.rb +2 -11
  206. data/lib/R_interface/rmd_indexed_object.rb +5 -1
  207. data/lib/R_interface/robject.rb +348 -290
  208. data/lib/R_interface/rpkg.rb +0 -0
  209. data/lib/R_interface/rsupport.rb +609 -328
  210. data/lib/R_interface/rsupport_scope.rb +2 -1
  211. data/lib/R_interface/rsymbol.rb +50 -0
  212. data/lib/R_interface/ruby_callback.rb +2 -3
  213. data/lib/R_interface/ruby_extensions.rb +225 -175
  214. data/lib/R_interface/runary_operators.rb +0 -0
  215. data/lib/R_interface/rvector.rb +162 -31
  216. data/lib/galaaz.rb +0 -0
  217. data/lib/galaaz_jruby.rb +22 -0
  218. data/lib/galaaz_ruby.rb +34 -0
  219. data/lib/gknit/diagnostics.rb +50 -0
  220. data/lib/gknit/draft.rb +23 -17
  221. data/lib/gknit/include_engine.rb +15 -7
  222. data/lib/gknit/knitr_engine.rb +223 -74
  223. data/lib/gknit/rb_engine.rb +3 -3
  224. data/lib/gknit/ruby_engine.rb +0 -0
  225. data/lib/gknit.rb +1 -0
  226. data/lib/new_bridge/bootstrap/windows_bootstrap.rb +285 -0
  227. data/lib/new_bridge/envelope.rb +51 -0
  228. data/lib/new_bridge/eval_result.rb +26 -0
  229. data/lib/new_bridge/framing.rb +39 -0
  230. data/lib/new_bridge/instance_pool_client.rb +38 -0
  231. data/lib/new_bridge/r_instance_manager.rb +404 -0
  232. data/lib/new_bridge/session_client.rb +530 -0
  233. data/lib/new_bridge/tcp_framed.rb +44 -0
  234. data/lib/new_bridge.rb +9 -0
  235. data/lib/util/exec_ruby.rb +95 -20
  236. data/lib/util/inline_file.rb +35 -30
  237. data/new_bridge_specs/benchmark_phase5_5_unboxing_spec.rb +96 -0
  238. data/new_bridge_specs/eval_r_async_spec.rb +113 -0
  239. data/new_bridge_specs/integration_phase5_1_concurrent_spec.rb +50 -0
  240. data/new_bridge_specs/integration_phase5_1_eval_spec.rb +16 -0
  241. data/new_bridge_specs/integration_phase5_1_r_api_spec.rb +25 -0
  242. data/new_bridge_specs/integration_phase5_1_smoke_spec.rb +31 -0
  243. data/new_bridge_specs/integration_phase5_2_dataframe_unboxing_spec.rb +19 -0
  244. data/new_bridge_specs/integration_phase5_2_handle_eval_unboxing_spec.rb +25 -0
  245. data/new_bridge_specs/integration_phase5_3_callback_args_spec.rb +28 -0
  246. data/new_bridge_specs/integration_phase5_3_callback_error_spec.rb +22 -0
  247. data/new_bridge_specs/integration_phase5_3_callback_timeout_spec.rb +28 -0
  248. data/new_bridge_specs/integration_phase5_3_callbacks_smoke_spec.rb +22 -0
  249. data/new_bridge_specs/integration_phase5_3_edge_cases_spec.rb +52 -0
  250. data/new_bridge_specs/integration_phase5_3_nested_spec.rb +30 -0
  251. data/new_bridge_specs/integration_phase5_4_concurrent_sessions_spec.rb +53 -0
  252. data/new_bridge_specs/integration_phase5_4_nested_session_callbacks_spec.rb +49 -0
  253. data/new_bridge_specs/integration_phase5_4_session_routing_spec.rb +38 -0
  254. data/new_bridge_specs/integration_phase5_5_stress_concurrency_spec.rb +52 -0
  255. data/new_bridge_specs/integration_phase5_5_unbox_walk_spec.rb +46 -0
  256. data/new_bridge_specs/phase0_protocol_spec.rb +96 -0
  257. data/new_bridge_specs/phase1_req_ret_spec.rb +66 -0
  258. data/new_bridge_specs/phase2_multi_instance_spec.rb +67 -0
  259. data/new_bridge_specs/phase3_callbacks_spec.rb +71 -0
  260. data/new_bridge_specs/phase4_2_hardening_spec.rb +252 -0
  261. data/new_bridge_specs/phase4_3_r_instance_manager_spec.rb +85 -0
  262. data/new_bridge_specs/phase4_nested_callbacks_spec.rb +123 -0
  263. data/r_requires/ggplot.rb +0 -0
  264. data/r_requires/knitr.rb +0 -0
  265. data/specs/all.rb +15 -11
  266. data/specs/arrow_from_ruby_batches_spec.rb +50 -0
  267. data/specs/arrow_semantics_spec.rb +64 -0
  268. data/specs/bridge_concurrent_spec.rb +46 -0
  269. data/specs/bridge_nested_spec.rb +25 -0
  270. data/specs/dataframe_semantics_spec.rb +122 -0
  271. data/specs/dataframe_single_index_logical_filter_spec.rb +21 -0
  272. data/specs/dispatch_probe_cache_spec.rb +38 -0
  273. data/specs/dispatch_probe_error_class_fallback_spec.rb +20 -0
  274. data/specs/dispatch_probe_fallback_spec.rb +18 -0
  275. data/specs/environment_semantics_spec.rb +89 -0
  276. data/specs/field_access_spec.rb +31 -0
  277. data/specs/figures/bg.jpeg +0 -0
  278. data/specs/figures/bg.png +0 -0
  279. data/specs/figures/bg.svg +168 -57
  280. data/specs/figures/dose_len.png +0 -0
  281. data/specs/figures/no_args.jpeg +0 -0
  282. data/specs/figures/no_args.png +0 -0
  283. data/specs/figures/no_args.svg +168 -57
  284. data/specs/figures/width_height.jpeg +0 -0
  285. data/specs/figures/width_height.png +0 -0
  286. data/specs/figures/width_height_units1.jpeg +0 -0
  287. data/specs/figures/width_height_units1.png +0 -0
  288. data/specs/figures/width_height_units2.jpeg +0 -0
  289. data/specs/figures/width_height_units2.png +0 -0
  290. data/specs/formula_semantics_spec.rb +81 -0
  291. data/specs/galaaz_util_exec_ruby_spec.rb +85 -0
  292. data/specs/galaaz_util_inline_file_spec.rb +54 -0
  293. data/specs/gknit_cli_option_permutation_spec.rb +24 -0
  294. data/specs/gknit_include_engine_spec.rb +72 -0
  295. data/specs/gknit_install_timeout_report_spec.rb +69 -0
  296. data/specs/gknit_internal_error_report_spec.rb +57 -0
  297. data/specs/gknit_vector_map_output_spec.rb +59 -0
  298. data/specs/globalenv_guardrail_spec.rb +52 -0
  299. data/specs/language_expression_semantics_spec.rb +145 -0
  300. data/specs/list_semantics_spec.rb +111 -0
  301. data/specs/new_bridge_bulk_dataframe_transfer_spec.rb +44 -0
  302. data/specs/new_bridge_bulk_vector_transfer_spec.rb +73 -0
  303. data/specs/new_bridge_callback_timeout_spec.rb +69 -0
  304. data/specs/new_bridge_eval_r_fallback_spec.rb +55 -0
  305. data/specs/nil_null_spec.rb +42 -0
  306. data/specs/object_build_phase2_spec.rb +53 -0
  307. data/specs/phase1_callback_bridge_spec.rb +84 -0
  308. data/specs/phase2_gknit_generic_rendering_guardrail_spec.rb +46 -0
  309. data/specs/phase2_gknit_no_raw_code_leakage_spec.rb +43 -0
  310. data/specs/phase3_gknit_generic_graphics_capture_spec.rb +71 -0
  311. data/specs/plot_device_semantics_spec.rb +28 -0
  312. data/specs/plot_snapshot_semantics_spec.rb +58 -0
  313. data/specs/protocol_result_spec.rb +236 -0
  314. data/specs/r_batch_fail_fast_spec.rb +47 -0
  315. data/specs/r_bridge_bootstrap_spec.rb +11 -0
  316. data/specs/r_devices.spec.rb +1 -1
  317. data/specs/r_eval.spec.rb +16 -18
  318. data/specs/r_function.spec.rb +1 -1
  319. data/specs/r_instance_manager_spec.rb +285 -0
  320. data/specs/r_list_apply.spec.rb +15 -15
  321. data/specs/r_matrix.spec.rb +0 -0
  322. data/specs/r_nse.spec.rb +5 -5
  323. data/specs/r_object_send_dispatch_spec.rb +13 -0
  324. data/specs/r_vector_comparator_spec.rb +8 -0
  325. data/specs/r_vector_creation.spec.rb +0 -0
  326. data/specs/r_vector_functions.spec.rb +0 -0
  327. data/specs/r_vector_object.spec.rb +0 -0
  328. data/specs/r_vector_operators.spec.rb +0 -0
  329. data/specs/r_vector_structured_scalar_reads_spec.rb +35 -0
  330. data/specs/r_vector_subsetting.spec.rb +0 -0
  331. data/specs/range_helper_spec.rb +21 -0
  332. data/specs/rsupport_scope_spec.rb +28 -0
  333. data/specs/rsupport_var_name_thread_safety_spec.rb +24 -0
  334. data/specs/scalar_character_spec.rb +44 -0
  335. data/specs/scoped_symbol_dsl_refinement_spec.rb +40 -0
  336. data/specs/session_env_bridge_spec.rb +25 -0
  337. data/specs/simplecov_bootstrap_spec.rb +10 -0
  338. data/specs/spec_helper.rb +10 -0
  339. data/specs/tmp.rb +0 -0
  340. data/specs/unboxing_recursion_regression_spec.rb +30 -0
  341. data/specs/unboxing_spec.rb +49 -0
  342. data/specs/verify_callbacks.rb +42 -0
  343. data/sty/galaaz.sty +0 -0
  344. data/version.rb +1 -1
  345. metadata +219 -63
  346. data/blogs/galaaz_ggplot/galaaz_ggplot.html +0 -520
  347. data/blogs/galaaz_ggplot/galaaz_ggplot.pdf +0 -0
  348. data/blogs/galaaz_ggplot/midwest.html +0 -188
  349. data/blogs/gknit/gknit.html +0 -2266
  350. data/blogs/gknit/gknit.pdf +0 -0
  351. data/blogs/manual/manual.html +0 -4638
  352. data/blogs/manual/manual.pdf +0 -0
  353. data/blogs/manual/manual_files/figure-latex/diverging_bar.pdf +0 -0
  354. data/blogs/nse_dplyr/nse_dplyr.html +0 -878
  355. data/blogs/nse_dplyr/nse_dplyr.pdf +0 -0
  356. data/blogs/oh_my/oh_my.html +0 -568
  357. data/blogs/ruby_plot/ruby_plot.html +0 -544
  358. data/blogs/ruby_plot/ruby_plot.pdf +0 -0
  359. data/examples/latex_templates/Test-acs_article/Test-acs_article.pdf +0 -0
  360. data/examples/latex_templates/Test-aea_article/Test-aea_article.pdf +0 -0
  361. data/examples/latex_templates/Test-amq_article/Test-amq_article.pdf +0 -0
  362. data/examples/latex_templates/Test-amq_article/pics/Figure2.pdf +0 -0
  363. data/examples/latex_templates/Test-asa_article/Test-asa_article.pdf +0 -0
  364. data/examples/latex_templates/Test-ieee_article/Test-ieee_article.pdf +0 -0
  365. data/examples/latex_templates/Test-rjournal_article/RJwrapper.pdf +0 -0
  366. data/examples/latex_templates/Test-springer_article/Test-springer_article.pdf +0 -0
  367. data/examples/rmarkdown/svm-rmarkdown-anon-ms-example/svm-rmarkdown-anon-ms-example.pdf +0 -0
  368. data/examples/rmarkdown/svm-rmarkdown-article-example/svm-rmarkdown-article-example.pdf +0 -0
  369. data/examples/rmarkdown/svm-rmarkdown-beamer-example/svm-rmarkdown-beamer-example.pdf +0 -0
  370. data/examples/rmarkdown/svm-rmarkdown-cv/svm-rmarkdown-cv.pdf +0 -0
  371. data/examples/rmarkdown/svm-rmarkdown-syllabus-example/svm-rmarkdown-syllabus-example.pdf +0 -0
  372. data/specs/r_dataframe.spec.rb +0 -379
  373. data/specs/r_environment.spec.rb +0 -140
  374. data/specs/r_formula.spec.rb +0 -232
  375. data/specs/r_language.spec.rb +0 -112
  376. data/specs/r_list.spec.rb +0 -293
  377. data/specs/r_plots.spec.rb +0 -72
  378. data/specs/ruby_expression.spec.rb +0 -316
@@ -3,8 +3,8 @@ title: "Extending R with classes, modules, procs, lambdas, oh my!"
3
3
  author:
4
4
  - "Rodrigo Botafogo"
5
5
  - "Daniel Mossé - University of Pittsburgh"
6
- tags: [Tech, Data Science, Ruby, R, GraalVM]
7
- date: "November 19th, 2018"
6
+ tags: [Tech, Data Science, Ruby, R, JRuby, "GNU R", Galaaz]
7
+ date: "November 19th, 2018 (narrative updated for Galaaz 2.0, 2026)"
8
8
  output:
9
9
  html_document:
10
10
  self_contained: true
@@ -25,17 +25,19 @@ fontsize: 11pt
25
25
 
26
26
  # Introduction
27
27
 
28
- This paper introduces and compares Galaaz with R's S4. It is a shameless rip off of
28
+ This paper introduces and compares Galaaz with R's S4. It is **modeled closely** on
29
29
  ["A '(not so)' Short Introduction to S4"](https://cran.r-project.org/doc/contrib/Genolini-S4tutorialV0-5en.pdf) by Christophe Genolini and follows the same structure and examples presented there.
30
30
 
31
31
  Galaaz is a Ruby Gem (library) that allows very tight integration between Ruby and R.
32
- It's integration is much tigher and transparent from what one can get beetween RinRuby
33
- or similar solutions in Python
32
+ Its integration is tighter and more transparent than what one can get between RinRuby
33
+ or similar solutions in Python,
34
34
  such as [PypeR](https://pypi.python.org/pypi/PypeR/1.1.0), [rpy2](http://rpy2.bitbucket.org/)
35
- and other similar solutions. Galaaz targets the GraalVM and it
36
- integrates with FastR, a high performance R interpreter for the GraalVM.
35
+ and other similar solutions.
37
36
 
38
- GraalVM:
37
+ **Galaaz 2.0** runs on **[JRuby](https://www.jruby.org/)** and drives **GNU R** through a **bridge**,
38
+ so Ruby code can create and manipulate R objects and call R functions while staying idiomatic Ruby.
39
+ An earlier prototype used Oracle’s **GraalVM** with **TruffleRuby** and **FastR**; that stack is
40
+ historical and is **not** what current Galaaz targets.
39
41
 
40
42
 
41
43
  # Bases of Object Programming
@@ -70,7 +72,7 @@ type information is also not a "compile" time type, since R is not compiled. Th
70
72
  checked at runtime. The same checking can be done in Ruby and we will do it later in this
71
73
  document.
72
74
 
73
- In the example bellow, we create
75
+ In the example below, we create
74
76
  class Trajectories with two instance variables, 'times' and 'matrix'. We will not go over
75
77
  the details of instance variables in Ruby, but here we created those variables with the
76
78
  keyword 'attr_reader' and a colom before the variables name:
@@ -85,7 +87,7 @@ end
85
87
 
86
88
 
87
89
  In order to create a new instance of object Trajectories we call method new on the class and
88
- we can store the result in a varible (not an instance variable) as bellow:
90
+ we can store the result in a variable (not an instance variable) as below:
89
91
 
90
92
  ```{ruby traj_variable}
91
93
  @traj = Trajectories.new
@@ -109,7 +111,7 @@ puts @traj.times
109
111
  Since there is no content stored in 'times' nor 'matrix', nil is returned. In order to add
110
112
  a value in the variables, we need to add a constructor to the class Trajectories. In R, a
111
113
  constructor is build by default, in Ruby, this has to be created by adding a method called
112
- 'initialize'. In the example bellow, we will create the initializer that accepts two values,
114
+ 'initialize'. In the example below, we will create the initializer that accepts two values,
113
115
  a 'times' value and a 'matrix' value and they are used to initialize the value of the
114
116
  instance variables:
115
117
 
@@ -144,7 +146,7 @@ i.e., R functions are all defined in Galaaz in the R namespace.
144
146
  Since Galaaz is Ruby and not R, some syntax adjustments are sometimes necessary. For instance,
145
147
  in R, a range is represented as '(1:4)', in Ruby, the same range is represented as '(1..4)'.
146
148
  When passing arguments to an R function in R one uses the '=' sign after the slot name; in R,
147
- one uses the ':' operator after parameter's name as we can see bellow:
149
+ one uses the ':' operator after parameter's name as we can see below:
148
150
 
149
151
  ```{ruby initializing_trajectories}
150
152
  # Create a Trajectories passing a times vector, but no matrix parameter
@@ -245,7 +247,7 @@ recommend its use, there are many cases in which default values are useful and m
245
247
  We have already seen default values in this document, with the default being 'nil'. This was
246
248
  necessary in order to be able to create our constructor and passing it the proper values.
247
249
 
248
- In the example bellow, a class TrajectoriesBis is created with default value 1 for times and a
250
+ In the example below, a class TrajectoriesBis is created with default value 1 for times and a
249
251
  matrix with no elements in matrix.
250
252
 
251
253
  ```{ruby}
@@ -339,7 +341,7 @@ Trajectories to add methods to it. In SS4, a method 'plot' is added to Trajecto
339
341
  point, Renjin and Galaaz do not yet have plotting capabilities, so we will have to skip this
340
342
  method and go directly to the implementation of the 'print' method.
341
343
 
342
- Bellow is the R code for method print:
344
+ Below is the R code for method print:
343
345
 
344
346
  ```
345
347
  > setMethod ("print","Trajectories",
@@ -434,25 +436,24 @@ features of Galaaz, some we have already seen, others will be described now:
434
436
  function look like a method of the object. For instance, R.nrow(@matrix), can be called by
435
437
  doing @matrix.nrow;
436
438
 
437
- * In R, every number is converted to a vector and this can be done with method R.i. Converting
438
- a vector with only one number back to a number can be done with method '.gz'. So if @num is
439
- an R vector that holds a number, then @num.gz is a number that can be used normally with Ruby
440
- methods;
439
+ * In R, every number is a length-1 vector. In Galaaz 2.0, unwrap a length-1 R vector
440
+ to a Ruby number with `>> 0` (or `unboxed_get(0)`). Older Galaaz docs used `.gz` /
441
+ `<< 0` for the same idea; `<<` still works as a compatibility alias for `>>`;
441
442
 
442
- * R functions and Ruby methods can be used freely in Galaaz. We show bellow two different ways
443
+ * R functions and Ruby methods can be used freely in Galaaz. We show below two different ways
443
444
  of getting the minimum of a number, either by calling R.min or by getting the minimum of an
444
445
  array, with the min method;
445
446
 
446
447
  * Galaaz allows for method 'chaining'. Method chaining, also known as named parameter idiom, is
447
448
  a common syntax for invoking multiple method calls in object-oriented programming languages.
448
449
  Each method returns an object, allowing the calls to be chained together in a single statement
449
- without requiring variables to store the intermediate results. For instance @matrix.nrow.gz,
450
- which returns the number of rows of the matrix as a number;
450
+ without requiring variables to store the intermediate results. For instance `@matrix.nrow >> 0`,
451
+ which returns the number of rows of the matrix as a Ruby number;
451
452
 
452
453
  * Ranges in Ruby are represented by (x..y), where x is the beginning of the range and y its end.
453
454
  An R matrix can be indexed by range, object@traj[1:nrowShow,1:ncolShow], the same result is
454
455
  obtained in Galaaz by indexing @matrix[(1..nrow_show), (1..ncol_show)]. Observe that this
455
- statement is then chained with the format function and with the pp method to print the matrix.
456
+ statement is then chained with the format function and printed with `puts`.
456
457
 
457
458
 
458
459
  ```{ruby}
@@ -466,8 +467,8 @@ class Trajectories
466
467
  puts("*** Class Trajectories, method Show *** ")
467
468
  Kernel.print("times = ")
468
469
  puts @times
469
- nrow_show = [10, @matrix.nrow << 0].min
470
- ncol_show = R.min(10, @matrix.ncol) << 0
470
+ nrow_show = [10, @matrix.nrow >> 0].min
471
+ ncol_show = R.min(10, @matrix.ncol) >> 0
471
472
  puts("* Traj (limited to a matrix 10x10) = ")
472
473
  puts @matrix[(1..nrow_show), (1..ncol_show)].format(digits: 2, nsmall: 2)
473
474
  puts("******* End Show (trajectories) ******* ")
@@ -487,7 +488,1215 @@ we try to 'show' it, it will generate an error. Let's see it:
487
488
  @empty_traj = Trajectories.new
488
489
  ```
489
490
 
490
- ```{ruby eval_error, warning = FALSE}
491
+ ```{ruby eval_error_1, warning = FALSE}
491
492
  @empty_traj.show
492
493
  ```
493
494
 
495
+ In this example, `@matrix` is `nil`, so calling `@matrix.nrow` raises
496
+ `undefined method 'nrow' for nil`. To fix this, we can either prevent an empty
497
+ trajectories class from being created, or make sure that method `show` will not
498
+ choke on the empty object. We will take the second alternative, to follow SS4,
499
+ and will check if either `@times` or `@matrix` is empty. If either one of them
500
+ is `nil`, then we will print a message saying so.
501
+
502
+ Although the first alternative, i.e., not allow for empty objects is a possibility in Ruby,
503
+ it seems that this is not the case for S4.
504
+
505
+ ```{ruby}
506
+ class Trajectories
507
+
508
+ def show
509
+ if (@times.nil? || @matrix.nil?)
510
+ puts("*** Class Trajectories is empty!! *** ")
511
+ return
512
+ end
513
+ puts("*** Class Trajectories, method Show *** ")
514
+ Kernel.print("times = ")
515
+ puts @times
516
+ nrow_show = [10, @matrix.nrow >> 0].min
517
+ ncol_show = R.min(10, @matrix.ncol) >> 0
518
+ puts("* Traj (limited to a matrix 10x10) = ")
519
+ puts @matrix[(1..nrow_show), (1..ncol_show)].format(digits: 2, nsmall: 2)
520
+ puts("******* End Show (trajectories) ******* ")
521
+ end
522
+
523
+ end
524
+ ```
525
+
526
+ ```{ruby}
527
+ @empty_traj.show
528
+ ```
529
+
530
+
531
+ # To Remove an Object
532
+
533
+ As far as I know, there isn't a good way of removing a defined class, but there might be
534
+ one and the interested user is directed to google it! In principle, there should not be
535
+ any real need to remove a defined class. Both in R and Galaaz, large programs are usually
536
+ written in a file and the file loaded. If one writes a wrong class, the better solution is
537
+ to correct it and then load it again. If the class is written directly on the console,
538
+ then leaving it there will not have any serious impact.
539
+
540
+ # Method count_missing
541
+
542
+ In R, methods 'print' and 'show' are methods that already exist. SS4 wants to add a method
543
+ called 'countMissing' which does not exist in R, and thus requires some special preparation. In
544
+ Ruby, every method we've created is a new method that exists inside the class. The fact that
545
+ 'print' happens to be also a method for class Kernel and 'show' is not, is not of special interest.
546
+ Actually we've seen that in order to call method print from the Kernel class we had to call
547
+ Kernel.print.
548
+
549
+ To create method 'count_missing' we just need to reopen the Trajectories class and add the
550
+ method the same way we've done with method 'show'. Again, let's first look at R's 'countMissing'
551
+ and then at Ruby's:
552
+
553
+
554
+ ```
555
+ > setMethod(
556
+ + f= "countMissing",
557
+ + signature= "Trajectories",
558
+ + definition=function(object){
559
+ + return(sum(is.na(object@traj)))
560
+ + }
561
+ + )
562
+ ```
563
+
564
+ Here we introduce another particular case of Galaaz. R has many methods that have a '.' in
565
+ their names, such as 'is.na'. In Ruby, the dot '.' has a special meaning as it is the way
566
+ we call a method on an object. Doing 'R.is.na' will not work. So, in Galaaz, R functions that
567
+ have a dot in them will have the dot substituted by '__'. So, method is.na in Galaaz, becomes
568
+ R.is__na. In method count_missing we use method chaining and convert the final count to a
569
+ Ruby number with `>> 0` (unbox).
570
+
571
+ ```{ruby}
572
+ class Trajectories
573
+
574
+ def count_missing
575
+ return @matrix.is__na.sum >> 0
576
+ end
577
+
578
+ end
579
+ ```
580
+
581
+ ```{ruby}
582
+ puts @trajCochin.count_missing
583
+ ```
584
+
585
+ # To See the Methods
586
+
587
+ In order to see the methods we have defined so far, we call on class Trajectories the method
588
+ 'instance_methods' passing it one argument, 'false', as follows:
589
+
590
+ ```{ruby}
591
+ puts Trajectories.instance_methods(false)
592
+ ```
593
+
594
+ It is interesting to observe that we see our three methods 'count_missing', 'print' and 'show', but
595
+ we also see two other methods 'times' and 'matrix', but those last two as far as we know are
596
+ just instance variables and not methods, right? More on that when we talk about Accessors.
597
+
598
+ Galaaz and Ruby do not by default provide a way to see a method's code. However, if the user uses
599
+ a Ruby console such as Pry, then seeing methods and debugging is possible. Pry is beyond the
600
+ scope of this document.
601
+
602
+ # Construction
603
+
604
+ Every class in Ruby has a constructor, if not explicitly defined, at least implicitly. Method
605
+ initialize is the constructor method and the one that coordinates the whole construction process.
606
+
607
+ # Inspector
608
+
609
+ There is no default 'inspector' in Ruby as in R, although there is nothing that prevents the
610
+ developer from inspecting and validating the input. For example, in the object Trajectories, one may
611
+ want to check that the number of elements in 'times' is equal to the number of columns in 'matrix'
612
+ and if they are not, issue an error. In order to understand why this restriction exists, the user is
613
+ again directed to SS4.
614
+
615
+ Here we show the R code for this validation:
616
+
617
+ ```
618
+ > setClass(
619
+ + Class="Trajectories",
620
+ + representation(times="numeric",traj="matrix"),
621
+ + validity=function(object){
622
+ + cat("~~~ Trajectories: inspector ~~~ \\n")
623
+ + if(length(object@times)!=ncol(object@traj)){
624
+ + stop ("[Trajectories: validation] the number of temporal measurements does not correspond
625
+ + }else{}
626
+ + return(TRUE)
627
+ + }
628
+ + )
629
+ ```
630
+
631
+ In order to implement this validation we will coordinate it in the initialize method.
632
+
633
+ ```{ruby}
634
+ class Trajectories
635
+
636
+ def initialize(times: nil, matrix: nil)
637
+ @times = times
638
+ @matrix = matrix
639
+
640
+ # validate the input, to make sure that size of @times and the number of columns in
641
+ # @matrix are the same
642
+ puts ("~~~ Trajectories: inspector ~~~ ")
643
+ raise "[Trajectories: validation] the number of temporal measurements does not correspond with the number of columns in the matrix" if ((@times.length >> 0) != (@matrix.ncol >> 0))
644
+
645
+ # show the object just created
646
+ show
647
+
648
+ end
649
+
650
+ end
651
+ ```
652
+
653
+ Let's first create a Trajectories that validates fine, i.e., the number of elements in @times is
654
+ equal to the number of columns of the matrix. In this case, we will show a message saying that
655
+ validation was done and then print the object.
656
+
657
+ ```{ruby}
658
+ ok = Trajectories.new(times: R.c(1..2), matrix: R.matrix((1..2), ncol: 2))
659
+ ```
660
+
661
+ Now, if we try to create a Trajectories that does not pass the validation criteria, our code
662
+ will raise an exception. Exceptions are a standard way to deal with errors in Ruby code and
663
+ many other object oriented languages. The interested reader should look for further documentation
664
+ on exceptions on the web.
665
+
666
+
667
+ ```{ruby eval_error_2, warning = FALSE}
668
+ error = Trajectories.new(times: R.c(1..3), matrix: R.matrix((1..2), ncol: 2))
669
+ ```
670
+
671
+ The validation above does not consider the case when an empty object is created. Here we will
672
+ check to see if either times or matrix are nil; if either one of them is nil, then we will raise
673
+ an exception and interrupt the creation of the object. We also create a method validate that is
674
+ called from our initialize method.
675
+
676
+ Method validate has some interesting features about the integration of Galaaz and R. We compare
677
+ lengths after unboxing with `>> 0`, so the comparison is ordinary Ruby arithmetic on numbers.
678
+ (A length-1 R logical can likewise be treated as a Ruby boolean via `>> 0`.)
679
+
680
+
681
+ ```{ruby}
682
+ class Trajectories
683
+
684
+ def initialize(times: nil, matrix: nil)
685
+ @times = times
686
+ @matrix = matrix
687
+
688
+ # call method validate to validate our input
689
+ validate
690
+
691
+ # show the object just created
692
+ show
693
+
694
+ end
695
+
696
+ def validate
697
+
698
+ # Let's first check that we do not have an empty object
699
+ raise "Neither times nor matrix can be an empty object" if (@times.nil? || @matrix.nil?)
700
+
701
+ # validate the input, to make sure that size of @times and the number of columns in
702
+ # @matrix are the same
703
+ puts ("~~~ Trajectories: inspector ~~~ ")
704
+ raise "[Trajectories: validation] the number of temporal measurements does not correspond with the number of columns in the matrix" if ((@times.length >> 0) != (@matrix.ncol >> 0))
705
+
706
+ end
707
+
708
+ end
709
+ ```
710
+
711
+ **Note:** with this stricter `validate`, `Trajectories.new` no longer accepts empty objects.
712
+ The earlier `@empty_traj = Trajectories.new` / `@empty_traj.show` pattern from part 1 no longer
713
+ applies for *new* constructions; existing instances created before this reopen still exist in
714
+ memory, but calling `new` with missing `times` or `matrix` will raise.
715
+
716
+ Let's try then creating an empty object:
717
+
718
+
719
+ ```{ruby eval_error_3, warning = FALSE}
720
+ error = Trajectories.new
721
+ ```
722
+
723
+ Another example:
724
+
725
+ ```{ruby eval_error_4, warning = FALSE}
726
+ error = Trajectories.new(times: 1)
727
+ ```
728
+
729
+ Let's see now that the implementation is correct and that it does not raise an error on valid
730
+ input:
731
+
732
+ ```{ruby}
733
+ ok = Trajectories.new(times: R.c(1, 2), matrix: R.matrix((1..2), ncol: 2))
734
+ ```
735
+
736
+ The 'initialize' method is called ONLY during the initial creation of the object. If any instance
737
+ variable is later modified, no control is done. At this moment though, there is no way to change
738
+ the value of any of our instance variables.
739
+
740
+ ```
741
+ error.times = R.c(1, 2, 3)
742
+ ```
743
+
744
+ The Trajectories class works for R objects and expects as input R objects. Passing R objects in
745
+ all examples has been the obligation of the programmer. Galaaz, however, can also accept many
746
+ Ruby values (ranges, arrays of numbers, and so on) if we convert them at the boundary. There is
747
+ no `R.convert` in Galaaz 2.0; a small helper is enough: leave `nil` alone, keep objects that are
748
+ already `R::Object`, and otherwise wrap with `R.c` (which accepts ranges as well as scalars and
749
+ vectors). Matrices that are already R objects are kept as-is.
750
+
751
+ ```{ruby}
752
+ class Trajectories
753
+
754
+ def as_r(x)
755
+ return nil if x.nil?
756
+ return x if x.is_a?(R::Object)
757
+ R.c(x)
758
+ end
759
+
760
+ def initialize(times: nil, matrix: nil)
761
+ @times = as_r(times)
762
+ @matrix = as_r(matrix)
763
+
764
+ # call method validate to validate our input
765
+ validate
766
+
767
+ # show the object just created
768
+ show
769
+
770
+ end
771
+
772
+ def validate
773
+
774
+ # Let's first check that we do not have an empty object
775
+ raise "Neither times nor matrix can be an empty object" if (@times.nil? || @matrix.nil?)
776
+
777
+ # validate the input, to make sure that size of @times and the number of columns in
778
+ # @matrix are the same
779
+ puts ("~~~ Trajectories: inspector ~~~ ")
780
+ tl = @times.length >> 0; mc = @matrix.ncol >> 0
781
+ raise "[Trajectories: validation] the number of temporal measurements #{tl} does not correspond with the number of columns in the matrix #{mc}" if (tl != mc)
782
+
783
+ end
784
+
785
+ end
786
+ ```
787
+
788
+ And now let's create a new Trajectories, but we will now pass a Ruby range for times:
789
+
790
+ ```{ruby}
791
+ ok = Trajectories.new(times: (1..2), matrix: R.matrix((1..2), ncol: 2))
792
+ ```
793
+
794
+ Perfect! This works fine.
795
+
796
+ *(Historical note: an earlier Galaaz prototype on Renjin also demonstrated sharing storage with
797
+ the MDArray gem. Those shared-store demos are not part of Galaaz 2.0 / the GNU R bridge, and are
798
+ omitted here.)*
799
+
800
+ # The Initializator
801
+
802
+ As we have seen, method 'initialize' is the main object creator orchestrator. This method can be
803
+ as complex as needed. So, let's get on with some improvements to our Trajectories class.
804
+
805
+ It would be rather pleasant that the columns of the matrix of the trajectories have names, the
806
+ names of measurements times. In the same way, the lines could be subscripted by a number of
807
+ individual.
808
+
809
+ To do this in R, one also uses method initialize:
810
+
811
+
812
+ ```
813
+ > setMethod(
814
+ + f="initialize",
815
+ + signature="Trajectories",
816
+ + definition=function(.Object,times,traj){
817
+ + cat("~~~ Trajectories: initializator ~~~ \\n")
818
+ + colnames(traj) <- paste("T",times,sep="")
819
+ + rownames(traj) <- paste("I",1:nrow(traj),sep= "")
820
+ + .Object@traj <- traj # Assignment of the slots
821
+ + .Object@times <- times
822
+ + return(.Object) # return of the object
823
+ + }
824
+ + )
825
+ ```
826
+
827
+ In R, it is possible to assign a value to the result of a function, for example
828
+ `colnames(x) <- c("v1", "v2", "v3")`. In Galaaz 2.0 the same idea is expressed with ordinary
829
+ Ruby setters on the R object: `@matrix.colnames = ...` and `@matrix.rownames = ...`.
830
+
831
+ ```{ruby}
832
+ class Trajectories
833
+
834
+ def as_r(x)
835
+ return nil if x.nil?
836
+ return x if x.is_a?(R::Object)
837
+ R.c(x)
838
+ end
839
+
840
+ def initialize(times: nil, matrix: nil)
841
+ @times = as_r(times)
842
+ @matrix = as_r(matrix)
843
+
844
+ # call method validate to validate our input
845
+ validate
846
+
847
+ # Add row and column names
848
+ puts ("~~~ Trajectories: initializator ~~~ ")
849
+ @matrix.colnames = R.paste("T", @times, sep: "")
850
+ @matrix.rownames = R.paste("I", (1..(@matrix.nrow >> 0)), sep: "")
851
+
852
+ # show the object just created
853
+ show
854
+
855
+ end
856
+
857
+ end
858
+ ```
859
+
860
+ ```{ruby}
861
+ @traj = Trajectories.new(times: R.c(1,2,4,8), matrix: R.matrix((1..8), nrow: 2))
862
+ ```
863
+
864
+ Note that we still call our 'validate' method and it is still an error to create an empty
865
+ Trajectories or one in which the sizes are wrong:
866
+
867
+ ```{ruby eval_error_5, warning = FALSE}
868
+ error = Trajectories.new(times: R.c(1, 2, 48), matrix: R.matrix((1..8), nrow: 2))
869
+ ```
870
+
871
+ A constructor does not necessarily take the instance variable of the object as argument. For
872
+ example, if we know (that is not the case in reality, but let us imagine so) that the
873
+ BMI increases by 0.1 every week, we could build trajectories by providing the number
874
+ of weeks and the initial weights.
875
+
876
+ First the code in R, we skip the definition of class TrajectoriesBis:
877
+
878
+
879
+ ```
880
+ > setMethod ("initialize",
881
+ + "TrajectoriesBis",
882
+ + function(.Object,nbWeek,BMIinit){
883
+ + traj <- outer(BMIinit,1:nbWeek,function(init,week){return(init+0.1*week)})
884
+ + colnames(traj) <- paste("T",1:nbWeek,sep="")
885
+ + rownames(traj) <- paste("I",1:nrow(traj),sep="")
886
+ + .Object@times <- 1:nbWeek
887
+ + .Object@traj <- traj
888
+ + return(.Object)
889
+ + }
890
+ + )
891
+ ```
892
+
893
+ Now, let's make a TrajectoriesBis in Galaaz. Here again, we should point out some characteristics
894
+ of our code:
895
+
896
+ * We made initialize with two positional arguments, instead of named arguments, i.e.,
897
+ the first argument is the number of weeks and the second bmi_init. In this case,
898
+ when making a new object the position of the arguments is important and there is no
899
+ way to pass the argument by name;
900
+
901
+ * R function outer was called as if a method from bmi_init using dot notation, although
902
+ one could use R.outer without problem;
903
+
904
+ * Function 'outer' expects an R function as its 3rd argument. In order to build an R
905
+ function from Galaaz, we need to pass the function definition as a string to R.eval.
906
+
907
+ ```{ruby}
908
+ class TrajectoriesBis
909
+
910
+ attr_reader :times
911
+ attr_reader :matrix
912
+
913
+ def initialize(number_weeks, bmi_init)
914
+ @matrix = bmi_init.outer((1..number_weeks),
915
+ R.eval("function(init, week) {return(init + 0.1 * week)}"))
916
+ @times = R.c((1..number_weeks))
917
+ end
918
+
919
+ end
920
+
921
+ @traj_bis = TrajectoriesBis.new(4, R.c(16,17,15.6))
922
+ ```
923
+
924
+ ```{ruby}
925
+ puts @traj_bis.matrix
926
+ ```
927
+
928
+ It is always possible to pass a Ruby variable into a string by interpolating it. Put the
929
+ variable inside `#{...}`. As an example, let's also require the BMI increase as a parameter.
930
+ (A common mistake is to escape the interpolation — writing `\#{increment}` — which leaves the
931
+ characters literally in the R source and does not substitute the Ruby value. Use real
932
+ interpolation:)
933
+
934
+ ```{ruby}
935
+ class TrajectoriesBis
936
+
937
+ def initialize(number_weeks, bmi_init, increment)
938
+ @matrix = bmi_init.outer((1..number_weeks),
939
+ R.eval("function(init, week) {return(init + #{increment} * week)}"))
940
+ @times = R.c((1..number_weeks))
941
+ end
942
+
943
+ end
944
+
945
+ @traj_bis = TrajectoriesBis.new(4, R.c(16,17,15.6), 0.3)
946
+ ```
947
+
948
+ ```{ruby}
949
+ puts @traj_bis.matrix
950
+ ```
951
+
952
+ # Constructors for Users
953
+
954
+ Many times, it is interesting to have different ways of constructing an object depending on
955
+ what information our users have or want to provide to the constructor. Although we have only one
956
+ initialize method, we can create multiple methods, that do some preprocessing and then call the
957
+ initialize method to carry out the object building.
958
+
959
+ In order to do that, we use what are called class methods, instead of instance methods. All the
960
+ methods we've created so far are instance methods; class methods are defined by prepending the
961
+ self keyword to the method's name. Still using the assumption that the BMI will grow by 0.1 per
962
+ week, let's define a regular trajectory without having to define a TrajectoriesBis as above:
963
+
964
+
965
+ ```
966
+ > regularTrajectories <- function(nbWeek,BMIinit) {
967
+ + traj <- outer(BMIinit,1:nbWeek,function(init,week){return(init+0.1*week)})
968
+ + times <- 1: nbWeek
969
+ + return(new(Class="Trajectories",times=times,traj=traj))
970
+ + }
971
+ > regularTrajectories(nbWeek=3,BMIinit=c(14,15,16))
972
+ ```
973
+
974
+ Notice how method 'regular' is defined as 'self.regular', making it a class method. The last
975
+ statement of the method definition is actually a call to the Trajectories constructor 'new' passing
976
+ the calculated values for times and matrix.
977
+
978
+ Notice also how method regular is called, similar to the way new is called by adding it after class
979
+ Trajectories name: 'Trajectories.regular'.
980
+
981
+ ```{ruby}
982
+ class Trajectories
983
+
984
+ def self.regular(number_weeks: nil, bmi_init: nil)
985
+ matrix = bmi_init.outer((1..number_weeks),
986
+ R.eval("function(init, week) {return(init + 0.1 * week)}"))
987
+ times = R.c((1..number_weeks))
988
+ Trajectories.new(times: times, matrix: matrix)
989
+ end
990
+
991
+ end
992
+ ```
993
+
994
+ ```{ruby}
995
+ @regular = Trajectories.regular(bmi_init: R.c(14, 15, 16), number_weeks: 3)
996
+ ```
997
+
998
+ We have already seen that constructors can be as complex as needed, calling other methods and doing
999
+ calculations on the received parameters. On this last example, we will check if the times
1000
+ variable was provided. If it is not provided, then we will use matrix columns to define the times:
1001
+
1002
+ ```{ruby}
1003
+ class Trajectories
1004
+
1005
+ def self.init(times: nil, matrix: nil)
1006
+ times = R.c((1..(matrix.ncol >> 0))) if times.nil?
1007
+ Trajectories.new(times: times, matrix: matrix)
1008
+ end
1009
+
1010
+ end
1011
+ ```
1012
+
1013
+ ```{ruby}
1014
+ @traj = Trajectories.init(matrix: R.matrix((1..8), ncol: 4))
1015
+ ```
1016
+
1017
+ # Accessors
1018
+
1019
+ Accessors are methods for getting and setting the value of instance variables.
1020
+
1021
+ # Get
1022
+
1023
+ Getters are methods for getting the value of an instance variable. We have been using getters
1024
+ since the beginning of this document, without explicitly saying so. When defining attr_reader
1025
+ :times and attr_reader :matrix, we have actually defined two getter methods for reading the values
1026
+ of variables times and matrix respectively. We can however define getters explicitly:
1027
+
1028
+ ```{ruby}
1029
+ class TrajectoriesBis
1030
+
1031
+ def initialize(times: nil, matrix: nil)
1032
+ @times = times
1033
+ @matrix = matrix
1034
+ end
1035
+
1036
+ def times
1037
+ @times
1038
+ end
1039
+
1040
+ def matrix
1041
+ @matrix
1042
+ end
1043
+
1044
+ end
1045
+
1046
+ @traj = TrajectoriesBis.new(times: 1, matrix: 2)
1047
+ ```
1048
+
1049
+ ```{ruby}
1050
+ puts @traj.times
1051
+ ```
1052
+
1053
+ ```{ruby}
1054
+ puts @traj.matrix
1055
+ ```
1056
+
1057
+ It is also possible to define more sophisticated getters. For example one can
1058
+ regularly need the BMI at inclusion. In R, one would index a matrix as matrix[,1]. In Ruby,
1059
+ it is a syntax error to have a ',' just after the '['. In this case we need to add 'nil' as
1060
+ in matrix[nil, 1]:
1061
+
1062
+ ```{ruby}
1063
+ class Trajectories
1064
+
1065
+ def get_traj_inclusion
1066
+ @matrix[nil, 1]
1067
+ end
1068
+
1069
+ end
1070
+ ```
1071
+
1072
+ ```{ruby}
1073
+ puts @trajCochin.get_traj_inclusion
1074
+ ```
1075
+
1076
+ # Set
1077
+
1078
+ A setter is a method that assigns a value to a variable. As with getters, Ruby also provides an
1079
+ easy way to write setters and allow you to also write them explicitly. Let's first use the
1080
+ simple way:
1081
+
1082
+ ```{ruby}
1083
+ class TrajectoriesBis
1084
+
1085
+ attr_writer :times
1086
+ attr_writer :matrix
1087
+
1088
+ def initialize(times: nil, matrix: nil)
1089
+ @times = times
1090
+ @matrix = matrix
1091
+ end
1092
+
1093
+ end
1094
+
1095
+ @traj = TrajectoriesBis.new
1096
+ @traj.times = R.c(1, 2)
1097
+ @traj.matrix = R.matrix((1..2), ncol: 2)
1098
+ ```
1099
+
1100
+ ```{ruby}
1101
+ puts @traj.matrix
1102
+ ```
1103
+
1104
+ Note that now we can use '=' to assign a value to both variables times and matrix. Without
1105
+ setters, changing the value of variables times and matrix was not possible. Our class, up
1106
+ to this point was protected from any changes to those variables. If we need to allow changes
1107
+ to those variables, then setters are needed. In this case, the simple setter as shown above is
1108
+ not ideal, since it would allow changes that break the restriction that variable times has to
1109
+ have the same length as the number of columns of matrix. In order to do the verification we
1110
+ need to implement a more sophisticated setter. In the example below, we add the 'times=' setter
1111
+ that receives as input one argument. First we convert the given argument to an R object, then
1112
+ check to see that the length of times is the same as the number of columns and if everything is
1113
+ fine, then we set the value of instance variable times:
1114
+
1115
+ ```{ruby}
1116
+ class Trajectories
1117
+
1118
+ def as_r(x)
1119
+ return nil if x.nil?
1120
+ return x if x.is_a?(R::Object)
1121
+ R.c(x)
1122
+ end
1123
+
1124
+ def times=(times)
1125
+ times = as_r(times)
1126
+ tl = times.length >> 0; mc = @matrix.ncol >> 0
1127
+ raise "[Trajectories: validation] the number of temporal measurements #{tl} does not correspond with the number of columns in the matrix #{mc}" if (tl != mc)
1128
+ @times = times
1129
+ end
1130
+
1131
+ end
1132
+ ```
1133
+
1134
+ ```{ruby eval_error_6, warning = FALSE}
1135
+ @trajCochin.times = (1..5)
1136
+ ```
1137
+
1138
+ We now set the value appropriately and will not get any errors:
1139
+
1140
+ ```{ruby}
1141
+ @trajCochin.times = R.c(1, 5, 6, 8)
1142
+ ```
1143
+
1144
+ # The Operator '['
1145
+
1146
+ It is also possible to define getters by using the operator '['. This operator is not usually
1147
+ used for returning instance variables and it is preferable to use the methods we've used above;
1148
+ however, for completeness with SS4 we are showing how to define this here. Operator '[' is
1149
+ better left to be used for array/matrix indices.
1150
+
1151
+ ```{ruby}
1152
+ class Trajectories
1153
+
1154
+ def [](var_name)
1155
+
1156
+ case var_name
1157
+ when "times"
1158
+ @times
1159
+ when "matrix"
1160
+ @matrix
1161
+ else
1162
+ raise "Unknown instance variable"
1163
+ end
1164
+
1165
+ end
1166
+
1167
+ end
1168
+ ```
1169
+
1170
+ ```{ruby}
1171
+ puts @trajCochin["times"]
1172
+ ```
1173
+
1174
+ Similarly, we could use operator '[]=' to assign a value to times and matrix. We will not do this
1175
+ here as we think that the other options are better and the interested user can easily find help,
1176
+ if needed to implement such method.
1177
+
1178
+ # To Go Further
1179
+
1180
+ This section will introduce advanced features of Object Oriented programming such as Inheritance
1181
+ and Modules and will also show some aspects of S4 that do not apply to Ruby.
1182
+
1183
+ # Methods Using Several Arguments
1184
+
1185
+ In Ruby, methods can have as many arguments as needed and those methods are defined the way we
1186
+ have already seen in many of the examples above. The example in SS4 presents a method that prints
1187
+ different output if its input is numeric, character or both. Let's write a class in Ruby that
1188
+ does the same for Numeric and String. In Ruby we do not define global functions, we always define
1189
+ methods inside classes or modules (as we will see later). Also, Ruby is not typed, so methods are
1190
+ not called depending on their types as in SS4 examples. Below, method test will be called with
1191
+ one parameter. At the time of calling we do not know the type of the argument; the method can
1192
+ then check if the received argument is a Numeric or a String and at this time, decide what should
1193
+ be printed.
1194
+
1195
+ ```{ruby}
1196
+ class Test
1197
+
1198
+ def test(input)
1199
+
1200
+ case input
1201
+ when Numeric
1202
+ puts "The input is numeric: #{input}"
1203
+ when String
1204
+ puts "The input is a string: #{input}"
1205
+ else
1206
+ puts "The input is neither a number nor a string"
1207
+ end
1208
+
1209
+ end
1210
+
1211
+ end
1212
+
1213
+ @t = Test.new
1214
+ ```
1215
+
1216
+ ```{ruby}
1217
+ @t.test(5)
1218
+ ```
1219
+
1220
+ ```{ruby}
1221
+ @t.test("Hello")
1222
+ ```
1223
+
1224
+ Ruby has ways of dealing with multiple arguments, missing arguments, undefined number of arguments,
1225
+ named arguments, unnamed arguments, etc. This is beyond the scope of this document and we
1226
+ suggest the interested reader to go to the many resources about Ruby that can easily be found
1227
+ on the web.
1228
+
1229
+ We will now create a new class 'Partition' that we will use later in this document. This class will
1230
+ have only the basic methods needed for the examples to work.
1231
+
1232
+ ```{ruby}
1233
+ class Partition
1234
+
1235
+ attr_reader :nb_groups
1236
+ attr_reader :part
1237
+
1238
+ def initialize(nb_groups, part)
1239
+ @nb_groups = nb_groups
1240
+ @part = part
1241
+ end
1242
+
1243
+ end
1244
+
1245
+ @partCochin = Partition.new(2, R.c("A","B","A","B").factor)
1246
+ @partStAnne = Partition.new(2, R.c("A","B").rep(R.c(50,30)).factor)
1247
+ ```
1248
+
1249
+ ```{ruby}
1250
+ puts @partCochin.part
1251
+ ```
1252
+
1253
+ ```{ruby}
1254
+ puts @partStAnne.part
1255
+ ```
1256
+
1257
+ We will suppose that part is always composed of capital letters going from A to
1258
+ LETTERS[nb_groups].
1259
+
1260
+ # Inheritance
1261
+
1262
+ Ruby being a powerful Object Oriented language has the concept of Inheritance, but it does not
1263
+ allow for multiple inheritance. Multiple inheritance has many drawbacks and Ruby just does not
1264
+ support it. However, Ruby has other concepts that make up for the lack of multiple inheritance as
1265
+ we will see in the following examples.
1266
+
1267
+ So, let's go back to SS4 examples. We want now to define a class called TrajPartitioned that
1268
+ inherits from class Trajectories. When a class has a parent, all methods available for the
1269
+ parent are also available to the child.
1270
+
1271
+
1272
+ ```{ruby}
1273
+ class TrajPartitioned < Trajectories
1274
+
1275
+ attr_reader :list_partitions
1276
+
1277
+ end
1278
+ ```
1279
+
1280
+ That's all there is to it! We've just created a class TrajPartitioned that inherits all methods
1281
+ from class Trajectories and at this point does nothing different from Trajectories, but adds a
1282
+ new instance variable: list_partitions.
1283
+
1284
+ Creating TrajPartitioned without arguments will generate an error, since a Trajectories requires
1285
+ both times and matrix to be non null.
1286
+
1287
+
1288
+ ```{ruby eval_error_7, warning = FALSE}
1289
+ @tdPitie = TrajPartitioned.new
1290
+ ```
1291
+
1292
+ Let's try to create a TrajPartitioned, but passing to it two partitions. For that, let's first
1293
+ create a new Partition:
1294
+
1295
+ ```{ruby}
1296
+ @partCochin2 = Partition.new(3, R.c("A", "C", "C", "B").factor)
1297
+ ```
1298
+
1299
+ And now let's create the TrajPartitioned:
1300
+
1301
+ ```{ruby eval_error_8, warning = FALSE}
1302
+ @tdCochin = TrajPartitioned.new(times: R.c(1,3,4,5), matrix: @trajCochin.matrix,
1303
+ list_partitions: R.list(@partCochin, @partCochin2))
1304
+ ```
1305
+
1306
+ This didn't work: R function 'list' expects R objects, and in this case, @partCochin and
1307
+ @partCochin2 are Ruby classes, so trying to apply function list to them does not work. Clearly,
1308
+ we will have to work in the realm of Ruby to keep the list of partitions. This is not a problem
1309
+ as Ruby has data structures to maintain a list of objects, the Array. Let's then try another
1310
+ solution:
1311
+
1312
+ ```{ruby eval_error_9, warning = FALSE}
1313
+ @tdCochin = TrajPartitioned.new(times: R.c(1,3,4,5), matrix: @trajCochin.matrix,
1314
+ list_partitions: [@partCochin, @partCochin2])
1315
+ ```
1316
+
1317
+ We now get a second error: 'unknown keyword: list_partitions'. Class TrajPartitioned inherits
1318
+ from class Trajectories and class Trajectories has an initialize function that requires two
1319
+ parameters, times and matrix; list_partitions is not a parameter for initialize and is thus
1320
+ unknown. In order to fix this problem we need to create an initialize method for class
1321
+ TrajPartitioned.
1322
+
1323
+
1324
+ # The 'super' Keyword
1325
+
1326
+ R has a method called 'callNextMethod' for control flow between inherited classes. In Ruby, we
1327
+ have a model that is a bit different. When a method is called on a subclass, if this method is
1328
+ not found it will be searched in the parent class and it will go up the hierarchy of classes until
1329
+ it is found or an error is issued. If we want the parent method to be called we can call 'super':
1330
+
1331
+
1332
+ ```{ruby}
1333
+ class TrajPartitioned
1334
+
1335
+ def initialize(times: nil, matrix: nil, list_partitions: nil)
1336
+ super(times: times, matrix: matrix)
1337
+ @list_partitions = list_partitions
1338
+ end
1339
+
1340
+ end
1341
+ ```
1342
+
1343
+ Let's try our example again:
1344
+
1345
+ ```{ruby}
1346
+ @tdCochin = TrajPartitioned.new(times: R.c(1,3,4,5), matrix: @trajCochin.matrix,
1347
+ list_partitions: [@partCochin, @partCochin2])
1348
+ ```
1349
+
1350
+ Now @tdCochin is created correctly; however, the 'show' method only shows information about
1351
+ times and matrix, there is nothing about our new list_partitions variable. This is so, since
1352
+ there is no method 'show' in TrajPartitioned, so method 'show' from Trajectories is executed.
1353
+
1354
+ So, let's start by writing a 'print' method, that will print all the information we have in
1355
+ TrajPartitioned. The flow of control for this method is: Ruby sees a call to 'print', so it checks
1356
+ to see if 'print' is a method for TrajPartitioned. Since we have just defined this method, Ruby
1357
+ finds it and executes it. The first command in print is a call to 'super', which will call the
1358
+ parent 'print' method, that prints information for 'times' and 'matrix'. When the parent 'print'
1359
+ finishes control continues after the 'super' call, printing the number of available partitions.
1360
+
1361
+ ```{ruby}
1362
+ class TrajPartitioned
1363
+
1364
+ def print
1365
+ super
1366
+ puts ("the object also contains #{@list_partitions.length} partition")
1367
+ puts ("***** Fine of print (TrajPartitioned) *****")
1368
+ end
1369
+
1370
+ end
1371
+ ```
1372
+
1373
+ ```{ruby}
1374
+ @tdCochin.print
1375
+ ```
1376
+
1377
+ Notice that this model is much cleaner than 'callNextMethod' and is not subject to any of the
1378
+ difficulties presented in SS4 and there is no need for the keywords “is”, “as” and “as<-”, although
1379
+ Ruby provides methods to check the class of an object, its hierarchy, etc. when needed.
1380
+
1381
+ In Ruby there is no similar method as "setIs" and it is not possible to convert one class into
1382
+ another, but there are other ways of getting the necessary results. Let's then implement a
1383
+ method that returns the partition with the least number of groups. First, as usual, the R code
1384
+ with 'setIs':
1385
+
1386
+ ```
1387
+ > setIs(
1388
+ + class1="TrajPartitioned",
1389
+ + class2="Partition",
1390
+ + coerce=function(from,to){
1391
+ + numberGroups <- sapply(tdCochin@listPartitions,getNbGroups)
1392
+ + Smallest <- which.min(-numberGroups)
1393
+ + to<-new("Partition")
1394
+ + to@nbGroups <- getNbGroups(from@listPartitions[[Smallest]])
1395
+ + to@part <- getPart(from@listPartitions[[Smallest]])
1396
+ + return(to)
1397
+ + }
1398
+ + )
1399
+ ```
1400
+
1401
+ And now the Ruby code. Here we are getting deeper into Ruby and it is becoming harder for a
1402
+ pure R developer to understand the code. We will describe it in more detail:
1403
+
1404
+ * We define a method called 'to_part' that has one argument 'which'. By default 'which'
1405
+ is ':min', the name of the minimum method. This means that if no argument is given to
1406
+ to_part it will assume which = :min;
1407
+
1408
+ * @list_partitions is a Ruby array. Method map is similar to method sapply in R, it
1409
+ applies a 'block' to every element of the array, returning an array. Describing
1410
+ blocks is beyond the scope of this document, but we can think of it as if it were a
1411
+ function. The block is in '{}' and has one argument named 'part'. Thus, map goes
1412
+ through all elements of the array, and gets the nb_groups of the element and returns
1413
+ them into the number_groups array.
1414
+
1415
+ * number_groups is an array and doing number_groups.min returns the minimum value in
1416
+ number_groups and number_groups.max the maximum. We can call a method on an object
1417
+ by 'sending' the method name to the object, so, number_groups.send(:min) is equivalent to
1418
+ number_groups.min;
1419
+
1420
+ * Method 'index' for array, returns the index of a given element. So, number_groups.index(3)
1421
+ would return the index of the element '3'. Then number_groups.index(number_groups.min)
1422
+ returns the index of the minimum element in the array. This is the equivalent of R
1423
+ which.min(number_groups);
1424
+
1425
+ * Finally, number_groups.index(number_groups.send(which)), will return the index of the
1426
+ element we ask for, be it :min or :max. Note that if we pass another value, this would
1427
+ be an error.
1428
+
1429
+ ```{ruby}
1430
+ class TrajPartitioned
1431
+
1432
+ def to_part(which = :min)
1433
+ number_groups = @list_partitions.map { |part| part.nb_groups }
1434
+ selected = number_groups.index(number_groups.send(which))
1435
+ return @list_partitions[selected]
1436
+ end
1437
+
1438
+ end
1439
+ ```
1440
+
1441
+ To get the partition with the minimum number of elements:
1442
+
1443
+ ```{ruby}
1444
+ puts @tdCochin.to_part.part
1445
+ ```
1446
+
1447
+ To get the partition with the maximum number of elements:
1448
+
1449
+ ```{ruby}
1450
+ puts @tdCochin.to_part(:max).part
1451
+ ```
1452
+
1453
+ In this example we did not follow exactly the R code from SS4. The reason for that is that
1454
+ 'list_partitions' is a list of Ruby classes and we cannot run sapply on this list. If we
1455
+ try to call a 'getNbGroups' or in the Ruby case nb_groups via R's sapply, the code will crash.
1456
+
1457
+ # Virtual Classes
1458
+
1459
+ In Ruby there are no "Virtual Classes", but it is possible to implement derived classes from
1460
+ a parent class with methods that behave properly according to the object's class. Following
1461
+ SS4 we will implement two classes: PartitionSimple and PartitionEval which are subclasses
1462
+ of class PartitionFather. PartitionFather will just be a regular class. Methods defined in
1463
+ PartitionFather will be available to be used in the subclasses
1464
+
1465
+ Here is the R code of those classes and the implementation of a method in PartitionFather
1466
+ that multiplies the number of groups by 2:
1467
+
1468
+
1469
+ ```
1470
+ > setClass(
1471
+ + Class="PartitionFather",
1472
+ + representation=representation(nbGroups="numeric","VIRTUAL")
1473
+ + )
1474
+
1475
+ > setClass(
1476
+ + Class="PartitionSimple",
1477
+ + representation=representation(part="factor"),
1478
+ + contains="PartitionFather"
1479
+ + )
1480
+
1481
+ > setClass(
1482
+ + Class="PartitionEval",
1483
+ + representation=representation(part="ordered"),
1484
+ + contains="PartitionFather"
1485
+ + )
1486
+
1487
+ > setGeneric("nbMultTwo",function(object){standardGeneric("nbMultTwo")})
1488
+
1489
+ > setMethod("nbMultTwo","PartitionFather",
1490
+ + function(object){
1491
+ + object@nbGroups <- object@nbGroups*2
1492
+ + return (object)
1493
+ + }
1494
+ + )
1495
+ ```
1496
+
1497
+ Since Ruby has no type definition, there is no really need for a parent class and subclasses.
1498
+ However, we will implement those classes in order to show Ruby's inheritance:
1499
+
1500
+ ```{ruby}
1501
+ # Parent class. Differently from SS4, both 'nb_groups' and 'part' are defined in the
1502
+ # parent class.
1503
+ class PartitionFather
1504
+
1505
+ attr_reader :nb_groups
1506
+ attr_reader :part
1507
+
1508
+ # initialize class PartitionFather with the number of groups and parts. Note that we
1509
+ # use R.c for nb_groups in order to convert the number of groups into an R vector.
1510
+ def initialize(nb_groups: 0, part: nil)
1511
+ @nb_groups = R.c(nb_groups)
1512
+ @part = part
1513
+ end
1514
+
1515
+ # method nb_mult_two can be called from all subclasses
1516
+ def nb_mult_two
1517
+ @nb_groups * 2
1518
+ end
1519
+
1520
+ # method 'to_s' is called whenever we try to print a Ruby object. This method emulates
1521
+ # R 'print' method that prints all the slots.
1522
+ def to_s
1523
+ puts ("Variable 'nb_groups':")
1524
+ puts @nb_groups
1525
+ puts
1526
+ puts ("Variable 'part':")
1527
+ puts @part
1528
+ puts
1529
+ end
1530
+
1531
+ end
1532
+
1533
+ # Class PartitionSimple is a subclass of PartitionFather. To make a subclass of a
1534
+ # class we use the operator '<'. Since the whole logic is in the parent class
1535
+ # PartitionSimple is just an empty class
1536
+ class PartitionSimple < PartitionFather
1537
+
1538
+ end
1539
+
1540
+ # PartitionEval is also only an empty class
1541
+ class PartitionEval < PartitionFather
1542
+
1543
+ end
1544
+ ```
1545
+
1546
+ ```{ruby}
1547
+ @a = PartitionSimple.new(nb_groups: 3, part: ((~R[:LETTERS])[R.c(1, 2, 3, 2, 2, 1)].factor))
1548
+ puts @a
1549
+ ```
1550
+
1551
+ ```{ruby}
1552
+ puts @a.nb_mult_two
1553
+ ```
1554
+
1555
+ ```{ruby}
1556
+ @b = PartitionEval.new(nb_groups: 5, part: (~R[:LETTERS])[R.c(1, 5, 3, 4, 2, 4)].ordered)
1557
+ puts @b
1558
+ ```
1559
+
1560
+ ```{ruby}
1561
+ puts @b.nb_mult_two
1562
+ ```
1563
+
1564
+ The example above, although it replicates SS4 is not actually very useful from the point of
1565
+ view of class hierarchy in Ruby. We will then write a new function to_s in class
1566
+ PartitionSimple that will print the name of the class:
1567
+
1568
+ ```{ruby}
1569
+ class PartitionSimple
1570
+
1571
+ def to_s
1572
+ puts("Class PartitionSimple")
1573
+ super
1574
+ end
1575
+
1576
+ end
1577
+ ```
1578
+
1579
+ ```{ruby}
1580
+ puts @a
1581
+ ```
1582
+
1583
+ As can be seen, 'puts @a' now calls method 'to_s' defined in class PartitionSimple. This
1584
+ method prints 'Class PartitionSimple' and then calls the super method, i.e., method 'to_s'
1585
+ from class PartitionFather.
1586
+
1587
+ Note though that 'puts @b' still prints the same output, since it has no particular 'to_s'
1588
+ method.
1589
+
1590
+ ```{ruby}
1591
+ puts @b
1592
+ ```
1593
+
1594
+ # Internal Modification of an Object
1595
+
1596
+
1597
+ ## Method to Modify a Field
1598
+
1599
+ Let us return to our trajectories example and define a third method that imputes data for
1600
+ missing values. To simplify, we will impute by replacing by the mean values. This is the R
1601
+ code to do this:
1602
+
1603
+ ```
1604
+ > meanWithoutNa <- function (x){mean(x,na.rm=TRUE)}
1605
+ > setGeneric("impute",function (.Object){standardGeneric("impute")})
1606
+ > setMethod(
1607
+ + f="impute",
1608
+ + signature="Trajectories",
1609
+ + def=function(.Object){
1610
+ + average <- apply(.Object@traj,2,meanWithoutNa)
1611
+ + for (iCol in 1:ncol(.Object@traj)){
1612
+ + .Object@traj[is.na(.Object@traj[,iCol]),iCol] <- average[iCol]
1613
+ + }
1614
+ + return(.Object)
1615
+ + }
1616
+ + )
1617
+ ```
1618
+
1619
+ The code above, as explained in SS4 creates a new object and does not change the original one.
1620
+ So, calling impute(trajCochin) will work correctly by creating a new object but will not
1621
+ change trajCochin. This works fine, but can be memory expensive if the matrix is a large
1622
+ one.
1623
+
1624
+ Let's now implement the same method in Galaaz 2.0. We stay on the R side of the bridge:
1625
+ for each column, compute the mean with `na.rm = true`, then replace NA entries with that mean
1626
+ (via `R.ifelse` / `is__na`), and rebuild the matrix with `R.cbind`. No MDArray iteration is
1627
+ required.
1628
+
1629
+ ```{ruby}
1630
+ class Trajectories
1631
+
1632
+ def impute
1633
+ ncols = @matrix.ncol >> 0
1634
+ imputed = (1..ncols).map do |j|
1635
+ col = @matrix[nil, j]
1636
+ avg = col.mean(na__rm: true)
1637
+ R.ifelse(col.is__na, avg, col)
1638
+ end
1639
+ col_names = @matrix.colnames
1640
+ row_names = @matrix.rownames
1641
+ @matrix = R.cbind(*imputed)
1642
+ @matrix.colnames = col_names unless col_names.nil?
1643
+ @matrix.rownames = row_names unless row_names.nil?
1644
+ self
1645
+ end
1646
+
1647
+ end
1648
+ ```
1649
+
1650
+ ```{ruby}
1651
+ @trajCochin.impute
1652
+ puts @trajCochin.matrix
1653
+ ```
1654
+
1655
+ It works, and `@trajCochin.matrix` was updated. Under GNU R, assignment follows R's usual
1656
+ copy-on-write semantics: replacing `@matrix` (or assigning into an R object through the bridge)
1657
+ binds a new vector/matrix rather than mutating a shared MDArray store. That is a deliberate
1658
+ difference from the Renjin/MDArray mutation experiments in the older paper; those demos are not
1659
+ part of Galaaz 2.0.
1660
+
1661
+ # Conclusions I
1662
+
1663
+ This ends the SS4 paper material for classes and inheritance. We believe we have shown that R S4
1664
+ can be substituted by Galaaz and Ruby classes and that Galaaz makes an easy transition from R
1665
+ developers to Ruby. Ruby is a very flexible and powerful language and has many interesting
1666
+ libraries, where Rails is maybe one of the best known, but there are thousands of others. For
1667
+ those interested in getting deeper into Ruby's libraries, we suggest they look at:
1668
+
1669
+ * https://github.com/markets/awesome-ruby
1670
+ * http://bestgems.org/
1671
+
1672
+ For those interested in Ruby and science, we recommend:
1673
+
1674
+ * http://sciruby.com/
1675
+
1676
+ **Galaaz 2.0** runs on **JRuby** and talks to **GNU R** through the bridge described in this
1677
+ series — the same integration model used throughout the examples above.
1678
+
1679
+ # Callbacks and R calling into Ruby
1680
+
1681
+ On this paper we have focused on accessing R functions from Ruby and have shown how to
1682
+ integrate Ruby with R from the point of view of a Ruby developer. The complementary direction —
1683
+ R calling back into Ruby — is also supported in Galaaz 2.0.
1684
+
1685
+ Galaaz 2.0 uses the **bridge callback** mechanism: Ruby procs (and related callables) can be
1686
+ passed where R expects functions, so algorithms written in R (for example optimizers or higher-order
1687
+ `*apply` helpers) can invoke Ruby logic without leaving the bridge session. Details, options such
1688
+ as callback timeouts, and further examples are in the project manual and on the documentation site:
1689
+ [https://rbotafogo.github.io/galaaz/](https://rbotafogo.github.io/galaaz/).
1690
+
1691
+ We do not reproduce here the older Renjin-era material on packing Ruby objects as R external
1692
+ pointers, constructing Ruby classes from R via JVM APIs, or calling Java collections from R
1693
+ scripts. Those sections belonged to a different runtime; the callback bridge is the supported
1694
+ path in Galaaz 2.0.
1695
+
1696
+ # Conclusions II
1697
+
1698
+ **JRuby + GNU R + Galaaz** gives a practical polyglot stack: idiomatic Ruby for structure and
1699
+ libraries, GNU R for statistics and the CRAN/Bioconductor ecosystem, and Galaaz as the bridge
1700
+ between them. As always, choose the right tools for the job at hand — and when the job sits
1701
+ between an R-only workflow and a broader polyglot application, Galaaz is designed to connect those
1702
+ worlds.