From 705bb16e227940f0f70162306482048ff6fc97ac Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Tue, 9 Apr 2024 10:27:25 -0400 Subject: [PATCH] remove 'we' --- _freeze/heckin-case-converter/execute-results/html.json | 4 ++-- heckin-case-converter.qmd | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/_freeze/heckin-case-converter/execute-results/html.json b/_freeze/heckin-case-converter/execute-results/html.json index 2c7a6ee..ae0bf8c 100644 --- a/_freeze/heckin-case-converter/execute-results/html.json +++ b/_freeze/heckin-case-converter/execute-results/html.json @@ -1,8 +1,8 @@ { - "hash": "21f6fe328746e9383a6e4ce4fc0a8128", + "hash": "e574f9355b2df3bb424c15083347d4a2", "result": { "engine": "knitr", - "markdown": "---\ntitle: \"A package from start to finish\"\nsubtitle: \"making a heckin' case converter\"\nfreeze: true\n---\n\n\nThe Rust crate ecosystem is rich with very small and very powerful utility libraries. One of the most downloaded crates is [heck](https://docs.rs/heck). It provides traits and structs to perform some of the most common case conversions.\n\nIn this tutorial we'll create a 0 dependency R package to provide the common case conversions. The resultant R package will be more performant but less flexible than the [`{snakecase}`](https://tazinho.github.io/snakecase/) R package. \n\nThis tutorial covers: \n\n- vectorization\n- `NA` handling\n- code generation using a macro\n\n## Getting started\n\nCreate a new R package:\n\n```r\nusethis::create_package(\"heck\")\n```\n\nWhen the new R package has opened up, add `extendr`.\n\n```r\nrextendr::use_extendr(crate_name = \"rheck\", lib_name = \"rheck\")\n```\n\n::: callout-note\nWhen adding the extendr dependency, make sure that the `crate_name` and `lib_name` arguments _are not_ `heck`. In order to add the `heck` crate as a dependency, the crate itself cannot be called `heck` because it creates a recursive dependency. Doing this allows us to name the R package `{heck}`, but the internal Rust crate is called `rheck`.\n:::\n\nNext, we need to add `heck` as a dependency. From your terminal, navigate to `src/rust` and run `cargo add heck`. With this, we have everything we need to get started.\n\n\n## snek case conversion\n\n\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse heck::ToSnekCase;\n```\n:::\n\n\nLet's start by creating a simple function to take a single string, and convert it to snake case. First, the trait `ToSnekCase` needs to be imported so that the method `to_snek_case()` is available to `&str`.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse heck::ToSnekCase;\n\n#[extendr]\nfn to_snek_case(x: &str) -> String {\n x.to_snek_case()\n}\n```\n:::\n\n\nSimple enough, right? Let's give it a shot. To make it accessible from your R session, it needs to be included in your `extendr_module! {}` macro. \n\n```rust\nextendr_module! {\n mod heck;\n fn to_snek_case;\n}\n```\n\nFrom your R session, run `rextendr::document()` followed by `devtools::load_all()` to make the function available. We'll skip these step from now on, but be sure to remember it!\n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(\"MakeMe-Snake case\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"make_me_snake_case\"\n```\n\n\n:::\n:::\n\n\nRarely is it useful to run a function on just a scalar character value. Rust, though, works with scalars by default and adding vectorization is another step. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError in to_snek_case(c(\"DontStep\", \"on-Snek\")): Not a string object.\n```\n\n\n:::\n:::\n\n\nProviding a character vector causes an error. So how do you go about vectorizing? \n\n## vectorizing snek case conversion\n\nTo vectorize this function, you need to be apply the conversion to each element in a character vector. The extendr wrapper struct for a character vector is called `Strings`. To take in a character vector and also return one, the function signature should look like this:\n\n```rust\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n}\n```\n\nThis says we have an argument `x` which must be a character vector and this function must also `->` return the `Strings` (a character vector).\n\nTo iterate through this you can use the `.into_iter()` method on the character vector. \n\n```rust\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n x\n .into_iter()\n // the rest of the function\n}\n```\n\nIterators have a method called `.map()` (yes, just like `purrr::map()`). It lets you apply a closure (an anonymous function) to each element of the iterator. In this case, each element is an [`Rstr`](https://extendr.github.io/extendr/extendr_api/wrapper/rstr/struct.Rstr.html). The `Rstr` has a method `.as_str()` which will return a string slice `&str`. We will use this and pass the result to `.to_snek_case()`. After having mapped over each element, the results are `.collect()`ed into another `Strings`. \n\n\n\n::: {.cell preamble='use_heck'}\n\n```{.rust .cell-code}\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n x\n .into_iter()\n .map(|xi| {\n xi.as_str().to_snek_case()\n })\n .collect::()\n}\n```\n:::\n\n\n\nThis new version of the function can be used in a vectorized manner: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"dont_step\" \"on_snek\" \n```\n\n\n:::\n:::\n\n\nBut can it handle a missing value out of the box? \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", NA_character_, \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"dont_step\" \"na\" \"on_snek\" \n```\n\n\n:::\n:::\n\n\nWell, sort of. The `as_str()` method when used on a missing value will return `\"NA\"` which is not in a user's best interest. \n\n\n## handling missing values\n\nInstead of returning `\"na\"`, it would be better to return an _actual_ missing value. Those can be created each scalar's `na()` method e.g. `Rstr::na()`. \n\nYou can modify the `.map()` statement to check if an `NA` is present, and, if so, return an `NA` value. To perform this check, use the `is_na()` method which returns a `bool` which is either `true` or `false`. The result can be [`match`ed](https://doc.rust-lang.org/book/ch06-02-match.html). When it is missing, the match arm returns the `NA` scalar value. When it is not missing, the `Rstr` is converted to snek case. However, since the `true` arm is an `Rstr` the other `false` arm must _also_ be an `Rstr`. To accomplish this use the `Rstr::from()` method. \n\n\n::: {.cell preamble='use_heck' profile='release'}\n\n```{.rust .cell-code}\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n x.into_iter()\n .map(|xi| match xi.is_na() {\n true => Rstr::na(),\n false => Rstr::from(xi.as_str().to_snek_case()),\n })\n .collect::()\n}\n```\n:::\n\n\nThis function can now handle missing values! \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", NA_character_, \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"dont_step\" NA \"on_snek\" \n```\n\n\n:::\n:::\n\n\n## automating other methods with a macro! \n\nThere are traits for the other case conversions such as `ToKebabCase`, `ToPascalCase`, `ToShoutyKebabCase` and others. The each have a similar method name: `.to_kebab_case()`, `to_pascal_case()`, `.to_shouty_kebab_case()`. You can either choose to copy the above and change the method call multiple times, _or_ use a macro as a form of code generation. \n\nA macro allows you to generate code in a short hand manner. This macro take an identifier which has a placeholder called `$fn_name`: `$fn_name:ident`. \n\n```rust\nmacro_rules! make_heck_fn {\n ($fn_name:ident) => {\n #[extendr]\n /// @export\n fn $fn_name(x: Strings) -> Strings {\n x.into_iter()\n .map(|xi| match xi.is_na() {\n true => Rstr::na(),\n false => Rstr::from(xi.as_str().$fn_name()),\n })\n .collect::()\n }\n };\n}\n```\n\nThe `$fn_name` placeholder is put as the function name definition which is the same as the method name. To use this macro to generate the rest of the functions the other traits need to be imported.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse heck::{\n ToKebabCase, ToShoutyKebabCase,\n ToSnekCase, ToShoutySnakeCase,\n ToPascalCase, ToUpperCamelCase,\n ToTrainCase, ToTitleCase,\n};\n```\n:::\n\n\nWith the traits in scope, the macro can be invoked to generate the other functions.\n\n```rust\nmake_heck_fn!(to_snek_case);\nmake_heck_fn!(to_shouty_snake_case);\nmake_heck_fn!(to_kebab_case);\nmake_heck_fn!(to_shouty_kebab_case);\nmake_heck_fn!(to_pascal_case);\nmake_heck_fn!(to_upper_camel_case);\nmake_heck_fn!(to_train_case);\nmake_heck_fn!(to_title_case);\n```\n\nNote that each of these functions should be added to the `extendr_module! {}` macro in order for them to be available from R. \n\n\n\n\n\nTest it out with the `to_shouty_kebab_case()` function! \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_shouty_kebab_case(\"lorem:IpsumDolor__sit^amet\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"LOREM-IPSUM-DOLOR-SIT-AMET\"\n```\n\n\n:::\n:::\n\n\nAnd with that, you've created an R package that provides case conversion using heck and with very little code!\n\n\n## bench marking with `{snakecase}`\n\nTo illustrate the performance gains from using a vectorized Rust funciton, we can create a `bench::mark()` between `to_snek_case()` and `snakecase::to_snake_case()` \n\n\n\n\n\nThe bench mark will use 5000 randomly generated lorem ipsum sentences. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- unlist(lorem::ipsum(5000, 1, 25))\n\nhead(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"Ipsum nulla interdum id laoreet vitae non nullam taciti tincidunt dictumst accumsan parturient class ad congue nam sollicitudin congue aliquam proin accumsan quisque etiam vulputate.\" \n[2] \"Consectetur quam mollis malesuada maecenas sodales volutpat tellus proin augue vehicula non pretium vitae porttitor congue ultricies laoreet blandit tempus sagittis fusce dis ridiculus facilisis taciti nascetur ultrices neque rhoncus nunc dictumst vulputate!\"\n[3] \"Lorem nisi sem pellentesque aliquet rutrum nec lacinia massa tortor varius facilisi proin sed eleifend nulla platea sociosqu erat felis viverra aenean arcu.\" \n[4] \"Elit elementum pretium suscipit tristique mus non vitae feugiat ad curae ligula dictumst sem sapien nostra enim pulvinar venenatis scelerisque vivamus neque porta habitasse porttitor mauris.\" \n[5] \"Sit senectus luctus taciti sagittis posuere posuere dignissim torquent nam nostra tristique felis ad nibh augue suscipit curae aptent accumsan proin hac in euismod ad nisl interdum?\" \n[6] \"Adipiscing parturient semper in risus ac lobortis suspendisse taciti fusce nulla vivamus conubia cubilia tellus erat rutrum nostra erat nec pulvinar velit orci fusce montes elementum integer porttitor ultricies platea sociosqu curabitur eleifend.\" \n```\n\n\n:::\n\n```{.r .cell-code}\nbench::mark(\n rust = to_snek_case(x),\n snakecase = snakecase::to_snake_case(x)\n)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 2 × 6\n expression min median `itr/sec` mem_alloc `gc/sec`\n \n1 rust 15.4ms 16.5ms 61.0 1.17MB 0 \n2 snakecase 234.6ms 235.1ms 4.19 12.32MB 6.98\n```\n\n\n:::\n:::\n\n\n\n\n## The whole thing\n\nIn just 42 lines of code (empty lines included), you can create a very performant R package! \n\n```rust\nuse extendr_api::prelude::*;\n\nuse heck::{\n ToKebabCase, ToPascalCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnekCase, ToTitleCase,\n ToTrainCase, ToUpperCamelCase,\n};\n\nmacro_rules! make_heck_fn {\n ($fn_name:ident) => {\n #[extendr]\n /// @export\n fn $fn_name(x: Strings) -> Strings {\n x.into_iter()\n .map(|xi| match xi.is_na() {\n true => Rstr::na(),\n false => Rstr::from(xi.as_str().$fn_name()),\n })\n .collect::()\n }\n };\n}\n\nmake_heck_fn!(to_snek_case);\nmake_heck_fn!(to_shouty_snake_case);\nmake_heck_fn!(to_kebab_case);\nmake_heck_fn!(to_shouty_kebab_case);\nmake_heck_fn!(to_pascal_case);\nmake_heck_fn!(to_upper_camel_case);\nmake_heck_fn!(to_train_case);\nmake_heck_fn!(to_title_case);\n\nextendr_module! {\n mod heck;\n fn to_snek_case;\n fn to_shouty_snake_case;\n fn to_kebab_case;\n fn to_shouty_kebab_case;\n fn to_pascal_case;\n fn to_upper_camel_case;\n fn to_title_case;\n fn to_train_case;\n}\n```", + "markdown": "---\ntitle: \"A package from start to finish\"\nsubtitle: \"making a heckin' case converter\"\nfreeze: true\n---\n\n\nThe Rust crate ecosystem is rich with very small and very powerful utility libraries. One of the most downloaded crates is [heck](https://docs.rs/heck). It provides traits and structs to perform some of the most common case conversions.\n\nIn this tutorial we'll create a 0 dependency R package to provide the common case conversions. The resultant R package will be more performant but less flexible than the [`{snakecase}`](https://tazinho.github.io/snakecase/) R package. \n\nThis tutorial covers: \n\n- vectorization\n- `NA` handling\n- code generation using a macro\n\n## Getting started\n\nCreate a new R package:\n\n```r\nusethis::create_package(\"heck\")\n```\n\nWhen the new R package has opened up, add `extendr`.\n\n```r\nrextendr::use_extendr(crate_name = \"rheck\", lib_name = \"rheck\")\n```\n\n::: callout-note\nWhen adding the extendr dependency, make sure that the `crate_name` and `lib_name` arguments _are not_ `heck`. In order to add the `heck` crate as a dependency, the crate itself cannot be called `heck` because it creates a recursive dependency. Doing this allows us to name the R package `{heck}`, but the internal Rust crate is called `rheck`.\n:::\n\nNext, `heck` is needed as a dependency. From your terminal, navigate to `src/rust` and run `cargo add heck`. With this, you have everything you need to get started.\n\n\n## snek case conversion\n\n\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse heck::ToSnekCase;\n```\n:::\n\n\nLet's start by creating a simple function to take a single string, and convert it to snake case. First, the trait `ToSnekCase` needs to be imported so that the method `to_snek_case()` is available to `&str`.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse heck::ToSnekCase;\n\n#[extendr]\nfn to_snek_case(x: &str) -> String {\n x.to_snek_case()\n}\n```\n:::\n\n\nSimple enough, right? Let's give it a shot. To make it accessible from your R session, it needs to be included in your `extendr_module! {}` macro. \n\n```rust\nextendr_module! {\n mod heck;\n fn to_snek_case;\n}\n```\n\nFrom your R session, run `rextendr::document()` followed by `devtools::load_all()` to make the function available. We'll skip these step from now on, but be sure to remember it!\n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(\"MakeMe-Snake case\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"make_me_snake_case\"\n```\n\n\n:::\n:::\n\n\nRarely is it useful to run a function on just a scalar character value. Rust, though, works with scalars by default and adding vectorization is another step. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError in to_snek_case(c(\"DontStep\", \"on-Snek\")): Not a string object.\n```\n\n\n:::\n:::\n\n\nProviding a character vector causes an error. So how do you go about vectorizing? \n\n## vectorizing snek case conversion\n\nTo vectorize this function, you need to be apply the conversion to each element in a character vector. The extendr wrapper struct for a character vector is called `Strings`. To take in a character vector and also return one, the function signature should look like this:\n\n```rust\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n}\n```\n\nThis says there is an argument `x` which must be a character vector and this function must also `->` return the `Strings` (a character vector).\n\nTo iterate through this you can use the `.into_iter()` method on the character vector. \n\n```rust\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n x\n .into_iter()\n // the rest of the function\n}\n```\n\nIterators have a method called `.map()` (yes, just like `purrr::map()`). It lets you apply a closure (an anonymous function) to each element of the iterator. In this case, each element is an [`Rstr`](https://extendr.github.io/extendr/extendr_api/wrapper/rstr/struct.Rstr.html). The `Rstr` has a method `.as_str()` which will return a string slice `&str`. You can take this slice and pass it on to `.to_snek_case()`. After having mapped over each element, the results are `.collect()`ed into another `Strings`. \n\n\n\n::: {.cell preamble='use_heck'}\n\n```{.rust .cell-code}\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n x\n .into_iter()\n .map(|xi| {\n xi.as_str().to_snek_case()\n })\n .collect::()\n}\n```\n:::\n\n\n\nThis new version of the function can be used in a vectorized manner: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"dont_step\" \"on_snek\" \n```\n\n\n:::\n:::\n\n\nBut can it handle a missing value out of the box? \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", NA_character_, \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"dont_step\" \"na\" \"on_snek\" \n```\n\n\n:::\n:::\n\n\nWell, sort of. The `as_str()` method when used on a missing value will return `\"NA\"` which is not in a user's best interest. \n\n\n## handling missing values\n\nInstead of returning `\"na\"`, it would be better to return an _actual_ missing value. Those can be created each scalar's `na()` method e.g. `Rstr::na()`. \n\nYou can modify the `.map()` statement to check if an `NA` is present, and, if so, return an `NA` value. To perform this check, use the `is_na()` method which returns a `bool` which is either `true` or `false`. The result can be [`match`ed](https://doc.rust-lang.org/book/ch06-02-match.html). When it is missing, the match arm returns the `NA` scalar value. When it is not missing, the `Rstr` is converted to snek case. However, since the `true` arm is an `Rstr` the other `false` arm must _also_ be an `Rstr`. To accomplish this use the `Rstr::from()` method. \n\n\n::: {.cell preamble='use_heck' profile='release'}\n\n```{.rust .cell-code}\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n x.into_iter()\n .map(|xi| match xi.is_na() {\n true => Rstr::na(),\n false => Rstr::from(xi.as_str().to_snek_case()),\n })\n .collect::()\n}\n```\n:::\n\n\nThis function can now handle missing values! \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", NA_character_, \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"dont_step\" NA \"on_snek\" \n```\n\n\n:::\n:::\n\n\n## automating other methods with a macro! \n\nThere are traits for the other case conversions such as `ToKebabCase`, `ToPascalCase`, `ToShoutyKebabCase` and others. The each have a similar method name: `.to_kebab_case()`, `to_pascal_case()`, `.to_shouty_kebab_case()`. You can either choose to copy the above and change the method call multiple times, _or_ use a macro as a form of code generation. \n\nA macro allows you to generate code in a short hand manner. This macro take an identifier which has a placeholder called `$fn_name`: `$fn_name:ident`. \n\n```rust\nmacro_rules! make_heck_fn {\n ($fn_name:ident) => {\n #[extendr]\n /// @export\n fn $fn_name(x: Strings) -> Strings {\n x.into_iter()\n .map(|xi| match xi.is_na() {\n true => Rstr::na(),\n false => Rstr::from(xi.as_str().$fn_name()),\n })\n .collect::()\n }\n };\n}\n```\n\nThe `$fn_name` placeholder is put as the function name definition which is the same as the method name. To use this macro to generate the rest of the functions the other traits need to be imported.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse heck::{\n ToKebabCase, ToShoutyKebabCase,\n ToSnekCase, ToShoutySnakeCase,\n ToPascalCase, ToUpperCamelCase,\n ToTrainCase, ToTitleCase,\n};\n```\n:::\n\n\nWith the traits in scope, the macro can be invoked to generate the other functions.\n\n```rust\nmake_heck_fn!(to_snek_case);\nmake_heck_fn!(to_shouty_snake_case);\nmake_heck_fn!(to_kebab_case);\nmake_heck_fn!(to_shouty_kebab_case);\nmake_heck_fn!(to_pascal_case);\nmake_heck_fn!(to_upper_camel_case);\nmake_heck_fn!(to_train_case);\nmake_heck_fn!(to_title_case);\n```\n\nNote that each of these functions should be added to the `extendr_module! {}` macro in order for them to be available from R. \n\n\n\n\n\nTest it out with the `to_shouty_kebab_case()` function! \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_shouty_kebab_case(\"lorem:IpsumDolor__sit^amet\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"LOREM-IPSUM-DOLOR-SIT-AMET\"\n```\n\n\n:::\n:::\n\n\nAnd with that, you've created an R package that provides case conversion using heck and with very little code!\n\n\n## bench marking with `{snakecase}`\n\nTo illustrate the performance gains from using a vectorized Rust funciton, a `bench::mark()` is created between `to_snek_case()` and `snakecase::to_snake_case()`.\n\n\n\n\n\nThe bench mark will use 5000 randomly generated lorem ipsum sentences. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- unlist(lorem::ipsum(5000, 1, 25))\n\nhead(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"Consectetur montes fames netus odio dis nulla ut habitasse tristique diam ac arcu ante lacus in metus odio sociosqu mattis cras vitae dignissim quis ullamcorper urna dis.\" \n[2] \"Consectetur sapien platea inceptos orci aliquet turpis urna in suscipit bibendum class cubilia pretium tempus tempor eros duis etiam sapien viverra.\" \n[3] \"Adipiscing mi tortor vitae aenean condimentum magna varius risus netus viverra lobortis habitant nulla ornare sapien dapibus fermentum taciti porttitor luctus odio pharetra lacinia imperdiet a himenaeos.\"\n[4] \"Amet eleifend habitasse malesuada est commodo nulla nullam libero erat vivamus scelerisque curae tortor porta torquent fermentum eget morbi cursus urna consequat ridiculus velit per!\" \n[5] \"Elit felis est facilisi ante scelerisque nam per venenatis eu neque cum quisque odio mauris phasellus ante erat potenti ultricies vehicula fames mi nullam montes malesuada.\" \n[6] \"Consectetur metus neque egestas tellus vulputate porta primis sociosqu posuere congue facilisis sociosqu ad convallis cras ante dictumst felis libero taciti eu ridiculus sollicitudin nascetur!\" \n```\n\n\n:::\n\n```{.r .cell-code}\nbench::mark(\n rust = to_snek_case(x),\n snakecase = snakecase::to_snake_case(x)\n)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 2 × 6\n expression min median `itr/sec` mem_alloc `gc/sec`\n \n1 rust 15ms 16.3ms 61.1 1.16MB 0 \n2 snakecase 249ms 250.2ms 4.00 12.27MB 6.00\n```\n\n\n:::\n:::\n\n\n\n\n## The whole thing\n\nIn just 42 lines of code (empty lines included), you can create a very performant R package! \n\n```rust\nuse extendr_api::prelude::*;\n\nuse heck::{\n ToKebabCase, ToPascalCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnekCase, ToTitleCase,\n ToTrainCase, ToUpperCamelCase,\n};\n\nmacro_rules! make_heck_fn {\n ($fn_name:ident) => {\n #[extendr]\n /// @export\n fn $fn_name(x: Strings) -> Strings {\n x.into_iter()\n .map(|xi| match xi.is_na() {\n true => Rstr::na(),\n false => Rstr::from(xi.as_str().$fn_name()),\n })\n .collect::()\n }\n };\n}\n\nmake_heck_fn!(to_snek_case);\nmake_heck_fn!(to_shouty_snake_case);\nmake_heck_fn!(to_kebab_case);\nmake_heck_fn!(to_shouty_kebab_case);\nmake_heck_fn!(to_pascal_case);\nmake_heck_fn!(to_upper_camel_case);\nmake_heck_fn!(to_train_case);\nmake_heck_fn!(to_title_case);\n\nextendr_module! {\n mod heck;\n fn to_snek_case;\n fn to_shouty_snake_case;\n fn to_kebab_case;\n fn to_shouty_kebab_case;\n fn to_pascal_case;\n fn to_upper_camel_case;\n fn to_title_case;\n fn to_train_case;\n}\n```", "supporting": [], "filters": [ "rmarkdown/pagebreak.lua" diff --git a/heckin-case-converter.qmd b/heckin-case-converter.qmd index 223d50c..13e16c8 100644 --- a/heckin-case-converter.qmd +++ b/heckin-case-converter.qmd @@ -32,7 +32,7 @@ rextendr::use_extendr(crate_name = "rheck", lib_name = "rheck") When adding the extendr dependency, make sure that the `crate_name` and `lib_name` arguments _are not_ `heck`. In order to add the `heck` crate as a dependency, the crate itself cannot be called `heck` because it creates a recursive dependency. Doing this allows us to name the R package `{heck}`, but the internal Rust crate is called `rheck`. ::: -Next, we need to add `heck` as a dependency. From your terminal, navigate to `src/rust` and run `cargo add heck`. With this, we have everything we need to get started. +Next, `heck` is needed as a dependency. From your terminal, navigate to `src/rust` and run `cargo add heck`. With this, you have everything you need to get started. ## snek case conversion @@ -90,7 +90,7 @@ fn to_snek_case(x: Strings) -> Strings { } ``` -This says we have an argument `x` which must be a character vector and this function must also `->` return the `Strings` (a character vector). +This says there is an argument `x` which must be a character vector and this function must also `->` return the `Strings` (a character vector). To iterate through this you can use the `.into_iter()` method on the character vector. @@ -103,7 +103,7 @@ fn to_snek_case(x: Strings) -> Strings { } ``` -Iterators have a method called `.map()` (yes, just like `purrr::map()`). It lets you apply a closure (an anonymous function) to each element of the iterator. In this case, each element is an [`Rstr`](https://extendr.github.io/extendr/extendr_api/wrapper/rstr/struct.Rstr.html). The `Rstr` has a method `.as_str()` which will return a string slice `&str`. We will use this and pass the result to `.to_snek_case()`. After having mapped over each element, the results are `.collect()`ed into another `Strings`. +Iterators have a method called `.map()` (yes, just like `purrr::map()`). It lets you apply a closure (an anonymous function) to each element of the iterator. In this case, each element is an [`Rstr`](https://extendr.github.io/extendr/extendr_api/wrapper/rstr/struct.Rstr.html). The `Rstr` has a method `.as_str()` which will return a string slice `&str`. You can take this slice and pass it on to `.to_snek_case()`. After having mapped over each element, the results are `.collect()`ed into another `Strings`. ```{extendrsrc preamble = "use_heck"} @@ -230,7 +230,7 @@ And with that, you've created an R package that provides case conversion using h ## bench marking with `{snakecase}` -To illustrate the performance gains from using a vectorized Rust funciton, we can create a `bench::mark()` between `to_snek_case()` and `snakecase::to_snake_case()` +To illustrate the performance gains from using a vectorized Rust funciton, a `bench::mark()` is created between `to_snek_case()` and `snakecase::to_snake_case()`. ```{r include=FALSE} rextendr::rust_source(code = r"(