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
@@ -1,11 +1,17 @@
1
1
  ---
2
2
  title: "Galaaz Manual"
3
- subtitle: "How to tightly couple Ruby and R in GraalVM"
3
+ subtitle: "R-on-Rails: GNU R meets Ruby for the web"
4
4
  author: "Rodrigo Botafogo"
5
- tags: [Galaaz, Ruby, R, TruffleRuby, FastR, GraalVM, ggplot2]
6
- date: "2019"
7
- bibliography: "/home/rbotafogo/Bibliography/stats.bib"
5
+ tags: [Galaaz, "R-on-Rails", Ruby, Rails, JRuby, R, "GNU R", ggplot2, knitr, dplyr, Bioconductor, Arrow]
6
+ date: "2026"
7
+ bibliography: "../../examples/Bibliography/stats.bib"
8
8
  output:
9
+ html_document:
10
+ self_contained: true
11
+ keep_md: true
12
+ toc: true
13
+ toc_depth: 3
14
+ number_sections: true
9
15
  pdf_document:
10
16
  includes:
11
17
  in_header: "../../sty/galaaz.sty"
@@ -13,15 +19,15 @@ output:
13
19
  number_sections: yes
14
20
  toc: true
15
21
  toc_depth: 3
16
- html_document:
17
- self_contained: true
18
- keep_md: true
19
22
  md_document:
20
23
  variant: markdown_github
21
24
  fontsize: 11pt
22
25
  ---
23
26
 
24
27
  ```{ruby setup, echo=FALSE}
28
+ # Bridge default is 60s; some chunks (Arrow, large dplyr pipes) need more.
29
+ ENV['GALAAZ_BRIDGE_TIMEOUT_SEC'] ||= '300'
30
+
25
31
  R.options(crayon__enabled: false)
26
32
  R.install_and_loads('kableExtra')
27
33
  ```
@@ -32,8 +38,9 @@ Galaaz is a system for tightly coupling Ruby and R. Ruby is a powerful language,
32
38
  community, a very large set of libraries and great for web development. However, it lacks
33
39
  libraries for data science, statistics, scientific plotting and machine learning. On the
34
40
  other hand, R is considered one of the most powerful languages for solving all of the above
35
- problems. Maybe the strongest competitor to R is Python with libraries such as NumPy,
36
- Panda, SciPy, SciKit-Learn and a couple more.
41
+ problems. **Python** is a strong competitor: NumPy, pandas, SciPy, and scikit-learn are
42
+ widely used building blocks, and **PyPI** hosts many thousands of other packages for
43
+ numerical work, machine learning, and beyond.
37
44
 
38
45
  With Galaaz we do not intend to re-implement any of the scientific libraries in R, we allow
39
46
  for very tight coupling between the two languages to the point that the Ruby developer does
@@ -44,59 +51,39 @@ general-purpose programming language. It was designed and developed in the mid-1
44
51
  "Matz" Matsumoto in Japan." It reached high popularity with the development of Ruby on Rails
45
52
  (RoR) by David Heinemeier Hansson. RoR is a web application framework first released
46
53
  around 2005. It makes extensive use of Ruby's metaprogramming features. With RoR,
