Skip to content

regexp: add S.replace and S.replace_#685

Closed
davidchambers wants to merge 1 commit intomasterfrom
davidchambers/replace
Closed

regexp: add S.replace and S.replace_#685
davidchambers wants to merge 1 commit intomasterfrom
davidchambers/replace

Conversation

@davidchambers
Copy link
Copy Markdown
Member

This pull request adds the following two functions:

replace  :: RegExp ->                          String  -> String -> String
replace_ :: RegExp -> (Array (Maybe String) -> String) -> String -> String

Design decisions:

  • Both functions take pattern :: RegExp, so there is no straightforward equivalent of s1.replace (s2, s3) for strings s1, s2, and s3. One could use S.replace (S.regex ('') (S.regexEscape (s2))) (s3) (s1), but it would be simpler and clearer to use String#replace directly in such cases.

    Using pattern :: RegExp exclusively provides two benefits:

    • Simplicity. Supporting both RegExp and String would require splitting replace into two functions (which would pose a naming challenge) or using Either RegExp String (which would not be ergonomic).

    • Clarity. It's unclear whether R.replace ('o') ('x') ('foo') should evaluate to 'fxo' or to 'fxx', whereas the presence of the g flag in /o/g indicates that all occurrences of o should be replaced, and the absence of the g flag in /o/ indicates that only the first occurrence of o should be replaced.

  • Like replace', S.replace_ ignores the “offset” and “string” arguments provided by String#replace (when given a function). These are rarely useful.

  • Unlike replace', S.replace_ ignores the “groups” argument provided by String#replace (when given a function) if the pattern contains any named capturing groups. I included a fix for this deficiency in purescript/purescript-strings#126.

  • Unlike replace', S.replace_ ignores the “match” argument provided by String#replace (when given a function). This simplifies the function's type, and saves one from using S.K (...) when only the captured groups are important (as is commonly the case). If one does require access to the matched substring as a whole, one can use /(...)/ to capture it. Admittedly, this approach is impractical if the RegExp object is defined in another module, but in such exceptional cases one can of course use String#replace directly.

  • Unlike replace', S.replace_ acknowledges the existence of optional capturing groups:

    > S.replace_ (S.regex ('') ('(Pure)?(Script)')) (S.show) ('JavaScript')
    'Java[Nothing, Just ("Script")]'

    Evaluating the equivalent PureScript expression results in an exception being thrown:

    > replace' (unsafeRegex "(Pure)?(Script)" noFlags) (const show) "JavaScript"
    ! TypeError: undefined is not an object (evaluating 's.length')

    I have proposed the use of Array (Maybe String) in place of Array String in purescript/purescript-strings#126.

I have opened sanctuary-js/sanctuary-site#87 to provide several examples of replace and replace_ in use.

@sanctuary-js/owners, what do you think of the proposed additions to the library?


@CrossEye, you may like to rethink R.replace, which currently exposes all the quirks of String#replace.

Comment thread .circleci/config.yml
Comment on lines -31 to +33
0) test_with_version 8 ;;
1) test_with_version 10 ;;
2) npm install && npm test ;;
0) test_with_version 10 ;;
1) npm install && npm test ;;
2) test_with_version 14 ;;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to use named capturing groups (in the test suite) I had to drop 8 from the set of Node.js versions.

Comment thread index.js
//. > const replaceString = S.curry3 ((what, replacement, string) =>
//. . string.replace (what, replacement)
//. . )
//. > const defineProperty = S.curry3 (Object.defineProperty)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not easy to find a built-in function that takes three arguments!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use a variadic function, such as Math.max, right? Then you could create curried built-ins for any number of arguments.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. Binary versions of variadic functions are compelling, but the use cases for ternary versions are less apparent. I am happy to have found Object.defineProperty for this example. :)

@Avaq
Copy link
Copy Markdown
Member

Avaq commented Jul 27, 2020

Regarding naming, maybe we can use

replace     :: RegExp -> (Array (Maybe String) -> String) -> String -> String
replaceWith :: RegExp ->                          String  -> String -> String

(or replaceBy, but I wanted to stay consistent with joinWith)


@davidchambers said:

Naming these functions is challenging because '$1-$2-$3' and ([$1, $2, $3]) => `${$1}-${$2}-${$3}` both specify how a replacement string is to be determined. Any sensible name for one function makes just as much sense for the other function. -- sanctuary-js/sanctuary-site#87 (comment)

@Avaq
Copy link
Copy Markdown
Member

Avaq commented Jul 27, 2020

because '$1-$2-$3'

I did not realise that in the replacement string $n has special meaning.

@davidchambers
Copy link
Copy Markdown
Member Author

replaceWith would be an excellent name for a RegExp -> String -> String -> String function without support for special replacement patterns. 🤔

@Avaq
Copy link
Copy Markdown
Member

Avaq commented Jul 27, 2020

I like the idea of a function RegExp -> String -> String -> String, that doesn't support special replacement patterns. It seems more sane to me, and less prone to creation of some weird attack vector where a user can supply $& as input to gain access to the raw string. I think the usage of matching groups could be left solely for RegExp -> (Array (Maybe String) -> String) -> String -> String.

@davidchambers
Copy link
Copy Markdown
Member Author

Closing in favour of #686

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants