(Σ*)² ⟲ Σ* (aka Round-Trip a String Pair)
I know this is extra credit, but your problem got me thinking a lot about this and what interested me the most was not just separate being the left inverse of combine, but having them be true inverses of each other. This ends up with us defining a bijection from String -&gt; String x String. The path I took was as follows

Define a bijection from String -&gt; N
Define a bijection from N -&gt; N x N
Combine them to get the functions we need

String -&gt; N
Let r be the size of the character set you're working with and give each character a unique value from 1 to r, say f(c) = n. Going from right to left and indexing starting from zero, create the number f(s[0])r^0 + f(s[1])r^1 + f(s[2])r^2 ... f(s[length(s)])r^(length(s)). The lowest number you can get this way is 1, so we let the empty string equal 0.
N -&gt; N x N
Take n in any base you like. Separate even digits into one part of the tuple and the odd digits into the other to make a pair of numbers. E.g. 12345 -&gt; (24, 135). To work your way back, combine the numbers in the opposite way, inserting extra zeros as necessary.
Putting it all together
Haskell

module Main where

import Data.Function ((&amp;))
-- tio is saying this isn't correct for some reason so gotta define it manually
-- import Data.Functor ((&lt;&amp;&gt;))

main :: IO ()
main = do
  putStrLn $ show $ separate &quot;Hello, world!&quot;
  putStrLn $ combine (&quot;Hello, world!&quot;, &quot;&quot;)
  putStrLn $ show $ separate $ combine (&quot;Hello, world!&quot;, &quot;&quot;)
  putStrLn $ combine $ separate &quot;Hello, world!&quot; 

separate :: String -&gt; (String, String)
separate str =
  str
  &lt;&amp;&gt; toInt
  &amp; fromNZ
  &amp; split
  &amp; tmap toNZ
  &amp; tmap (map fromInt)

combine :: (String, String) -&gt; String
combine strs =
  strs
  &amp; tmap (map toInt)
  &amp; tmap fromNZ
  &amp; join
  &amp; toNZ
  &lt;&amp;&gt; fromInt

base :: Int
base = 95

toInt :: Char -&gt; Int
toInt c = fromEnum c - 31

fromInt :: Int -&gt; Char
fromInt i = toEnum $ i + 31

strip :: [Int] -&gt; [Int]
strip is = case is of
  0 : i : tail -&gt; strip $ i : tail
  _ -&gt; is

split :: [Int] -&gt; ([Int], [Int])
split =
  tmap strip . fst . foldr
    (\a ((acc1, acc0), i) -&gt;
      (if mod i 2 == 0 then
        (acc1, a : acc0)
      else
        (a : acc1, acc0)
      , i + 1
      )
    )
    (([], []), 0)

join :: ([Int], [Int]) -&gt; [Int]
join (l1, l2)= strip $ reverse $ go [] (reverse l1, reverse l2)
  where
    go :: [Int] -&gt; ([Int], [Int]) -&gt; [Int]
    go acc (l1', l2') = case (l1', l2') of
      (head1 : tail1, head2 : tail2) -&gt; head2 : head1 : go acc (tail1, tail2)
      ([], head : tail) -&gt; head : 0 : go acc ([], tail)
      (head : tail, []) -&gt; 0 : head : go acc (tail, [])
      ([], []) -&gt;  acc

tmap :: (a -&gt; b) -&gt; (a, a) -&gt; (b, b)
tmap f (a1, a2) = (f a1, f a2)

fromNZ :: [Int] -&gt; [Int]
fromNZ is =
  if is == [] then [0]
  else let
    (result, finalCarry) =
      foldr
        (\i' (acc, carry) -&gt;
          let
            i =
              if carry then i' + 1
              else i'
          in
            if i &gt;= base then (i - base : acc, True)
            else (i : acc, False)
        )
        ([], False)
        is
  in
    if finalCarry then 1 : result
    else result

toNZ :: [Int] -&gt; [Int]
toNZ is =
  if is == [0] then []
  else let
    (result, finalUncarry) =
      foldr
        (\i' (acc, uncarry) -&gt;
          let
            i =
              if uncarry then i' - 1
              else i'
          in
            if i &lt;= 0 then (i + base : acc, True)
            else (i : acc, False)
        )
        ([], False)
        is
  in
    if finalUncarry then
      case result of
        a : tail -&gt;
          if a == base then tail
          else result
        _ -&gt; result
    else result

m &lt;&amp;&gt; f = f &lt;$&gt; m
infixl 1 &lt;&amp;&gt;

Try it online!