47
- Ruby became very popular. According to [Ruby's Tiobe index](https://www.tiobe.com/tiobe-index/ruby/)
48
- it peeked in popularity around 2008, then declined until 2015 when it started picking up again.
49
- At the time of this writing (November 2018), the Tiobe index puts Ruby in 16th position as
50
- most popular language.
54
+ Ruby became very popular. According to [Ruby’s place in the TIOBE index](https://www.tiobe.com/tiobe-index/ruby/)
55
+ it peaked in popularity around 2008, then declined until 2015 when it started picking up again.
56
+ Ruby remains a significant language in web development and general-purpose scripting.
51
57
 
52
58
  Python, a language similar to Ruby, ranks 4th in the index. Java, C and C++ take the
53
59
  first three positions. Ruby is often criticized for its focus on web applications.
54
60
  But Ruby can do [much more](https://github.com/markets/awesome-ruby) than just web applications.
55
- Yet, for scientific computing, Ruby lags way behind Python and R. Python has
56
- Django framework for web, NumPy for numerical arrays, Pandas for data analysis.
61
+ Yet, for scientific computing, Ruby lags behind Python and R. Python offers Django and
62
+ similar frameworks for the web, plus NumPy, pandas, and a deep catalog of science and ML libraries.
57
63
  R is a free software environment for statistical computing and graphics with thousands
58
64
  of libraries for data analysis.
59
65
 
60
66
  Until recently, there was no real perspective for Ruby to bridge this gap.
61
67
  Implementing a complete scientific computing infrastructure would take too long.
62
- Enters [Oracle's GraalVM](https://www.graalvm.org/):
63
-
64
- > GraalVM is a universal virtual machine for running applications written in
65
- > JavaScript, Python 3, Ruby, R, JVM-based languages like Java, Scala, Kotlin,
66
- > and LLVM-based languages such as C and C++.
67
- >
68
- > GraalVM removes the isolation between programming languages and enables
69
- > interoperability in a shared runtime. It can run either standalone or in the
70
- > context of OpenJDK, Node.js, Oracle Database, or MySQL.
71
- >
72
- > GraalVM allows you to write polyglot applications with a seamless way to pass
73
- > values from one language to another. With GraalVM there is no copying or
74
- > marshaling necessary as it is with other polyglot systems. This lets you
75
- > achieve high performance when language boundaries are crossed. Most of the time
76
- > there is no additional cost for crossing a language boundary at all.
77
- >
78
- > Often developers have to make uncomfortable compromises that require them
79
- > to rewrite their software in other languages. For example:
80
- >
81
- > * That library is not available in my language. I need to rewrite it.
82
- > * That language would be the perfect fit for my problem, but we cannot
83
- > run it in our environment.
84
- > * That problem is already solved in my language, but the language is
85
- > too slow.
86
- >
87
- > With GraalVM we aim to allow developers to freely choose the right language for
88
- > the task at hand without making compromises.
89
-
90
- As stated above, GraalVM is a _universal_ virtual machine that allows Ruby and R (and other
91
- languages) to run on the same environment. GraalVM allows polyglot applications to
92
- _seamlessly_ interact with one another and pass values from one language to the other.
93
- Although a great idea, GraalVM still requires application writers to know several languages.
94
- To eliminate that requirement, we built Galaaz, a gem for Ruby, to tightly couple
95
- Ruby and R and allow those languages to interact in a way that the user will be unaware
96
- of such interaction. In other words, a Ruby programmer will be able to use all
97
- the capabilities of R without knowing the R syntax.
98
-
99
- Library wrapping is a usual way of bringing features from one language into another.
68
+
69
+ **Galaaz 2.0** couples **JRuby** (Ruby on the JVM) with **GNU R**—the same R you use for
70
+ CRAN and Bioconductor. Ruby and R run in **separate processes**; the **Galaaz bridge**
71
+ sends requests to R and returns results to Ruby. From your point of view you still write
72
+ Ruby: `R.c(...)`, `R.library('ggplot2')`, `~R[:mtcars]`, and dplyr-style chains on R objects.
73
+ You do not need to learn R syntax to get a lot done, though reading R documentation for
74
+ individual packages remains useful.
75
+
76
+ Earlier experiments with Galaaz used Oracle’s **GraalVM** with TruffleRuby and FastR so that
77
+ Ruby and R could share one runtime. That path is no longer the focus: **standard GNU R**
78
+ gives full compatibility with the R package ecosystem (including compiled extensions and
79
+ Bioconductor) while JRuby gives a mature Ruby with **real multithreading** for application
80
+ and I/O code.
81
+
82
+ The bridge handles **communication and typing** between the two worlds; large tables can
83
+ also flow through **Apache Arrow** on the R side when you use the optional helpers described
84
+ later in this manual.
85
+
86
+ Library wrapping is a common way to bring features from one language into another.
100
87
  To improve performance, Python often wraps more efficient C libraries. For the
101
88
  Python developer, the existence of such C libraries is hidden. The problem with
102
89
  library wrapping is that for any new library, there is the need to handcraft a new
@@ -121,27 +108,203 @@ Galaaz is the Portuguese name for "Galahad". From Wikipedia:
121
108
  His name should not be mistaken with Galehaut, a different knight from
122
109
  Arthurian legend.
123
110
 
111
+ # Command-line tools (`bin/`)
112
+
113
+ The Galaaz repository ships many helpers under **`bin/`**. When working from a **clone**, call
114
+ them as **`bin/<name>`** from the project root (or `./bin/<name>`). If you install the **gem**,
115
+ only a subset is guaranteed on your `PATH` (see the gemspec: **`galaaz`**, **`gstudio`**, **`gknit`**, **`grun`**, **`gknit-draft`**); for development and CI, prefer the **`bin/`** copies so JVM flags and paths stay correct.
116
+
117
+ Below, **current (Galaaz 2.0 + JRuby or CRuby + GNU R)** means the tool uses **`bin/galaaz-ruby`**
118
+ / **`GALAAZ_RUBY`** (default: `ruby` on `PATH`) and applies JVM flags only when the interpreter is
119
+ JRuby (`bin/galaaz_jruby_env.inc.sh` / `lib/galaaz_jruby.rb`). **`bin/galaaz-jruby`** forces JRuby.
120
+ **Legacy** means the script still targets **GraalVM** polyglot Ruby / FastR-era invocation and is
121
+ **not** expected to work on a typical JRuby or CRuby NewBridge setup.
122
+
123
+ **Table layout:** names in the first column are **`bin/`** filenames (run as `bin/<name>` from the repo root). Long options and examples sit **outside** the tables so PDF columns stay readable.
124
+
125
+ ```{r bin-tables-helper, echo=FALSE}
126
+ bin_tbl <- function(df) {
127
+ k <- knitr::kable(df, row.names = FALSE, booktabs = TRUE, linesep = "",
128
+ col.names = c("Script", "Role", "2.0?"))
129
+ if (knitr::is_latex_output()) {
130
+ k <- kableExtra::kable_styling(k, font_size = 9, latex_options = "scale_down")
131
+ k <- kableExtra::column_spec(k, 1, width = "2.5cm")
132
+ k <- kableExtra::column_spec(k, 2, width = "9.5cm")
133
+ k <- kableExtra::column_spec(k, 3, width = "2.8cm")
134
+ } else {
135
+ k <- kableExtra::kable_styling(k, bootstrap_options = c("striped", "condensed"), full_width = TRUE)
136
+ }
137
+ k
138
+ }
139
+ ```
140
+
141
+ ```{r bin-tables-bootstrap, echo=FALSE}
142
+ df_boot <- data.frame(
143
+ Script = c("galaaz-bootstrap", "galaaz-jruby", "galaaz_jruby_env.inc.sh", "install-tinytex"),
144
+ Role = c(
145
+ "WSL2 helper: Docker checks; optional TinyTeX or poppler for gKnit PDF.",
146
+ "JRuby with repo lib/ on LOAD_PATH and required JVM flags (e.g. Arrow).",
147
+ "Sourced by bash wrappers; sets GALAAZ_REQUIRED_JRUBY_J_ARGS.",
148
+ "Install TinyTeX for PDF output."
149
+ ),
150
+ X2 = c("Yes*", "Yes", "Yes†", "Yes"),
151
+ stringsAsFactors = FALSE
152
+ )
153
+ bin_tbl(df_boot)
154
+ ```
155
+
156
+ \* Where WSL/Docker apply. **`galaaz-bootstrap` flags:** `--check`, `--apply`, `--runtime` (`docker` \| `local` \| `auto`), `--[no-]prompt-doc-tools`.
157
+
158
+ † Not run directly.
159
+
160
+ **`galaaz-jruby` examples** (from repo root):
161
+
162
+ ```text
163
+ bin/galaaz-jruby my_script.rb
164
+ bin/galaaz-jruby -S rspec
165
+ ```
166
+
167
+ ## Interactive use, examples, and Rake
168
+
169
+ ```{r bin-tables-interactive, echo=FALSE}
170
+ df_ix <- data.frame(
171
+ Script = c("gstudio", "run_example", "galaaz"),
172
+ Role = c(
173
+ "IRB or Pry with Galaaz preloaded (JRuby + JVM flags).",
174
+ "Run one Ruby file using the same JRuby/JVM setup as tests.",
175
+ "Forward arguments to rake (needs rake; usually JRuby)."
176
+ ),
177
+ X2 = c("Yes", "Yes", "Yes"),
178
+ stringsAsFactors = FALSE
179
+ )
180
+ bin_tbl(df_ix)
181
+ ```
182
+
183
+ ## gKnit and document drafts
184
+
185
+ ```{r bin-tables-gknit, echo=FALSE}
186
+ df_gk <- data.frame(
187
+ Script = c("gknit", "gknit-draft", "gknit-draft.rb", "gknit_Rscript"),
188
+ Role = c(
189
+ "Knit .Rmd via JRuby and R Markdown render.",
190
+ "Drafts from rticles-style templates; wrapper still uses legacy polyglot ruby.",
191
+ "Ruby entry: GKnit.draft (use with JRuby + LOAD_PATH).",
192
+ "Polyglot Rscript launcher; hard-coded LOAD_PATH sample."
193
+ ),
194
+ X2 = c("Yes", "Legacy", "JRuby", "No"),
195
+ stringsAsFactors = FALSE
196
+ )
197
+ bin_tbl(df_gk)
198
+ ```
199
+
200
+ **`gknit` CLI** (see `gknit -h`): `--output_format`, `--output_file`, `--output_dir`, `--bridge_timeout_sec`, `--callback_timeout_ms`. If `--output_format` is omitted, the **first** YAML `output:` target wins.
201
+
202
+ Prefer **`galaaz-jruby`** for **`gknit-draft`** workflows until that wrapper matches the **`gknit`** stack.
203
+
204
+ ## Tests
205
+
206
+ ```{r bin-tables-tests, echo=FALSE}
207
+ df_ts <- data.frame(
208
+ Script = c("run_rspec", "run_all_rspec", "run_slow_rspec", "run_old_rspec", "run_rspec_subset"),
209
+ Role = c(
210
+ "Top-level specs/*_spec.rb with spec_helper (see docs/testing.md).",
211
+ "Compile ext/new_bridge; run specs/ and new_bridge_specs/ together.",
212
+ "Suites under slow-specs/ (read script header for spec_helper).",
213
+ "Legacy suites under old_specs/.",
214
+ "Numbered subset 1–18 (Documentation/Spec_Subsets.md)."
215
+ ),
216
+ X2 = c("Yes", "Yes", "Yes", "Yes", "Yes"),
217
+ stringsAsFactors = FALSE
218
+ )
219
+ bin_tbl(df_ts)
220
+ ```
221
+
222
+ ## Other
223
+
224
+ ```{r bin-tables-other, echo=FALSE}
225
+ df_ot <- data.frame(
226
+ Script = c("grun", "gstudio_irb.rb / gstudio_pry.rb"),
227
+ Role = c(
228
+ "Graal-era launcher: polyglot ruby with --jvm. Use galaaz-jruby -S instead.",
229
+ "Loaded by gstudio; not meant to be run standalone."
230
+ ),
231
+ X2 = c("No", "Yes"),
232
+ stringsAsFactors = FALSE
233
+ )
234
+ bin_tbl(df_ot)
235
+ ```
236
+
237
+ For day-to-day **2.0** use, rely on **`bin/galaaz-ruby`** (or **`bin/galaaz-jruby`** when you want to force JRuby), **`bin/gstudio`**, **`bin/gknit`**, **`bin/run_example`**, **`bin/run_rspec`** / **`bin/run_all_rspec`**, and **`bin/galaaz-bootstrap`** on WSL when using Dockerized R. Treat **`grun`**, **`gknit_Rscript`**, and the polyglot **`ruby`** invocation in **`gknit-draft`** as **legacy** until they are ported to the same launcher path as **`gknit`**.
238
+
124
239
  # System Compatibility
125
240
 
126
- * Oracle Linux 7
127
- * Ubuntu 18.04 LTS
128
- * Ubuntu 16.04 LTS
129
- * Fedora 28
130
- * macOS 10.14 (Mojave)
131
- * macOS 10.13 (High Sierra)
241
+ Typical development and CI targets:
132
242
 
133
- # Dependencies
243
+ * **Linux** — recent Ubuntu LTS or comparable distributions (x86_64).
244
+ * **macOS** — recent releases with JRuby and GNU R available.
245
+ * **Windows** — use **WSL2** (same Linux stack as above); native Windows is not the primary target.
134
246
 
135
- * TruffleRuby
136
- * FastR
247
+ The native **gatekeeper** component under `ext/new_bridge` is built with `make` and a C++ toolchain; see the project `README` if compilation fails on your platform.
137
248
 
249
+ # Dependencies
250
+
251
+ * **JRuby** — Galaaz 2.0 requires JRuby (tested with **10.1.1.0**) and a matching **JDK** (tested with **Java 21**). MRI Ruby is not supported.
252
+ * **GNU R** — `R` and `Rscript` on your `PATH` (tested with **4.3.3**), plus a C++ toolchain (`g++`, `make`) and the **Rcpp** package to compile the gatekeeper.
253
+ * **galaaz gem** — runtime dependency `msgpack` is pulled in by `gem install`.
254
+ * Optional: **Docker** — if you run R in a container (common on WSL2); see bootstrap below.
255
+ * Optional R packages for examples in this manual — e.g. `ggplot2`, `dplyr`, `knitr`, `kableExtra`, `arrow`, Bioconductor tools such as **DESeq2** (installed the usual R way).
138
256
 
139
257
  # Installation
140
258
 
141
- * Install GrallVM (http://www.graalvm.org/)
142
- * Install Ruby (gu install Ruby)
143
- * Install FastR (gu install R)
144
- * Install rake if you want to run the specs and examples (gem install rake)
259
+ The supported install is **`gem install` + compile the gatekeeper**. You do not need a git clone.
260
+
261
+ 1. Install **JRuby**, a compatible **JDK**, and **GNU R** (with `Rscript` and a C++ compiler).
262
+ 2. In R, install **Rcpp**: `install.packages("Rcpp")`.
263
+ 3. Install the gem: `jruby -S gem install galaaz`
264
+ 4. Compile the native gatekeeper from the installed gem:
265
+
266
+ ```
267
+ gem_dir="$(jruby -e "puts Gem::Specification.find_by_name('galaaz').full_gem_path")"
268
+ make -C "${gem_dir}/ext/new_bridge" all
269
+ ```
270
+
271
+ 5. Ensure **`R`** starts GNU R and can install packages (network access to CRAN when you first call `R.install_and_loads`). For **Apache Arrow** on Java 9+, pass `-J--add-opens=java.base/java.nio=ALL-UNNAMED` to JRuby (from a checkout, `bin/galaaz-jruby` does this).
272
+
273
+ For **gKnit**, **knitr**, **rmarkdown**, and LaTeX (PDF output), install the corresponding R packages, **Pandoc**, and a TeX distribution if you need PDF; the repository includes helpers such as **`bin/install-tinytex`** where appropriate.
274
+
275
+ A **table of all `bin/` scripts** (bootstrap, JRuby wrapper, gstudio, gknit, test runners, and which ones are legacy) is in the section **Command-line tools (`bin/`)** earlier in this manual.
276
+
277
+ ### From a repository checkout (contributors)
278
+
279
+ 1. Install **bundler** if needed, then run **`jruby -S bundle install`** in the repository root.
280
+ 2. Build the bridge native code: **`make -C ext/new_bridge all`** (or **`rake compile_gatekeeper`**).
281
+ 3. Run scripts with **`bin/galaaz-jruby`** (sources **`bin/galaaz_jruby_env.inc.sh`** and adds **`-I lib`**).
282
+
283
+ A **gstudio** try image (JRuby + R + Galaaz already installed) is **`docker run --rm -it ghcr.io/rbotafogo/galaaz-try:gstudio`** (or **`./docker/try-gstudio/run.sh`** from a checkout). Maintainers can prove a RubyGems install on a throwaway Ubuntu machine (no repo inside the container) with **`./docker/cold-install/run.sh published-specs`**.
284
+
285
+ ## Windows + WSL2 (optional: Docker / R in a container)
286
+
287
+ If you run Galaaz on Windows through WSL2 and want containerized R instances,
288
+ Docker Desktop is the supported setup.
289
+
290
+ 1. Install Docker Desktop on Windows:
291
+ - https://www.docker.com/products/docker-desktop/
292
+ 2. Open Docker Desktop and enable WSL integration:
293
+ - Settings > Resources > WSL Integration
294
+ - Enable integration for your target distro
295
+ - Apply & Restart Docker Desktop
296
+ 3. In WSL, run Galaaz bootstrap:
297
+
298
+ > ruby bin/galaaz-bootstrap --apply
299
+ > ruby bin/galaaz-bootstrap --check
300
+
301
+ Expected result:
302
+ - docker CLI available
303
+ - docker compose available
304
+ - docker daemon reachable (`docker info` works)
305
+
306
+ If bootstrap reports daemon is unreachable, check Docker Desktop is running and
307
+ WSL integration is enabled for the distro where Galaaz is installed.
145
308
 
146
309
  # Usage
147
310
 
@@ -170,7 +333,7 @@ Galaaz is the Portuguese name for "Galahad". From Wikipedia:
170
333
 
171
334
  > galaaz -T
172
335
 
173
- Shows a list with all available executalbe tasks. To execute a task, substitute the
336
+ Shows a list with all available executable tasks. To execute a task, substitute the
174
337
  'rake' word in the list with 'galaaz'. For instance, the following line shows up
175
338
  after 'galaaz -T'
176
339
 
@@ -180,20 +343,230 @@ Galaaz is the Portuguese name for "Galahad". From Wikipedia:
180
343
 
181
344
  > galaaz master_list:scatter_plot
182
345
 
346
+ # JRuby, CRuby, multithreading, and the R bridge
347
+
348
+ Galaaz 2.0 supports **JRuby** and **CRuby** equally for NewBridge. On **JRuby**, your
349
+ application can use **real parallel threads** for I/O-bound work (HTTP clients, database
350
+ connections, message consumers, and so on). On **CRuby**, prefer multi-process scaling for
351
+ CPU-bound concurrency. R itself is still executed in a **single GNU R process** behind the
352
+ Galaaz bridge (use multiple R workers when you need more R throughput).
353
+
354
+ When several Ruby threads call into R at the same time, the bridge **serializes** those calls:
355
+ each request is matched to a reply using an internal per-call **queue**, so you do not need to
356
+ add your own mutex around every `R.foo` from application threads. (You should still use normal
357
+ Ruby synchronization when **Ruby** data structures are shared between threads—for example, when
358
+ appending rows from each thread into a shared array before sending them to R.)
359
+
360
+ A practical pattern is:
361
+
362
+ 1. Use threads (or a connection pool) to read from **multiple databases or shards** in parallel.
363
+ 2. Merge the rows in Ruby under a `Mutex` if you collect into one structure.
364
+ 3. Hand the merged table to R **once** (for example with `R::Arrow.from_ruby_batches` and dplyr,
365
+ or by building a data frame) so heavy statistics run in R with fewer bridge round-trips.
366
+
367
+ A runnable sketch lives in
368
+ `examples/multithread_shards_to_r/shards_to_r.rb` (simulated shard queries; swap in your DB
369
+ driver). For concurrency tests on the bridge itself, see `specs/bridge_concurrent_spec.rb` and
370
+ `specs/arrow_from_ruby_batches_spec.rb`.
371
+
372
+ ## Long-running R calls and a completion block
373
+
374
+ For R work that can take a long time, the bridge can avoid a Ruby-side **wait timeout** by
375
+ scheduling the call and resuming in a **block** when the `RET` arrives.
376
+
377
+ - **`R.eval_r_async(code, timeout: nil) { |result| ... }`** — string eval; on success, `result.value`
378
+ is the same formatted string as **`R.eval_r`** (use `timeout: nil` for no Ruby-side limit).
379
+ - **`R::Async.<rname>(...) { |result| ... }`** — same dispatch as **`R.<rname>(...)`**, but async;
380
+ on success, `result.value` is an **`R::Object`** (or unboxed Ruby value / Symbol), like synchronous
381
+ **`R.<rname>`**. Optional keyword **`timeout:`** applies a Ruby-side wait limit (completion receives
382
+ **`NewBridge::SessionClient::TimeoutError`** if R is too slow).
383
+
384
+ **Important:** **`R.foo(...) { |x| }`** is already used for dplyr-style scopes (`R::Support.new_scope`),
385
+ so async R calls must use **`R::Async`** or **`R.eval_r_async`**, not a bare **`R.foo` with a block.**
386
+
387
+ `NewBridge::EvalResult` exposes **`#ok?`**, **`#value`**, and **`#error`**. The completion block runs on a
388
+ **background thread** (not the bridge reader thread).
389
+
390
+ The example below is **plain Ruby** (no Rails). The R snippet sleeps (standing in for heavy work) and then
391
+ returns an integer so the success branch shows a **non-nil** value. (`Sys.sleep` alone returns **NULL** in R;
392
+ on success **`result.value`** is then **`nil`** in Ruby—that is expected, not a bridge error.)
393
+
394
+ ```{ruby long_r_completion_block}
395
+ require 'thread'
396
+
397
+ completion = Queue.new
398
+
399
+ R.eval_r_async('({ Sys.sleep(0.3); 42L })', timeout: nil) do |result|
400
+ if result.ok?
401
+ puts "[completion] R finished; eval_r-style value: #{result.value.inspect}"
402
+ else
403
+ puts "[completion] R/bridge error: #{result.error.class}: #{result.error.message}"
404
+ end
405
+ completion.push(:done)
406
+ end
407
+
408
+ 3.times do |i|
409
+ puts "[main] other Ruby work step #{i + 1}"
410
+ sleep 0.05
411
+ end
412
+
413
+ completion.pop
414
+ puts "[main] R completion has run; exiting."
415
+ ```
416
+
417
+ In a **web application**, the HTTP response usually ends before R finishes, so you would not
418
+ `Queue#pop` in the controller; you would persist an identifier, let the completion block write
419
+ the outcome to storage, and notify the client (poll, WebSocket, Turbo Stream, etc.). The plain
420
+ Ruby pattern above is only to show **when** the result exists (inside the block, or after data
421
+ written there is observed elsewhere). Runnable specs live in **`new_bridge_specs/eval_r_async_spec.rb`**.
422
+
423
+ ## Galaaz + Rails (R-on-Rails) integration baseline
424
+
425
+ This is the practical **R-on-Rails** starter: an R scientist’s analysis behind a small Rails
426
+ app. The baseline we used in WSL aimed at:
427
+
428
+ 1. Rails boots under **JRuby or CRuby** (same bridge; see Installation).
429
+ 2. Galaaz is loaded from a local checkout (before publishing to RubyGems).
430
+ 3. A request path can execute **`R.eval(...)`** and return a result.
431
+
432
+ ### 1) Create the app with Ruby-friendly options
433
+
434
+ Rails defaults can pull gems that are awkward on some setups (for example sqlite native
435
+ extension paths on JRuby, or deployment extras you do not need). A minimal app avoids early friction:
436
+
437
+ ```bash
438
+ cd /home/rbotafogo/desenv_linux
439
+ jruby -S rails new hedi --skip-git --minimal --skip-kamal --skip-solid --skip-active-record
440
+ ```
441
+
442
+ Then install gems:
443
+
444
+ ```bash
445
+ cd /home/rbotafogo/desenv_linux/hedi
446
+ jruby -S bundle install
447
+ ```
448
+
449
+ ### 2) Use Galaaz as a local path gem
450
+
451
+ For local development we keep a stable path:
452
+
453
+ - `~/gems/galaaz` -> symlink to your Galaaz checkout
454
+ - optional built gem archive in `~/gems/pkg/`
455
+
456
+ In Rails `Gemfile`:
457
+
458
+ ```ruby
459
+ gem "galaaz", path: "/home/rbotafogo/gems/galaaz", require: false
460
+ ```
461
+
462
+ And load after Rails boot in `config/application.rb`:
463
+
464
+ ```ruby
465
+ config.after_initialize { require "galaaz" }
466
+ ```
467
+
468
+ Why `require: false` + `after_initialize`? In this integration, loading Galaaz too early via
469
+ `Bundler.require` triggered Rails/JRuby initialization failures.
470
+
471
+ ### 3) Simple request-path smoke test
472
+
473
+ A direct smoke test from Rails runner:
474
+
475
+ ```bash
476
+ cd /home/rbotafogo/desenv_linux/hedi
477
+ jruby -S bundle exec rails runner "puts R.eval('sum(c(1,2,3,4,5))').inspect"
478
+ ```
479
+
480
+ Expected output:
481
+
482
+ ```text
483
+ 15.0
484
+ ```
485
+
486
+ ### 4) HTTP endpoint pattern
487
+
488
+ For this baseline, a small Rack endpoint was the most stable first step to prove request-time R
489
+ evaluation. (A full ActionController stack can be enabled later as the app evolves.)
490
+
491
+ Minimal pattern:
492
+
493
+ 1. Define a Rack app class under `lib/` that runs `R.eval(...)` and returns HTML/JSON.
494
+ 2. Point a route to that Rack app (`root to: MyRackApp`).
495
+ 3. Verify with browser/curl.
496
+
497
+ ### 5) Running from WSL and opening from Windows
498
+
499
+ Recommended bind:
500
+
501
+ ```bash
502
+ jruby -S bundle exec rails server -b 0.0.0.0 -p 3000
503
+ ```
504
+
505
+ Then open from Windows:
506
+
507
+ - `http://localhost:3000` (usually works with WSL localhost forwarding), or
508
+ - `http://<wsl-ip>:3000` if needed.
509
+
510
+ In development, if Host Authorization blocks requests with unexpected Host headers, use:
511
+
512
+ ```ruby
513
+ # config/environments/development.rb
514
+ config.hosts.clear
515
+ ```
516
+
517
+ ### 6) Troubleshooting checklist
518
+
519
+ - `Could not find ... in locally installed gems`:
520
+ run `jruby -S bundle install` in the Rails app directory.
521
+ - Stale PID after crash:
522
+ remove `tmp/pids/server.pid`.
523
+ - Local Galaaz path changed:
524
+ verify `Gemfile` path target exists and rerun bundler.
525
+ - R runtime issues:
526
+ confirm GNU R is installed and on `PATH` in the same shell where Rails runs.
527
+
528
+ As new Rails features are added (controllers, jobs, websockets, background rendering, plot
529
+ generation), extend this section with concrete, runnable snippets and the associated operational
530
+ checks.
183
531
 
184
532
  # Accessing R from Ruby
185
533
 
186
- One of the nice aspects of Galaaz on GraalVM, is that variables and functions defined in R, can
187
- be easily accessed from Ruby. For instance, to access the 'mtcars' data frame from R
188
- in Ruby, we use the ':mtcar' symbol preceded by the '~' operator, thus '~:r_vec' retrieves the
189
- value of the 'mtcars' variable.
534
+ One of the nice aspects of Galaaz is that variables and functions defined in R can
535
+ be easily accessed from Ruby. For instance, to access the `mtcars` data frame from R
536
+ in Ruby, we use the symbol `:mtcars` preceded by the `~` operator: `~R[:mtcars]` retrieves the
537
+ value of the `mtcars` object in R.
190
538
 
191
539
  ```{ruby access_r}
192
- puts ~:mtcars
540
+ puts ~R[:mtcars]
541
+ ```
542
+
543
+ ## Scoped symbols and lexical scoping
544
+
545
+ Galaaz 2.0 uses **scoped symbols** by default. The canonical style is `R[:name]`:
546
+
547
+ - `~R[:mtcars]` fetches an R object by name.
548
+ - `R[:a] + R[:b]` builds an expression.
549
+ - `R[:year].up_to(R[:day])` builds range expressions.
550
+
551
+ If you prefer the terse `:x` syntax, you can opt in with lexical scoping using a Ruby refinement:
552
+
553
+ ```ruby
554
+ module MyScript
555
+ using Galaaz::SymbolDSL
556
+
557
+ def self.run
558
+ expr = :a + :b
559
+ puts expr
560
+ puts ~:mtcars
561
+ end
562
+ end
193
563
  ```
194
564
 
195
- To access an R function from Ruby, the R function needs to be preceeded by 'R.' scoping.
196
- Bellow we see and example of creating a R::Vector by calling the 'c' R function
565
+ `using Galaaz::SymbolDSL` is **lexically scoped**: only code in that module/file scope gets `:x` DSL behavior.
566
+ Outside that scope, plain Ruby `Symbol` behavior is unchanged.
567
+
568
+ To access an R function from Ruby, the R function needs to be preceded by `R.` scoping.
569
+ Below we see an example of creating a R::Vector by calling the 'c' R function
197
570
 
198
571
  ```{ruby call_r_func}
199
572
  puts vec = R.c(1.0, 2.0, 3.0, 4.0)
@@ -224,7 +597,7 @@ The call above to the 'c' function can also be done using '.' notation:
224
597
  ```{ruby concat_with_dot}
225
598
  puts vec.c(10, 20, 30)
226
599
  ```
227
- We will talk about vector indexing in a latter section. But notice here that indexing
600
+ We will talk about vector indexing in a later section. But notice here that indexing
228
601
  an R::Vector will return another R::Vector:
229
602
 
230
603
  ```{ruby indexing}
@@ -258,12 +631,12 @@ puts vec.map { |x| x + 2 }
258
631
 
259
632
  # gKnitting a Document
260
633
 
261
- This manual has been formatted usign gKnit. gKnit uses Knitr and R markdown to knit
262
- a document in Ruby or R and output it in any of the available formats for R markdown.
263
- gKnit runs atop of GraalVM, and Galaaz. In gKnit, Ruby variables are persisted between
634
+ This manual has been formatted using gKnit. gKnit uses knitr and R Markdown to knit
635
+ a document in Ruby or R and output it in any of the available formats for R Markdown.
636
+ gKnit runs with **JRuby**, **GNU R**, and Galaaz. In gKnit, Ruby variables are persisted between
264
637
  chunks, making it an ideal solution for literate programming. Also, since it is based
265
- on Galaaz, Ruby chunks can have access to R variables and Polyglot Programming with
266
- Ruby and R is quite natural.
638
+ on Galaaz, Ruby chunks can have access to R variables and combining Ruby with R in one
639
+ document is natural.
267
640
 
268
641
  The idea of "literate programming" was first introduced by Donald Knuth in the
269
642
  1980's [@Knuth:literate_programming].
@@ -282,7 +655,7 @@ single document or set of documents that when distributed to peers could be reru
282
655
  the same output and reports.
283
656
 
284
657
  The R community has put a great deal of effort in reproducible research. In 2002, Sweave was
285
- introduced and it allowed mixing R code with Latex generating high quality PDF documents. A
658
+ introduced and it allowed mixing R code with LaTeX, generating high-quality PDF documents. A
286
659
  Sweave document could include code, the results of executing the code, graphics and text
287
660
  such that it contained the whole narrative to reproduce the research. In
288
661
  2012, Knitr, developed by Yihui Xie from RStudio was released to replace Sweave and to
@@ -291,7 +664,7 @@ were necessary for Sweave.
291
664
 
292
665
  With Knitr, __R markdown__ was also developed, an extension to the
293
666
  Markdown format. With __R markdown__ and Knitr it is possible to generate reports in a multitude
294
- of formats such as HTML, markdown, Latex, PDF, dvi, etc. __R markdown__ also allows the use of
667
+ of formats such as HTML, Markdown, LaTeX, PDF, DVI, etc. __R markdown__ also allows the use of
295
668
  multiple programming languages such as R, Ruby, Python, etc. in the same document.
296
669
 
297
670
  In __R markdown__, text is interspersed with
@@ -326,7 +699,7 @@ Now, any single code has dozens of variables that we might want to use and reuse
326
699
  Clearly, such an approach becomes quickly unmanageable. Probably, because of
327
700
  this problem, it is very rare to see any __R markdown__ document in the Ruby community.
328
701
 
329
- When variables can be used accross chunks, then no overhead is needed:
702
+ When variables can be used across chunks, then no overhead is needed:
330
703
 
331
704
  ```{ruby persistence}
332
705
  lst = R.list(a: 1, b: 2, c: 3)
@@ -338,8 +711,8 @@ puts lst
338
711
  ```
339
712
 
340
713
  In the Python community, the same effort to have code and text in an integrated environment
341
- started around the first decade of 2000. In 2006 iPython 0.7.2 was released. In 2014,
342
- Fernando Pérez, spun off project Jupyter from iPython creating a web-based interactive
714
+ started around the first decade of the 2000s. In 2006 IPython 0.7.2 was released. In 2014,
715
+ Fernando Pérez spun off the Jupyter project from IPython, creating a web-based interactive
343
716
  computation environment. Jupyter can now be used with many languages, including Ruby with the
344
717
  iruby gem (https://github.com/SciRuby/iruby). In order to have multiple languages in a Jupyter
345
718
  notebook the SoS kernel was developed (https://vatlab.github.io/sos-docs/).
@@ -353,8 +726,8 @@ have in a single document, text and code.
353
726
 
354
727
  In gKnit, Ruby variables are persisted between
355
728
  chunks, making it an ideal solution for literate programming in this language. Also,
356
- since it is based on Galaaz, Ruby chunks can have access to R variables and Polyglot Programming
357
- with Ruby and R is quite natural.
729
+ since it is based on Galaaz, Ruby chunks can access R variables (`~R[:name]`, `R.*`) through the
730
+ **Galaaz bridge** while knitr drives **GNU R**—no GraalVM polyglot runtime is required.
358
731
 
359
732
  This is not a blog post on __R markdown__, and the interested user is directed to the following links
360
733
  for detailed information on its capabilities and use.
@@ -368,7 +741,7 @@ gKnitting Ruby and R documents quickly.
368
741
  ## The Yaml header
369
742
 
370
743
  An __R markdown__ document should start with a Yaml header and be stored in a file with
371
- '.Rmd' extension. This document has the following header for gKitting an HTML document.
744
+ '.Rmd' extension. This document has the following header for gKnitting an HTML document.
372
745
 
373
746
  ```
374
747
  ---
@@ -376,7 +749,7 @@ title: "How to do reproducible research in Ruby with gKnit"
376
749
  author:
377
750
  - "Rodrigo Botafogo"
378
751
  - "Daniel Mossé - University of Pittsburgh"
379
- tags: [Tech, Data Science, Ruby, R, GraalVM]
752
+ tags: [Tech, Data Science, Ruby, R, JRuby, Galaaz]
380
753
  date: "20/02/2019"
381
754
  output:
382
755
  html_document:
@@ -391,6 +764,35 @@ output:
391
764
 
392
765
  For more information on the options in the Yaml header, [check here](https://bookdown.org/yihui/rmarkdown/html-document.html).
393
766
 
767
+ ## Choosing the output format when calling gknit
768
+
769
+ Yes: you can select the render target on the **command line**. **`bin/gknit`** (or **`gknit`** on your `PATH`) forwards options to **`rmarkdown::render`** via **`R::Rmarkdown.render`**.
770
+
771
+ * **`--output_format FORMAT`** — name of the format, as in the YAML `output:` block. Examples:
772
+ * **`html_document`** — HTML (often the default you list first under `output:`).
773
+ * **`pdf_document`** — PDF (you need a working LaTeX setup, e.g. TinyTeX; see **`bin/install-tinytex`**).
774
+ * **`md_document`**, **`github_document`**, or any other format defined in your YAML.
775
+ * **`all`** — render **every** format declared under `output:` in the document (same idea as in R Markdown).
776
+
777
+ If you **omit** **`--output_format`**, gknit passes **`NULL`** for the format argument. In that case **rmarkdown** uses the **first** format listed under **`output:`** in the YAML (and if none is specified there, behavior follows the usual rmarkdown defaults, typically HTML).
778
+
779
+ Other useful flags:
780
+
781
+ * **`--output_file NAME`** — output file name (optional path; see also **`--output_dir`**).
782
+ * **`--output_dir DIR`** — directory for the rendered file (created if missing).
783
+ * **`--bridge_timeout_sec`** / **`--callback_timeout_ms`** — longer R or install steps (see elsewhere in this manual).
784
+
785
+ Examples (run from the directory where paths make sense, or use absolute paths):
786
+
787
+ ```text
788
+ bin/gknit blogs/manual/manual.Rmd
789
+ bin/gknit --output_format html_document blogs/manual/manual.Rmd
790
+ bin/gknit --output_format pdf_document blogs/manual/manual.Rmd
791
+ bin/gknit --output_format all blogs/manual/manual.Rmd
792
+ ```
793
+
794
+ Use **`gknit -h`** for the full option list.
795
+
394
796
  ## __R Markdown__ formatting
395
797
 
396
798
  Document formatting can be done with simple markups such as:
@@ -435,7 +837,7 @@ Running and executing Ruby and R code is actually what really interests us is th
435
837
  Inserting a code chunk is done by adding code in a block delimited by three back ticks
436
838
  followed by an open
437
839
  curly brace ('{') followed with the engine name (r, ruby, rb, include, ...), an
438
- any optional chunk_label and options, as shown bellow:
840
+ any optional chunk_label and options, as shown below:
439
841
 
440
842
  ````
441
843
  ```{engine_name [chunk_label], [chunk_options]}`r ''`
@@ -524,7 +926,7 @@ grammar of graphics" [@Wilkinson:grammar_of_graphics]. The idea of the grammar o
524
926
  is to build a graphics by adding layers to the plot. More information can be found in
525
927
  https://towardsdatascience.com/a-comprehensive-guide-to-the-grammar-of-graphics-for-effective-visualization-of-multi-dimensional-1f92b4ed4149.
526
928
 
527
- In the plot bellow the 'mpg' dataset from base R is used. "The data concerns city-cycle fuel
929
+ In the plot below the 'mpg' dataset from base R is used. "The data concerns city-cycle fuel
528
930
  consumption in miles per gallon, to be predicted in terms of 3 multivalued discrete and 5
529
931
  continuous attributes." (Quinlan, 1993)
530
932
 
@@ -713,17 +1115,17 @@ Here, for instance, is a table definition in HTML and its output in the document
713
1115
  </div>
714
1116
 
715
1117
  But manually creating HTML output is not always easy or desirable, specially
716
- if we intend the document to be rendered in other formats, for example, as Latex.
1118
+ if we intend the document to be rendered in other formats, for example, as LaTeX.
717
1119
  Also, The above
718
1120
  table looks ugly. The 'kableExtra' library is a great library for
719
1121
  creating beautiful tables. Take a look at https://cran.r-project.org/web/packages/kableExtra/vignettes/awesome_table_in_html.html
720
1122
 
721
1123
  In the next chunk, we output the 'mtcars' dataframe from R in a nicely formatted
722
- table. Note that we retrieve the mtcars dataframe by using '~:mtcars'.
1124
+ table. Note that we retrieve the mtcars dataframe by using '~R[:mtcars]'.
723
1125
 
724
1126
  ```{ruby nice_table}
725
1127
  R.install_and_loads('kableExtra')
726
- outputs (~:mtcars).kable.kable_styling
1128
+ outputs (~R[:mtcars]).kable.kable_styling
727
1129
  ```
728
1130
 
729
1131
  ## Including Ruby files in a chunk
@@ -751,7 +1153,7 @@ true, ruby's 'require\_relative' semantics is used to load the file, when false,
751
1153
  ```
752
1154
  ````
753
1155
 
754
- Bellow we include file 'model.rb', which is in the same directory of this blog.
1156
+ Below we include file 'model.rb', which is in the same directory of this blog.
755
1157
  This code uses R 'caret' package to split a dataset in a train and test sets.
756
1158
  The 'caret' package is a very important a useful package for doing Data Analysis,
757
1159
  it has hundreds of functions for all steps of the Data Analysis workflow. To
@@ -772,7 +1174,7 @@ will install the package if it is not already installed and can take a while.
772
1174
  ```
773
1175
 
774
1176
  ```{ruby model_partition}
775
- mtcars = ~:mtcars
1177
+ mtcars = ~R[:mtcars]
776
1178
  model = Model.new(mtcars, percent_train: 0.8)
777
1179
  model.partition(:mpg)
778
1180
  puts model.train.head
@@ -784,9 +1186,9 @@ puts model.test.head
784
1186
  gKnit also allows developers to document and load files that are not in the same directory
785
1187
  of the '.Rmd' file.
786
1188
 
787
- Here is an example of loading the 'find.rb' file from TruffleRuby. In this example, relative
788
- is set to FALSE, so Ruby will look for the file in its $LOAD\_PATH, and the user does not
789
- need to no it's directory.
1189
+ Here is an example of loading Ruby’s standard library file `find.rb`. In this example, relative
1190
+ is set to FALSE, so Ruby will look for the file in its `$LOAD_PATH`, and the user does not
1191
+ need to know its directory on disk.
790
1192
 
791
1193
  ````
792
1194
  ```{include find, relative = FALSE}`r ''`
@@ -808,9 +1210,9 @@ the Yaml header to generate this blog in PDF format instead of HTML:
808
1210
 
809
1211
  ```
810
1212
  ---
811
- title: "gKnit - Ruby and R Knitting with Galaaz in GraalVM"
1213
+ title: "gKnit - Ruby and R Knitting with Galaaz"
812
1214
  author: "Rodrigo Botafogo"
813
- tags: [Galaaz, Ruby, R, TruffleRuby, FastR, GraalVM, knitr, gknit]
1215
+ tags: [Galaaz, Ruby, R, JRuby, knitr, gknit]
814
1216
  date: "29 October 2018"
815
1217
  output:
816
1218
  pdf\_document:
@@ -822,7 +1224,7 @@ output:
822
1224
 
823
1225
  ## Template based documents generation
824
1226
 
825
- When a document is converted to PDF it follows a certain convertion template. We've seen above
1227
+ When a document is converted to PDF it follows a certain conversion template. We've seen above
826
1228
  the use of 'galaaz.sty' as a basic template to generate a PDF document. Using the
827
1229
  'gknit-draft' app that comes with Galaaz, the same .Rmd file can be compiled to different
828
1230
  looking PDF documents. Galaaz automatically loads the 'rticles' R package that comes with
@@ -872,14 +1274,14 @@ gknit-draft --filename my_r_article --template rjournal_article --package rticle
872
1274
 
873
1275
  # Accessing R variables
874
1276
 
875
- Galaaz allows Ruby to access variables created in R. For example, the 'mtcars' data set is
876
- available in R and can be accessed from Ruby by using the 'tilda' operator followed by the
877
- symbol for the variable, in this case ':mtcar'. In the code bellow method 'outputs' is
878
- used to output the 'mtcars' data set nicely formatted in HTML by use of the 'kable' and
879
- 'kable_styling' functions. Method 'outputs' is only available when used with 'gknit'.
1277
+ Galaaz allows Ruby to access variables created in R. For example, the `mtcars` data set is
1278
+ available in R and can be accessed from Ruby by using the tilde operator followed by the
1279
+ symbol for the variable, in this case `:mtcars`. In the code below, method `outputs` is
1280
+ used to output the `mtcars` data set nicely formatted in HTML by use of the `kable` and
1281
+ `kable_styling` functions. Method `outputs` is only available when used with gKnit.
880
1282
 
881
1283
  ```{ruby view_kable}
882
- outputs (~:mtcars).kable.kable_styling
1284
+ outputs (~R[:mtcars]).kable.kable_styling
883
1285
  ```
884
1286
 
885
1287
  # Basic Data Types
@@ -897,7 +1299,7 @@ table.
897
1299
  | logical | logical | logical |
898
1300
  | integer | numeric | integer |
899
1301
  | double | numeric | double |
900
- | complex | complex | comples |
1302
+ | complex | complex | complex |
901
1303
  | character | character | character |
902
1304
  | raw | raw | raw |
903
1305
 
@@ -974,7 +1376,7 @@ In this next example, method 'c' is chainned after 'vec1'. This also looks like
974
1376
  method of the vector, but in reallity, this is actually closer to the pipe operator. When
975
1377
  Galaaz identifies that 'c' is not a method of 'vec' it actually tries to call 'R.c' with
976
1378
  'vec1' as the first argument concatenated with all the other available arguments. The code
977
- bellow is automatically converted to the code above.
1379
+ below is automatically converted to the code above.
978
1380
 
979
1381
  ```{ruby chainning_methods}
980
1382
  vec = vec1.c(vec2)
@@ -1008,7 +1410,7 @@ Vectors can be indexed by using the '[]' operator:
1008
1410
  puts vec4[3]
1009
1411
  ```
1010
1412
 
1011
- We can also index a vector with another vector. For example, in the code bellow, we take elements
1413
+ We can also index a vector with another vector. For example, in the code below, we take elements
1012
1414
  1, 3, 5, and 7 from vec3:
1013
1415
 
1014
1416
  ```{ruby index_by_vector}
@@ -1179,7 +1581,7 @@ operator) and then the vector was indexed by its first element, extracting the n
1179
1581
 
1180
1582
  A data frame is a table like structure in which each column has the same number of
1181
1583
  rows. Data frames are the basic structure for storing data for data analysis. We have already
1182
- seen a data frame previously when we accessed variable '~:mtcars'. In order to create a
1584
+ seen a data frame previously when we accessed variable '~R[:mtcars]'. In order to create a
1183
1585
  data frame, function 'data__frame' is used:
1184
1586
 
1185
1587
  ```{ruby dataframe}
@@ -1196,45 +1598,45 @@ A data frame can be indexed the same way as a matrix, by using '[row, column]',
1196
1598
  column can either be a numeric or the name of the row or column
1197
1599
 
1198
1600
  ```{ruby dataframe_index}
1199
- puts (~:mtcars).head
1200
- puts (~:mtcars)[1, 2]
1201
- puts (~:mtcars)['Datsun 710', 'mpg']
1601
+ puts (~R[:mtcars]).head
1602
+ puts (~R[:mtcars])[1, 2]
1603
+ puts (~R[:mtcars])['Datsun 710', 'mpg']
1202
1604
  ```
1203
1605
 
1204
1606
  Extracting a column from a data frame as a vector can be done by using the double square bracket
1205
1607
  operator:
1206
1608
 
1207
1609
  ```{ruby dataframe_column}
1208
- puts (~:mtcars)[['mpg']]
1610
+ puts (~R[:mtcars])[['mpg']]
1209
1611
  ```
1210
1612
 
1211
1613
  A data frame column can also be accessed as if it were an instance variable of the data frame:
1212
1614
 
1213
1615
  ```{ruby dataframe_instance_variable}
1214
- puts (~:mtcars).mpg
1616
+ puts (~R[:mtcars]).mpg
1215
1617
  ```
1216
1618
 
1217
1619
  Slicing a data frame can be done by indexing it with a vector (we use 'head' to reduce the
1218
1620
  output):
1219
1621
 
1220
1622
  ```{ruby dataframe_column_slice}
1221
- puts (~:mtcars)[R.c('mpg', 'hp')].head
1623
+ puts (~R[:mtcars])[R.c('mpg', 'hp')].head
1222
1624
  ```
1223
1625
 
1224
1626
  A row slice can be obtained by indexing by row and using the ':all' keyword for the column:
1225
1627
 
1226
1628
  ```{ruby dataframe_row_slice}
1227
- puts (~:mtcars)[R.c('Datsun 710', 'Camaro Z28'), :all]
1629
+ puts (~R[:mtcars])[R.c('Datsun 710', 'Camaro Z28'), :all]
1228
1630
  ```
1229
1631
 
1230
1632
  Finally, a data frame can also be indexed with a logical vector. In this next example, the
1231
1633
  'am' column of :mtcars is compared with 0 (with method 'eq'). When 'am' is equal to 0 the
1232
- car is automatic. So, by doing '(~:mtcars).am.eq 0' a logical vector is created with
1634
+ car is automatic. So, by doing '(~R[:mtcars]).am.eq 0' a logical vector is created with
1233
1635
  'true' whenever 'am' is 0 and 'false' otherwise.
1234
1636
 
1235
1637
  ```{ruby logical_vector_filter}
1236
1638
  # obtain a vector with 'true' for cars with automatic transmission
1237
- automatic = (~:mtcars).am.eq 0
1639
+ automatic = (~R[:mtcars]).am.eq 0
1238
1640
  puts automatic
1239
1641
  ```
1240
1642
 
@@ -1243,7 +1645,7 @@ which all cars have automatic transmission.
1243
1645
 
1244
1646
  ```{ruby dataframe_logical}
1245
1647
  # slice the data frame by using this vector
1246
- puts (~:mtcars)[automatic, :all]
1648
+ puts (~R[:mtcars])[automatic, :all]
1247
1649
  ```
1248
1650
 
1249
1651
  # Writing Expressions in Galaaz
@@ -1253,24 +1655,24 @@ Galaaz extends Ruby to work with complex expressions, similar to R's expressions
1253
1655
 
1254
1656
  ## Expressions from operators
1255
1657
 
1256
- The code bellow
1658
+ The code below
1257
1659
  creates an expression summing two symbols
1258
1660
 
1259
1661
  ```{ruby expressions}
1260
- exp1 = :a + :b
1662
+ exp1 = R[:a] + R[:b]
1261
1663
  puts exp1
1262
1664
  ```
1263
1665
  We can build any complex mathematical expression
1264
1666
 
1265
1667
  ```{ruby expr2}
1266
- exp2 = (:a + :b) * 2.0 + :c ** 2 / :z
1668
+ exp2 = (R[:a] + R[:b]) * 2.0 + R[:c] ** 2 / R[:z]
1267
1669
  puts exp2
1268
1670
  ```
1269
1671
 
1270
1672
  It is also possible to use inequality operators in building expressions
1271
1673
 
1272
1674
  ```{ruby expr3}
1273
- exp3 = (:a + :b) >= :z
1675
+ exp3 = (R[:a] + R[:b]) >= :z
1274
1676
  puts exp3
1275
1677
  ```
1276
1678
 
@@ -1279,7 +1681,7 @@ notation for those operators such as (.gt, .ge, etc.). So the same expression w
1279
1681
  above can also be written as
1280
1682
 
1281
1683
  ```{ruby expr4}
1282
- exp4 = (:a + :b).ge :z
1684
+ exp4 = (R[:a] + R[:b]).ge :z
1283
1685
  puts exp4
1284
1686
  ```
1285
1687
 
@@ -1288,23 +1690,23 @@ those are expressions involving '==', and '='. In order to write an expression
1288
1690
  need to use the method '.eq' and for '=' we need the function '.assign'
1289
1691
 
1290
1692
  ```{ruby expr5}
1291
- exp5 = (:a + :b).eq :z
1693
+ exp5 = (R[:a] + R[:b]).eq :z
1292
1694
  puts exp5
1293
1695
  ```
1294
1696
 
1295
1697
  ```{ruby expr6}
1296
- exp6 = :y.assign :a + :b
1698
+ exp6 = R[:y].assign R[:a] + R[:b]
1297
1699
  puts exp6
1298
1700
  ```
1299
1701
  In general we think that using the functional notation is preferable to using the
1300
1702
  symbolic notation as otherwise, we end up writing invalid expressions such as
1301
1703
 
1302
1704
  ```{ruby exp_wrong, warning=FALSE, eval=FALSE}
1303
- exp_wrong = (:a + :b) == :z
1705
+ exp_wrong = (R[:a] + R[:b]) == :z
1304
1706
  puts exp_wrong
1305
1707
  ```
1306
1708
  and it might be difficult to understand what is going on here. The problem lies with the fact that
1307
- when using '==' we are comparing expression (:a + :b) to expression :z with '=='. When the
1709
+ when using '==' we are comparing expression (R[:a] + R[:b]) to expression :z with '=='. When the
1308
1710
  comparison is executed, the system tries to evaluate :a, :b and :z, and those symbols at
1309
1711
  this time are not bound to anything and we get a "object 'a' not found" message.
1310
1712
  If we only use functional notation, this type of error will not occur.
@@ -1319,21 +1721,21 @@ When we want the function to be part of the expression, we call the function pre
1319
1721
  by the letter E, such as 'E.sin(x)'
1320
1722
 
1321
1723
  ```{ruby method_expression}
1322
- exp7 = :y.assign E.sin(:x)
1724
+ exp7 = R[:y].assign E.sin(R[:x])
1323
1725
  puts exp7
1324
1726
  ```
1325
1727
 
1326
1728
  Expressions can also be written using '.' notation:
1327
1729
 
1328
1730
  ```{ruby expression_with_dot}
1329
- exp8 = :y.assign :x.sin
1731
+ exp8 = R[:y].assign R[:x].sin
1330
1732
  puts exp8
1331
1733
  ```
1332
1734
 
1333
1735
  When a function has multiple arguments, the first one can be used before the '.':
1334
1736
 
1335
1737
  ```{ruby expression_multiple_args}
1336
- exp9 = :x.c(:y)
1738
+ exp9 = R[:x].c(R[:y])
1337
1739
  puts exp9
1338
1740
  ```
1339
1741
 
@@ -1343,7 +1745,7 @@ Expressions can be evaluated by calling function 'eval' with a binding. A bindin
1343
1745
  with a list:
1344
1746
 
1345
1747
  ```{ruby eval_expression_list}
1346
- exp = (:a + :b) * 2.0 + :c ** 2 / :z
1748
+ exp = (R[:a] + R[:b]) * 2.0 + R[:c] ** 2 / R[:z]
1347
1749
  puts exp.eval(R.list(a: 10, b: 20, c: 30, z: 40))
1348
1750
  ```
1349
1751
 
@@ -1362,7 +1764,7 @@ puts exp.eval(df)
1362
1764
  # Manipulating Data
1363
1765
 
1364
1766
  One of the major benefits of Galaaz is to bring strong data manipulation to Ruby. The following
1365
- examples were extracted from Hardley's "R for Data Science" (https://r4ds.had.co.nz/). This
1767
+ examples were extracted from Hadley's "R for Data Science" (https://r4ds.had.co.nz/). This
1366
1768
  is a highly recommended book for those not already familiar with the 'tidyverse' style of
1367
1769
  programming in R. In the sections to follow, we will limit ourselves to convert the R code to
1368
1770
  Galaaz.
@@ -1374,9 +1776,9 @@ locally, and if not, installs it. This data frame contains all 336,776 flights t
1374
1776
  departed from New York City in 2013. The data comes from the US Bureau of
1375
1777
  Transportation Statistics.
1376
1778
 
1377
- Dplyr uses 'tibbles' in place of data frames; unfortunately, tibbles do not print yet properly in
1378
- Galaaz due to a bug in fastR. In order to print a tibble we need to convert it to a data frame
1379
- using the 'as\_\_data__frame' method.
1779
+ Dplyr often uses **tibbles** in place of classic data frames. In Galaaz, printing may differ from
1780
+ the R console; if you need a classic tabular printout, convert with **`as__data__frame`** (or use
1781
+ `head` / `str` in R via `R` calls).
1380
1782
 
1381
1783
  ```{ruby nycflights13}
1382
1784
  R.install_and_loads('nycflights13')
@@ -1384,17 +1786,17 @@ R.library('dplyr')
1384
1786
  ```
1385
1787
 
1386
1788
  ```{ruby flights}
1387
- flights = ~:flights
1789
+ flights = ~R[:flights]
1388
1790
  puts flights.head
1389
1791
  ```
1390
1792
 
1391
1793
  ## Filtering rows with Filter
1392
1794
 
1393
1795
  In this example we filter the flights data set by giving to the filter function two expressions:
1394
- the first :month.eq 1
1796
+ the first R[:month].eq 1
1395
1797
 
1396
1798
  ```{ruby filter_rows}
1397
- puts flights.filter((:month.eq 1), (:day.eq 1)).head
1799
+ puts flights.filter((R[:month].eq 1), (R[:day].eq 1)).head
1398
1800
  ```
1399
1801
 
1400
1802
  ## Logical Operators
@@ -1402,7 +1804,7 @@ puts flights.filter((:month.eq 1), (:day.eq 1)).head
1402
1804
  All flights that departed in November of December
1403
1805
 
1404
1806
  ```{ruby nov_dec}
1405
- puts flights.filter((:month.eq 11) | (:month.eq 12)).head
1807
+ puts flights.filter((R[:month].eq 11) | (R[:month].eq 12)).head
1406
1808
  ```
1407
1809
 
1408
1810
  The same as above, but using the 'in' operator. In R, it is possible to define many operators
@@ -1411,7 +1813,7 @@ operators from Galaaz the '._' method is used, where the first argument is the o
1411
1813
  symbol, in this case ':in' and the second argument is the vector:
1412
1814
 
1413
1815
  ```{ruby in_op}
1414
- puts flights.filter(:month._ :in, R.c(11, 12)).head
1816
+ puts flights.filter(R[:month]._ :in, R.c(11, 12)).head
1415
1817
  ```
1416
1818
 
1417
1819
  ## Filtering with NA (Not Available)
@@ -1430,13 +1832,13 @@ Now filtering by :x > 1 shows all lines that satisfy this condition, where the r
1430
1832
  not.
1431
1833
 
1432
1834
  ```{ruby filter_na}
1433
- puts df.filter(:x > 1)
1835
+ puts df.filter(R[:x] > 1)
1434
1836
  ```
1435
1837
 
1436
1838
  To match an NA use method 'is__na'
1437
1839
 
1438
1840
  ```{ruby with_na}
1439
- puts df.filter((:x.is__na) | (:x > 1))
1841
+ puts df.filter((R[:x].is__na) | (R[:x] > 1))
1440
1842
  ```
1441
1843
 
1442
1844
  ## Arrange Rows with arrange
@@ -1450,7 +1852,7 @@ puts flights.arrange(:year, :month, :day).head
1450
1852
  To arrange in descending order, use function 'desc'
1451
1853
 
1452
1854
  ```{ruby desc_arrange}
1453
- puts flights.arrange(:dep_delay.desc).head
1855
+ puts flights.arrange(R[:dep_delay].desc).head
1454
1856
  ```
1455
1857
 
1456
1858
  ## Selecting columns
@@ -1464,7 +1866,7 @@ puts flights.select(:year, :month, :day).head
1464
1866
  It is also possible to select column in a given range
1465
1867
 
1466
1868
  ```{ruby select_range}
1467
- puts flights.select(:year.up_to :day).head
1869
+ puts flights.select(R[:year].up_to(R[:day])).head
1468
1870
  ```
1469
1871
 
1470
1872
  Select all columns that start with a given name sequence
@@ -1494,7 +1896,7 @@ puts flights.select(:year, :month, :day, E.everything).head
1494
1896
 
1495
1897
  ```{ruby small_flights}
1496
1898
  flights_sm = flights.
1497
- select((:year.up_to :day),
1899
+ select((R[:year].up_to(R[:day])),
1498
1900
  E.ends_with('delay'),
1499
1901
  :distance,
1500
1902
  :air_time)
@@ -1504,8 +1906,8 @@ puts flights_sm.head
1504
1906
 
1505
1907
  ```{ruby mutate}
1506
1908
  flights_sm = flights_sm.
1507
- mutate(gain: :dep_delay - :arr_delay,
1508
- speed: :distance / :air_time * 60)
1909
+ mutate(gain: R[:dep_delay] - R[:arr_delay],
1910
+ speed: R[:distance] / R[:air_time] * 60)
1509
1911
  puts flights_sm.head
1510
1912
  ```
1511
1913
 
@@ -1522,7 +1924,7 @@ When a data frame is grouped with 'group_by' summaries apply to the given group:
1522
1924
 
1523
1925
  ```{ruby summarise_group_by}
1524
1926
  by_day = flights.group_by(:year, :month, :day)
1525
- puts by_day.summarise(delay: :dep_delay.mean(na__rm: true)).head
1927
+ puts by_day.summarise(delay: R[:dep_delay].mean(na__rm: true)).head
1526
1928
  ```
1527
1929
 
1528
1930
  Next we put many operations together by pipping them one after the other:
@@ -1532,23 +1934,25 @@ delays = flights.
1532
1934
  group_by(:dest).
1533
1935
  summarise(
1534
1936
  count: E.n,
1535
- dist: :distance.mean(na__rm: true),
1536
- delay: :arr_delay.mean(na__rm: true)).
1537
- filter(:count > 20, :dest != "NHL")
1937
+ dist: R[:distance].mean(na__rm: true),
1938
+ delay: R[:arr_delay].mean(na__rm: true)).
1939
+ filter(R[:count] > 20, R[:dest] != "NHL")
1538
1940
 
1539
1941
  puts delays.head
1540
1942
  ```
1541
1943
 
1542
1944
  # Using Data Table
1543
1945
 
1946
+ The next chunk converts the **nycflights13** `flights` tibble already loaded above into a
1947
+ **`data.table`**. That keeps the manual offline and avoids downloading a remote CSV during gknit
1948
+ (network stalls look like bridge hangs when the transfer runs inside a single R eval).
1949
+
1544
1950
  ```{ruby fread}
1545
1951
  R.library('data.table')
1546
- R.install_and_loads('curl')
1547
1952
 
1548
- input = "https://raw.githubusercontent.com/Rdatatable/data.table/master/vignettes/flights14.csv"
1549
- flights = R.fread(input)
1550
- puts flights
1953
+ flights = R.as__data__table(~R[:flights])
1551
1954
  puts flights.dim
1955
+ puts R.head(flights, 12)
1552
1956
  ```
1553
1957
 
1554
1958
  ```{ruby data_table}
@@ -1566,7 +1970,7 @@ puts data_table.ID
1566
1970
 
1567
1971
  ```{ruby subset_i}
1568
1972
  # subset rows in i
1569
- ans = flights[(:origin.eq "JFK") & (:month.eq 6)]
1973
+ ans = flights[(R[:origin].eq "JFK") & (R[:month].eq 6)]
1570
1974
  puts ans.head
1571
1975
 
1572
1976
  # Get the first two rows from flights.
@@ -1574,8 +1978,7 @@ puts ans.head
1574
1978
  ans = flights[(1..2)]
1575
1979
  puts ans
1576
1980
 
1577
- # Sort flights first by column origin in ascending order, and then by dest in descending order:
1578
-
1981
+ # Sort by origin asc, then dest desc (example kept commented):
1579
1982
  # ans = flights[E.order(:origin, -(:dest))]
1580
1983
  # puts ans.head
1581
1984
 
@@ -1588,66 +1991,274 @@ puts ans
1588
1991
  ans = flights[:all, :arr_delay]
1589
1992
  puts ans.head
1590
1993
 
1591
- # Select arr_delay column, but return as a data.table instead.
1994
+ # arr_delay as data.table (not plain vector).
1592
1995
 
1593
- ans = flights[:all, :arr_delay.list]
1996
+ ans = flights[:all, R[:arr_delay].list]
1594
1997
  puts ans.head
1595
1998
 
1596
- ans = flights[:all, E.list(:arr_delay, :dep_delay)]
1999
+ ans = flights[:all, E.list(R[:arr_delay], R[:dep_delay])]
2000
+ ```
2001
+
2002
+ # Apache Arrow
2003
+
2004
+ [Apache Arrow](https://arrow.apache.org/) is a **columnar** in-memory format used heavily in R
2005
+ and Python for analytics. In Galaaz, **Ruby does not hold an Arrow C++ table itself**; instead you
2006
+ build ordinary Ruby structures (arrays of row hashes), and **`R::Arrow.from_ruby_batches`** creates
2007
+ a real **Arrow `Table` inside GNU R**. From there you use R’s **`arrow`** and **`dplyr`** packages
2008
+ as usual: **`group_by`** on the Arrow table, **`summarise`** for aggregates, then **`collect()`** to
2009
+ materialize a tibble when you need in-memory R rows.
2010
+
2011
+ That pattern matches production use: **JRuby threads** (or sequential code) assemble many rows in
2012
+ Ruby; you pay **one** bridge-heavy handoff to R; **dplyr** runs vectorised work on the Arrow table
2013
+ in R.
2014
+
2015
+ **Prerequisites:** install R packages **`arrow`** and **`dplyr`**. Run scripts with
2016
+ **`bin/galaaz-jruby`** (or the same JVM flags as in **`docs/testing.md`**) so the Arrow JNI stack is
2017
+ available.
2018
+
2019
+ ## Other `R::Arrow` helpers
2020
+
2021
+ The Ruby module **`R::Arrow`** (see `lib/R_interface/r_arrow.rb`) also includes:
2022
+
2023
+ * **`R::Arrow.table_from(df)`** — wrap an R `data.frame` / tibble as an Arrow table.
2024
+ * **`R::Arrow.read_feather` / `write_feather`**, **`read_parquet`**, **`dataset(path)`** — file and
2025
+ dataset IO on paths visible to R.
2026
+
2027
+ ## Example: many Ruby rows → Arrow in R → grouped statistics
2028
+
2029
+ The repository test **`slow-specs/arrow_large_pipeline_spec.rb`** builds **200k rows** in parallel
2030
+ (eight threads × 25,000 rows), pushes them through **`R::Arrow.from_ruby_batches`**, then checks that
2031
+ **dplyr** group summaries match a Ruby reference calculation. The same logic appears below at a
2032
+ **smaller scale** so this manual can knit quickly; increase `thread_count` and `rows_per_thread`
2033
+ when experimenting locally.
2034
+
2035
+ ```{ruby arrow_pipeline_example, message=FALSE, warning=FALSE}
2036
+ # Scaled-down version of slow-specs/arrow_large_pipeline_spec.rb.
2037
+ unless R::Support.eval("requireNamespace('arrow', quietly=TRUE) && requireNamespace('dplyr', quietly=TRUE)") == true
2038
+ puts '(Skip: need arrow + dplyr in R; use bin/galaaz-jruby outside gKnit.)'
2039
+ else
2040
+ thread_count = 4
2041
+ rows_per_thread = 500
2042
+ group_count = 5
2043
+
2044
+ batches = []
2045
+ mutex = Mutex.new
2046
+ threads = []
2047
+
2048
+ thread_count.times do |tid|
2049
+ threads << Thread.new do
2050
+ start = tid * rows_per_thread
2051
+ local = (start...(start + rows_per_thread)).map do |i|
2052
+ {
2053
+ id: i,
2054
+ grp: "g#{i % group_count}",
2055
+ value: (i % 17) + 1,
2056
+ weight: ((i % 5) + 1) * 0.5
2057
+ }
2058
+ end
2059
+ mutex.synchronize { batches << local }
2060
+ end
2061
+ end
2062
+ threads.each(&:join)
2063
+
2064
+ tbl = R::Arrow.from_ruby_batches(batches)
2065
+ puts "R class after from_ruby_batches: #{tbl.rclass}"
2066
+
2067
+ grouped = R.dplyr___group_by(tbl, :grp)
2068
+ summarised = R.dplyr___summarise(
2069
+ grouped,
2070
+ n: E.n(),
2071
+ total: E.sum(:value),
2072
+ wsum: E.sum(R[:value] * R[:weight])
2073
+ )
2074
+ out = R.dplyr___collect(summarised)
2075
+
2076
+ puts 'Per-group summary (first rows):'
2077
+ puts R.as__data__frame(out).head(10)
2078
+
2079
+ total_n = 0
2080
+ (1..(out.nrow >> 0)).each { |i| total_n += (out[['n']][i] >> 0) }
2081
+ puts "Sum of group counts n (should equal #{thread_count * rows_per_thread}): #{total_n}"
2082
+ end
2083
+ ```
2084
+
2085
+ **What to notice:** (1) Ruby only sees **`Hash`** rows and Ruby **`Thread`** objects; (2) a single
2086
+ **`from_ruby_batches`** call creates the Arrow table in R; (3) **`dplyr___group_by`** /
2087
+ **`dplyr___summarise`** / **`dplyr___collect`** mirror **`dplyr::group_by`** /
2088
+ **`dplyr::summarise`** / **`dplyr::collect`** on an Arrow-backed table. For a lighter test, see
2089
+ **`specs/arrow_from_ruby_batches_spec.rb`**; for the full-size benchmark, run
2090
+ **`bin/run_slow_rspec slow-specs/arrow_large_pipeline_spec.rb`**.
2091
+
2092
+ # Bioconductor and DESeq2
2093
+
2094
+ **Bioconductor** packages are ordinary R packages installed from the Bioconductor repositories.
2095
+ Galaaz does not treat them specially: once installed in **GNU R**, you load them with
2096
+ **`R.library`** like any CRAN package.
2097
+
2098
+ ## Installing Bioconductor packages
2099
+
2100
+ From an R session (or `R -e '...'`), use **BiocManager** (see
2101
+ [bioconductor.org](https://bioconductor.org/install/)):
2102
+
2103
+ ```r
2104
+ if (!requireNamespace("BiocManager", quietly = TRUE))
2105
+ install.packages("BiocManager")
2106
+ BiocManager::install(c("DESeq2", "airway"))
2107
+ ```
2108
+
2109
+ The **`airway`** package ships the example **`SummarizedExperiment`** used below. **DESeq2**
2110
+ pulls in several dependencies; the first install can take several minutes.
2111
+
2112
+ ## Example: DESeq2 on the airway dataset
2113
+
2114
+ The script **`examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb`** is the canonical
2115
+ version in the repository. Run it from the **Galaaz repository root** with JRuby, for example:
2116
+
2117
+ ```text
2118
+ bin/galaaz-jruby examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
2119
+ ```
2120
+
2121
+ The workflow in Ruby mirrors a standard DESeq2 vignette:
2122
+
2123
+ 1. **`R.library('DESeq2')`** and **`R.library('airway')`**, then **`R.data('airway')`** so the
2124
+ object exists in R’s global environment.
2125
+ 2. **`airway = ~R[:airway]`** pulls the experiment into a Galaaz wrapper so you can pass it to R
2126
+ functions as a Ruby value.
2127
+ 3. **`R.DESeqDataSet(..., design: (R[:all].til R[:cell] + R[:dex]))`** builds the **`DESeqDataSet`**. The
2128
+ **`(R[:all].til R[:cell] + R[:dex])`** form is Galaaz’s way of passing the one-sided formula
2129
+ **`~ cell + dex`** (adjust for the design you need).
2130
+ 4. Prefilter rows with almost no counts: **`keep = R.rowSums(R.counts(dds)) >= 10`** and
2131
+ **`dds = dds[keep, :all]`**.
2132
+ 5. **`dds = R.DESeq(dds)`** fits the model; **`res = R.results(dds, contrast: R.c('dex', 'trt', 'untrt'))`**
2133
+ extracts the treatment contrast (adjust **`contrast`** for your experiment).
2134
+ 6. Summaries use normal Ruby string interpolation on **`R.nrow`**, **`R.ncol`**, **`R.colnames`**, etc.
2135
+ 7. **`R.pdf(...); R.plotMA(res, ...); R.dev__off`** writes DESeq2’s MA plot (path is relative to the
2136
+ process working directory—use the repo root when running the bundled script).
2137
+
2138
+ Related benchmarks and warm-run notes live under **`docs/deseq2_airway_benchmark.md`** and
2139
+ **`examples/bioconductor_deseq2_airway/bench_*.rb`**.
2140
+
2141
+ Below is the full listing (same as the file in the repository). It is **not** executed while this
2142
+ manual is knitted, because **DESeq2** is heavy and may be absent on the build machine.
2143
+
2144
+ ```{ruby deseq2_airway_full_listing, eval=FALSE}
2145
+ # Canonical script: examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
2146
+ # Run: bin/galaaz-jruby examples/.../deseq2_airway_galaaz.rb (repo root).
2147
+
2148
+ require 'galaaz'
2149
+
2150
+ R.library('DESeq2')
2151
+ R.library('airway')
2152
+ R.data('airway')
2153
+
2154
+ airway = ~R[:airway]
2155
+
2156
+ # Build DESeq2 dataset with one-sided formula: ~ cell + dex.
2157
+ dds = R.DESeqDataSet(airway, design: (R[:all].til R[:cell] + R[:dex]))
2158
+
2159
+ # Prefilter genes with almost no counts.
2160
+ keep = R.rowSums(R.counts(dds)) >= 10
2161
+ dds = dds[keep, :all]
2162
+
2163
+ # Fit DE model and extract treatment effect.
2164
+ dds = R.DESeq(dds)
2165
+ res = R.results(dds, contrast: R.c('dex', 'trt', 'untrt'))
2166
+
2167
+ # Compact sanity outputs for quick verification.
2168
+ puts "Samples: #{R.ncol(dds)}"
2169
+ puts "Genes after prefilter: #{R.nrow(dds)}"
2170
+ puts "Result rows: #{R.nrow(res)}"
2171
+ puts "Result columns: #{R.colnames(res)}"
2172
+ puts "Significant genes (padj < 0.05): #{R.sum(res.padj < 0.05, na__rm: true)}"
2173
+
2174
+ res_ordered = res[R.order(res.padj), :all]
2175
+ puts R.head(R.as__data__frame(res_ordered), 10)
2176
+
2177
+ # Standard DESeq2 plot call written to file.
2178
+ R.pdf('examples/bioconductor_deseq2_airway/plotMA_galaaz.pdf')
2179
+ R.plotMA(res, ylim: R.c(-5, 5))
2180
+ R.dev__off
1597
2181
  ```
1598
2182
 
2183
+ If **DESeq2** and **airway** are installed, the next chunk loads the data and prints a short
2184
+ preview (it does **not** run **`DESeq`** so the manual knits quickly).
2185
+
2186
+ ```{ruby deseq2_airway_smoke, message=FALSE, warning=FALSE}
2187
+ unless R::Support.eval("requireNamespace('DESeq2', quietly=TRUE) && requireNamespace('airway', quietly=TRUE)")
2188
+ puts '(Skip: install DESeq2 and airway via BiocManager in R to run the full example.)'
2189
+ else
2190
+ R.library('DESeq2')
2191
+ R.library('airway')
2192
+ R.data('airway')
2193
+ airway = ~R[:airway]
2194
+ puts 'airway object (head of assay / dims via R):'
2195
+ puts "ncol(samples): #{R.ncol(airway)}"
2196
+ puts R.head(R.assay(airway), 3)
2197
+ end
2198
+ ```
2199
+
2200
+ # Performance
2201
+
2202
+ For realistic analyses, **most wall-clock time is spent inside GNU R** (model fitting, I/O inside
2203
+ R, graphics). The Galaaz **bridge** adds overhead mainly from **starting a session**, **serializing
2204
+ requests**, and **wrapping results** in Ruby objects—not from reimplementing R’s numerical work.
2205
+
2206
+ Practical tips:
2207
+
2208
+ * Keep **hot loops** in R or vectorized code when possible; use Ruby for orchestration, I/O, and
2209
+ glue.
2210
+ * **Reuse one process**: running many short scripts cold-starts Ruby, the JVM, and R each time;
2211
+ a long-lived process or repeated calls in one run amortize setup (see benchmarks below).
2212
+ * **Batch data**: merge shards in Ruby, then call **`R::Arrow.from_ruby_batches`** (or build one
2213
+ data frame) instead of millions of tiny R calls.
2214
+
2215
+ For measured discussion (including DESeq2-style workloads and warm comparisons), see
2216
+ **`docs/performance.md`** and **`docs/deseq2_airway_benchmark.md`** in the Galaaz repository.
2217
+
1599
2218
  # Graphics in Galaaz
1600
2219
 
1601
2220
  Creating graphics in Galaaz is quite easy, as it can use all the power of ggplot2. There are
1602
- many resources in the web that teaches ggplot, so here we give a quick example of ggplot
2221
+ many resources on the web that teach ggplot, so here we give a quick example of ggplot
1603
2222
  integration with Ruby. We continue to use the :mtcars dataset and we will plot a diverging
1604
- bar plot, showing cars that have 'above' or 'below' gas consuption. Let's first prepare
2223
+ bar plot, showing cars that have 'above' or 'below' gas consumption. Let's first prepare
1605
2224
  the data frame with the necessary data:
1606
2225
 
1607
2226
  ```{ruby diverging_plot_pre}
1608
- # copy the R variable :mtcars to the Ruby mtcars variable
1609
- mtcars = ~:mtcars
1610
-
1611
- # create a new column 'car_name' to store the car names so that it can be
1612
- # used for plotting. The 'rownames' of the data frame cannot be used as
1613
- # data for plotting
1614
- mtcars.car_name = R.rownames(:mtcars)
1615
-
1616
- # compute normalized mpg and add it to a new column called mpg_z
1617
- # Note that the mean value for mpg can be obtained by calling the 'mean'
1618
- # function on the vector 'mtcars.mpg'. The same with the standard
1619
- # deviation 'sd'. The vector is then rounded to two digits with 'round 2'
2227
+ # :mtcars -> Ruby handle
2228
+ mtcars = ~R[:mtcars]
2229
+
2230
+ # Row labels are not a plot column; copy them to car_name.
2231
+ mtcars.car_name = R.rownames(R[:mtcars])
2232
+
2233
+ # Z-score mpg (mean/sd on mtcars.mpg); round to 2 decimals.
1620
2234
  mtcars.mpg_z = ((mtcars.mpg - mtcars.mpg.mean)/mtcars.mpg.sd).round 2
1621
2235
 
1622
- # create a new column 'mpg_type'. Function 'ifelse' is a vectorized function
1623
- # that looks at every element of the mpg_z vector and if the value is below
1624
- # 0, returns 'below', otherwise returns 'above'
2236
+ # ifelse is vectorized: below / above average mpg_z.
1625
2237
  mtcars.mpg_type = (mtcars.mpg_z < 0).ifelse("below", "above")
1626
2238
 
1627
- # order the mtcar data set by the mpg_z vector from smaler to larger values
2239
+ # Sort rows by mpg_z.
1628
2240
  mtcars = mtcars[mtcars.mpg_z.order, :all]
1629
2241
 
1630
- # convert the car_name column to a factor to retain sorted order in plot
2242
+ # Factor car_name so plot order follows sort.
1631
2243
  mtcars.car_name = mtcars.car_name.factor levels: mtcars.car_name
1632
2244
 
1633
- # let's look at the final data frame
1634
2245
  puts mtcars.head
1635
2246
  ```
1636
- Now, lets plot the diverging bar plot. When using gKnit, there is no need to call
1637
- 'R.awt' to create a plotting device, since gKnit does take care of it. Galaaz
2247
+ Now, let's plot the diverging bar plot. When using gKnit, you normally do **not** need to open a
2248
+ graphics device manually; gKnit arranges the figure device for chunk output. Galaaz
1638
2249
  provides integration with ggplot. The interested reader should check online for more
1639
2250
  information on ggplot, since it is outside the scope of this manual describing
1640
- how ggplot works. We give here but a brief description on how this plot is generated.
2251
+ how ggplot works. Here we give only a brief description of how this plot is generated.
1641
2252
 
1642
- ggplot implements the 'grammar of graphics'. In this approach, plots are build by
2253
+ ggplot implements the 'grammar of graphics'. In this approach, plots are built by
1643
2254
  adding layers to the plot. On the first layer we describe what we want on the 'x'
1644
2255
  and 'y' axis of the plot. In this case, we have 'car_name' on the 'x' axis and
1645
2256
  'mpg\_z' on the 'y' axis. Then the type of graph is specified by adding
1646
2257
  'geom\_bar' (for a bar graph). We specify that our bars should be filled using
1647
- 'mpg\_type', which is either 'above' or 'bellow' giving then two colours for
2258
+ 'mpg\_type', which is either 'above' or 'below' giving then two colours for
1648
2259
  filling. On the next layer we specify the labels for the graph, then we add the
1649
2260
  title and subtitle. Finally, in a bar chart usually bars go on the vertical direction,
1650
- but in this graph we want the bars to be horizontally layed so we add 'coord\_flip'.
2261
+ but in this graph we want the bars to be horizontally laid so we add 'coord\_flip'.
1651
2262
 
1652
2263
  ```{ruby diverging_bar, fig.width = 9.1, fig.height = 6.5}
1653
2264
  require 'ggplot'
@@ -1665,7 +2276,7 @@ puts mtcars.ggplot(E.aes(x: :car_name, y: :mpg_z, label: :mpg_z)) +
1665
2276
  # Coding with Tidyverse
1666
2277
 
1667
2278
  In R, and when coding with 'tidyverse', arguments to a function are usually not
1668
- *referencially transparent*. That is, you can’t replace a value with a seemingly equivalent
2279
+ *referentially transparent*. That is, you can’t replace a value with a seemingly equivalent
1669
2280
  object that you’ve defined elsewhere. To see the problem, let's first define a data frame:
1670
2281
 
1671
2282
  ```{ruby df}
@@ -1681,17 +2292,17 @@ filter(df, my_var == 1)
1681
2292
  ```
1682
2293
  It generates the following error: "object 'x' not found.
1683
2294
 
1684
- However, in Galaaz, arguments are referencially transparent as can be seen by the
1685
- code bellow. Note initally that 'my_var = :x' will not give the error "object 'x' not found"
2295
+ However, in Galaaz, arguments are referentially transparent as can be seen by the
2296
+ code below. Note initially that 'my_var = R[:x]' will not give the error "object 'x' not found"
1686
2297
  since ':x' is treated as an expression and assigned to my\_var. Then when doing (my\_var.eq 1),
1687
- my\_var is a variable that resolves to ':x' and it becomes equivalent to (:x.eq 1) which is
2298
+ my\_var is a variable that resolves to ':x' and it becomes equivalent to (R[:x].eq 1) which is
1688
2299
  what we want.
1689
2300
 
1690
2301
  ```{ruby my_var}
1691
- my_var = :x
2302
+ my_var = R[:x]
1692
2303
  puts df.filter(my_var.eq 1)
1693
2304
  ```
1694
- As stated by Hardley
2305
+ As stated by Hadley
1695
2306
 
1696
2307
  > dplyr code is ambiguous. Depending on what variables are defined where,
1697
2308
  > filter(df, x == y) could be equivalent to any of:
@@ -1703,9 +2314,9 @@ df[x == df$y, ]
1703
2314
  df[x == y, ]
1704
2315
  ```
1705
2316
  In galaaz this ambiguity does not exist, filter(df, x.eq y) is not a valid expression as
1706
- expressions are build with symbols. In doing filter(df, :x.eq y) we are looking for elements
2317
+ expressions are build with symbols. In doing filter(df, R[:x].eq y) we are looking for elements
1707
2318
  of the 'x' column that are equal to a previously defined y variable. Finally in
1708
- filter(df, :x.eq :y) we are looking for elements in which the 'x' column value is equal to
2319
+ filter(df, R[:x].eq R[:y]) we are looking for elements in which the 'x' column value is equal to
1709
2320
  the 'y' column value. This can be seen in the following two chunks of code:
1710
2321
 
1711
2322
  ```{ruby disamb1}
@@ -1713,13 +2324,13 @@ y = 1
1713
2324
  x = 2
1714
2325
 
1715
2326
  # looking for values where the 'x' column is equal to the 'y' column
1716
- puts df.filter(:x.eq :y)
2327
+ puts df.filter(R[:x].eq R[:y])
1717
2328
  ```
1718
2329
 
1719
2330
  ```{ruby disamb2}
1720
2331
  # looking for values where the 'x' column is equal to the 'y' variable
1721
2332
  # in this case, the number 1
1722
- puts df.filter(:x.eq y)
2333
+ puts df.filter(R[:x].eq y)
1723
2334
  ```
1724
2335
  ## Writing a function that applies to different data sets
1725
2336
 
@@ -1746,11 +2357,12 @@ Unfortunately, in R, this function can fail silently if one of the variables isn
1746
2357
  in the data frame, but is present in the global environment. We will not go through here how
1747
2358
  to solve this problem in R.
1748
2359
 
1749
- In Galaaz the method mutate_y bellow will work fine and will never fail silently.
2360
+ In Galaaz the method mutate_y below will work fine and will never fail silently.
1750
2361
 
1751
2362
  ```{ruby mutate_y, warning=FALSE}
1752
2363
  def mutate_y(df)
1753
- df.mutate(:y.assign :a + :x)
2364
+ # Mutate column names are Ruby kwargs (y: …). Use .assign only for R `<-` expressions.
2365
+ df.mutate(y: R[:a] + R[:x])
1754
2366
  end
1755
2367
  ```
1756
2368
  Here we create a data frame that has only one column named 'x':
@@ -1760,8 +2372,8 @@ df1 = R.data__frame(x: (1..3))
1760
2372
  puts df1
1761
2373
  ```
1762
2374
 
1763
- Note that method mutate_y will fail independetly from the fact that variable 'a' is defined and
1764
- in the scope of the method. Variable 'a' has no relationship with the symbol ':a' used in the
2375
+ Note that method mutate_y will fail independently from the fact that variable 'a' is defined and
2376
+ in the scope of the method. Variable 'a' has no relationship with the symbol `R[:a]` used in the
1765
2377
  definition of 'mutate\_y' above:
1766
2378
 
1767
2379
  ```{ruby call_mutate_y, warning = FALSE}
@@ -1770,12 +2382,13 @@ mutate_y(df1)
1770
2382
  ```
1771
2383
  ## Different expressions
1772
2384
 
1773
- Let's move to the next problem as presented by Hardley where trying to write a function in R
2385
+ Let's move to the next problem as presented by Hadley where trying to write a function in R
1774
2386
  that will receive two argumens, the first a variable and the second an expression is not trivial.
1775
- Bellow we create a data frame and we want to write a function that groups data by a variable and
2387
+ Below we create a data frame and we want to write a function that groups data by a variable and
1776
2388
  summarises it by an expression:
1777
2389
 
1778
2390
  ```{r diff_expr}
2391
+ library(dplyr)
1779
2392
  set.seed(123)
1780
2393
 
1781
2394
  df <- data.frame(
@@ -1800,7 +2413,7 @@ d2 <- df %>%
1800
2413
  as.data.frame(d2)
1801
2414
  ```
1802
2415
 
1803
- As shown by Hardley, one might expect this function to do the trick:
2416
+ As shown by Hadley, one might expect this function to do the trick:
1804
2417
 
1805
2418
  ```{r diff_exp_fnc}
1806
2419
  my_summarise <- function(df, group_var) {
@@ -1815,32 +2428,32 @@ my_summarise <- function(df, group_var) {
1815
2428
 
1816
2429
  In order to solve this problem, coding with dplyr requires the introduction of many new concepts
1817
2430
  and functions such as 'quo', 'quos', 'enquo', 'enquos', '!!' (bang bang), '!!!' (triple bang).
1818
- Again, we'll leave to Hardley the explanation on how to use all those functions.
2431
+ Again, we'll leave to Hadley the explanation on how to use all those functions.
1819
2432
 
1820
2433
  Now, let's try to implement the same function in galaaz. The next code block first prints the
1821
- 'df' data frame defined previously in R (to access an R variable from Galaaz, we use the tilda
1822
- operator '~' applied to the R variable name as symbol, i.e., ':df'.
2434
+ 'df' data frame defined previously in R (to access an R variable from Galaaz, we use the tilde
2435
+ operator `~` applied to the R variable name as a symbol, e.g. `:df`).
1823
2436
 
1824
2437
  ```{ruby r_dataframe}
1825
- puts ~:df
2438
+ puts ~R[:df]
1826
2439
  ```
1827
2440
 
1828
2441
  We then create the 'my_summarize' method and call it passing the R data frame and
1829
- the group by variable ':g1':
2442
+ the group by variable 'R[:g1]':
1830
2443
 
1831
2444
  ```{ruby diff_exp_ruby_func}
1832
2445
  def my_summarize(df, group_var)
1833
2446
  df.group_by(group_var).
1834
- summarize(a: :a.mean)
2447
+ summarize(a: R[:a].mean)
1835
2448
  end
1836
2449
 
1837
- puts my_summarize(:df, :g1)
2450
+ puts my_summarize(~R[:df], R[:g1])
1838
2451
  ```
1839
2452
 
1840
2453
  It works!!! Well, let's make sure this was not just some coincidence
1841
2454
 
1842
2455
  ```{ruby group_g2}
1843
- puts my_summarize(:df, :g2)
2456
+ puts my_summarize(~R[:df], R[:g2])
1844
2457
  ```
1845
2458
 
1846
2459
  Great, everything is fine! No magic, no new functions, no complexities, just normal, standard Ruby
@@ -1852,7 +2465,7 @@ In the previous section we've managed to get rid of all NSE formulation for a si
1852
2465
  does this remain true for more complex examples, or will the Galaaz way prove inpractical for
1853
2466
  more complex code?
1854
2467
 
1855
- In the next example Hardley proposes us to write a function that given an expression such as 'a'
2468
+ In the next example Hadley proposes us to write a function that given an expression such as 'a'
1856
2469
  or 'a * b', calculates three summaries. What we want a function that does the same as these R
1857
2470
  statements:
1858
2471
 
@@ -1881,9 +2494,9 @@ def my_summarise2(df, expr)
1881
2494
  )
1882
2495
  end
1883
2496
 
1884
- puts my_summarise2((~:df), :a)
2497
+ puts my_summarise2((~R[:df]), :a)
1885
2498
  puts "\n"
1886
- puts my_summarise2((~:df), :a * :b)
2499
+ puts my_summarise2((~R[:df]), R[:a] * R[:b])
1887
2500
  ```
1888
2501
 
1889
2502
  Once again, there is no need to use any special theory or functions. The only point to be
@@ -1891,7 +2504,7 @@ careful about is the use of 'E' to build expressions from functions 'mean', 'sum
1891
2504
 
1892
2505
  ## Different input and output variable
1893
2506
 
1894
- Now the next challenge presented by Hardley is to vary the name of the output variables based on
2507
+ Now the next challenge presented by Hadley is to vary the name of the output variables based on
1895
2508
  the received expression. So, if the input expression is 'a', we want our data frame columns to
1896
2509
  be named 'mean\_a' and 'sum\_a'. Now, if the input expression is 'b', columns
1897
2510
  should be named 'mean\_b' and 'sum\_b'.
@@ -1917,7 +2530,7 @@ mutate(df, mean_b = mean(b), sum_b = sum(b))
1917
2530
  #> 4 2 2 5 4 3 15
1918
2531
  #> # … with 1 more row
1919
2532
  ```
1920
- In order to solve this problem in R, Hardley needs to introduce some more new functions and notations:
2533
+ In order to solve this problem in R, Hadley needs to introduce some more new functions and notations:
1921
2534
  'quo_name' and the ':=' operator from package 'rlang'
1922
2535
 
1923
2536
  Here is our Ruby code:
@@ -1931,9 +2544,9 @@ def my_mutate(df, expr)
1931
2544
  sum_name => E.sum(expr))
1932
2545
  end
1933
2546
 
1934
- puts my_mutate((~:df), :a)
2547
+ puts my_mutate((~R[:df]), :a)
1935
2548
  puts "\n"
1936
- puts my_mutate((~:df), :b)
2549
+ puts my_mutate((~R[:df]), :b)
1937
2550
  ```
1938
2551
  It really seems that "Non Standard Evaluation" is actually quite standard in Galaaz! But, you
1939
2552
  might have noticed a small change in the way the arguments to the mutate method were called.
@@ -1945,7 +2558,7 @@ and variable mean\_name is not followed by ':' but by '=>'. This is standard Ru
1945
2558
 
1946
2559
  ## Capturing multiple variables
1947
2560
 
1948
- Moving on with new complexities, Hardley proposes us to solve the problem in which the
2561
+ Moving on with new complexities, Hadley proposes us to solve the problem in which the
1949
2562
  summarise function will receive any number of grouping variables.
1950
2563
 
1951
2564
  This again is quite standard Ruby. In order to receive an undefined number of paramenters
@@ -1957,7 +2570,7 @@ def my_summarise3(df, *group_vars)
1957
2570
  summarise(a: E.mean(:a))
1958
2571
  end
1959
2572
 
1960
- puts my_summarise3((~:df), :g1, :g2)
2573
+ puts my_summarise3((~R[:df]), R[:g1], R[:g2])
1961
2574
  ```
1962
2575
 
1963
2576
  ## Why does R require NSE and Galaaz does not?
@@ -1975,7 +2588,7 @@ In Ruby, there is no lazy evaluation of parameters and 'a' is always a variable
1975
2588
  Variables assume their value as soon as they are used, so 'x = a' is immediately evaluate and
1976
2589
  variable 'x' will receive the value of variable 'a' as soon as the Ruby statement is executed.
1977
2590
  Ruby also provides the notion of a symbol; ':a' is a symbol and does not evaluate to anything.
1978
- Galaaz uses Ruby symbols to build expressions that are not bound to anything: ':a.eq :b' is
2591
+ Galaaz uses Ruby symbols to build expressions that are not bound to anything: 'R[:a].eq R[:b]' is
1979
2592
  clearly an expression and has no relationship whatsoever with the statment 'a = b'. By using
1980
2593
  symbols, variables and expressions all the possible ambiguities that are found in R are
1981
2594
  eliminated in Galaaz.
@@ -1985,7 +2598,7 @@ of input they are expecting, they might be expecting regular variables or they m
1985
2598
  expecting expressions and the R function will know how to deal with an input of the form
1986
2599
  'a = b', now for the Ruby developer it might not be immediately clear if it should call the
1987
2600
  function passing the value 'true' if variable 'a' is equal to variable 'b' or if it should
1988
- call the function passing the expression ':a.eq :b'.
2601
+ call the function passing the expression 'R[:a].eq R[:b]'.
1989
2602
 
1990
2603
 
1991
2604
  ## Advanced dplyr features
@@ -2008,12 +2621,13 @@ In the following examples, we show the use of functions 'group\_by\_at', 'summar
2008
2621
  features of characters in the Starwars movies:
2009
2622
 
2010
2623
  ```{ruby starwars}
2011
- puts (~:starwars).head
2624
+ puts (~R[:starwars]).head
2012
2625
  ```
2013
- The grouped_mean function bellow will receive a grouping variable and calculate summaries for
2626
+ The grouped_mean function below will receive a grouping variable and calculate summaries for
2014
2627
  the value\_variables given:
2015
2628
 
2016
2629
  ```{r grouped_mean}
2630
+ library(dplyr)
2017
2631
  grouped_mean <- function(data, grouping_variables, value_variables) {
2018
2632
  data %>%
2019
2633
  group_by_at(grouping_variables) %>%
@@ -2035,24 +2649,26 @@ def grouped_mean(data, grouping_variables, value_variables)
2035
2649
  data.
2036
2650
  group_by_at(grouping_variables).
2037
2651
  mutate(count: E.n).
2038
- summarise_at(E.c(value_variables, "count"), ~:mean, na__rm: true).
2652
+ summarise_at(E.c(value_variables, "count"), ~R[:mean], na__rm: true).
2039
2653
  rename_at(value_variables, E.funs(E.paste0("mean_", value_variables)))
2040
2654
  end
2041
2655
 
2042
- puts grouped_mean((~:starwars), "eye_color", E.c("mass", "birth_year"))
2656
+ puts grouped_mean((~R[:starwars]), "eye_color", E.c("mass", "birth_year"))
2043
2657
  ```
2044
2658
 
2045
-
2046
- [TO BE CONTINUED...]
2047
-
2659
+ The examples above cover programmatic dplyr with string column names and `_at` helpers. The same
2660
+ Galaaz patterns (symbols, `E.*` for expression-safe functions, and Ruby methods on R-backed objects)
2661
+ extend to other tidyverse workflows; consult R package documentation for function-specific
2662
+ arguments.
2048
2663
 
2049
2664
  # Contributing
2050
2665
 
2051
2666
  * Fork it
2052
- * Create your feature branch (git checkout -b my-new-feature)
2053
- * Write Tests!
2054
- * Commit your changes (git commit -am 'Add some feature')
2055
- * Push to the branch (git push origin my-new-feature)
2056
- * Create new Pull Request
2667
+ * Create your feature branch (`git checkout -b my-new-feature`)
2668
+ * Write tests — use **`bin/run_rspec`** or **`bin/run_all_rspec`** with **JRuby** so JVM flags and
2669
+ the load path match **`docs/testing.md`**
2670
+ * Commit your changes (`git commit -am 'Add some feature'`)
2671
+ * Push to the branch (`git push origin my-new-feature`)
2672
+ * Open a pull request
2057
2673
 
2058
2674
  # References