diff --git a/Spintax.cabal b/Spintax.cabal
--- a/Spintax.cabal
+++ b/Spintax.cabal
@@ -1,5 +1,5 @@
 name:                Spintax
-version:             0.3.6.1
+version:             0.3.7.0
 synopsis:            Random text generation based on spintax
 description:         Random text generation based on spintax with nested alternatives and empty options.
 homepage:            https://github.com/MichelBoucey/spintax
@@ -12,14 +12,17 @@
 build-type:          Simple
 cabal-version:       >=1.10
 
-Tested-With: GHC ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8 || ==9.6.3 || ==9.8.2
+Tested-With: GHC ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8 || ==9.6.3 || ==9.8.2 || ==9.10.1
 
 library
-  hs-source-dirs:    src
+  hs-source-dirs:    src/
+  default-extensions: OverloadedStrings
   exposed-modules:   Text.Spintax
+                   , Text.Spintax.RandomPhrase
   build-depends:     attoparsec   >= 0.12.1.6 && < 0.15
                    , base         >= 4.7 && < 5
-                   , extra        >= 1.4.3 && < 1.8
+                   , either       >= 5 && < 6
+                   , extra        >= 1.4.3 && < 1.9
                    , mtl          >= 2.2.1 && < 2.4
                    , mwc-random   >= 0.13.3.2 && < 0.16
                    , text         >=1.1 && < 2.2
diff --git a/src/Text/Spintax.hs b/src/Text/Spintax.hs
--- a/src/Text/Spintax.hs
+++ b/src/Text/Spintax.hs
@@ -1,11 +1,11 @@
 {-# LANGUAGE FlexibleContexts  #-}
-{-# LANGUAGE OverloadedStrings #-}
 
-module Text.Spintax (spintax) where
+module Text.Spintax where
 
 import           Control.Applicative  ((<|>))
 import           Control.Monad.Reader (ask, runReaderT)
 import           Data.Attoparsec.Text
+import           Data.List            as L (intersperse)
 import qualified Data.List.Extra      as E
 import qualified Data.Text            as T
 import           System.Random.MWC
@@ -18,69 +18,81 @@
 spintax :: T.Text -> IO (Either String T.Text)
 spintax template =
   createSystemRandom >>= runReaderT (spin template)
-  where
-    spin t = go T.empty [] t (0::Int)
-      where
+    where
+      spin t = go T.empty [] t (0::Int)
+        where
 
-        go o as i l
-          | l < 0  = parseFail
-          | l == 0 =
-            case parse spinSyntax i of
-              Done r m  ->
-                case m of
-                  "{"                      -> go o as r (l+1)
-                  n | n == "}" || n == "|" -> parseFail
-                  _                        -> go (o <> m) as r l
-              Partial _ -> pure (Right $ o <> i)
-              Fail {}   -> parseFail
-          | l == 1 =
-            case parse spinSyntax i of
-              Done r m ->
-                case m of
-                  "{" -> go o (addAlter m) r (l+1)
-                  "}" -> do
-                    a <- spin =<< randAlter =<< ask
-                    case a of
-                      Right t' -> go (o <> t') [] r (l-1)
-                      Left _   -> parseFail
-                  "|" ->
-                    if E.null as
-                      then go o ["",""] r l
-                      else go o (E.snoc as "") r l
-                  _   -> go o (addAlter m) r l
-              Partial _ -> parseFail
-              Fail {} -> parseFail
-          | l > 1 =
-            case parse spinSyntax i of
-              Done r m ->
-                case m of
-                  "{" -> go o (addAlter m) r (l+1)
-                  "}" -> go o (addAlter m) r (l-1)
-                  _   -> go o (addAlter m) r l
-              Partial _ -> parseFail
-              Fail {}   -> parseFail
-          where
-            addAlter n =
-                case E.unsnoc as of
-                  Just (xs,x) -> E.snoc xs (x <> n)
-                  Nothing     -> pure n
-            randAlter g =
-              (\r -> (!!) as (r-1)) <$> uniformR (1,E.length as) g
-        go _ _ _ _ = parseFail
+          go o as i l
+            | l < 0  = parseFail
+            | l == 0 =
+              case parse spinSyntax i of
+                Done r m  ->
+                  case m of
+                    "{"                      -> go o as r (l+1)
+                    n | n == "}" || n == "|" -> parseFail
+                    _                        -> go (o <> m) as r l
+                Partial _ -> pure (Right $ o <> i)
+                Fail {}   -> parseFail
+            | l == 1 =
+              case parse spinSyntax i of
+                Done r m ->
+                  case m of
+                    "{" -> go o (addAlter m) r (l+1)
+                    "}" -> do
+                      a <- spin =<< randAlter =<< ask
+                      case a of
+                        Right t' -> go (o <> t') [] r (l-1)
+                        Left _   -> parseFail
+                    "|" ->
+                      if E.null as
+                        then go o ["",""] r l
+                        else go o (E.snoc as "") r l
+                    _   -> go o (addAlter m) r l
+                Partial _ -> parseFail
+                Fail {} -> parseFail
+            | l > 1 =
+              case parse spinSyntax i of
+                Done r m ->
+                  case m of
+                    "{" -> go o (addAlter m) r (l+1)
+                    "}" -> go o (addAlter m) r (l-1)
+                    _   -> go o (addAlter m) r l
+                Partial _ -> parseFail
+                Fail {}   -> parseFail
+            where
+              addAlter n =
+                  case E.unsnoc as of
+                    Just (xs,x) -> E.snoc xs (x <> n)
+                    Nothing     -> pure n
+              randAlter g =
+                (\r -> (!!) as (r-1)) <$> uniformR (1,E.length as) g
+          go _ _ _ _ = parseFail
 
-        parseFail = fail "Spintax template parsing failure"
+          parseFail = fail "Spintax template parsing failure"
 
-        spinSyntax =
-          openBrace <|> closeBrace <|> pipe <|> content
-            where
-              openBrace = string "{"
-              closeBrace = string "}"
-              pipe = string "|"
-              content =
-                takeWhile1 ctt
-                  where
-                    ctt '{' = False
-                    ctt '}' = False
-                    ctt '|' = False
-                    ctt _   = True
+          spinSyntax =
+            openBrace <|> closeBrace <|> pipe <|> content
+              where
+                openBrace = string "{"
+                closeBrace = string "}"
+                pipe = string "|"
+                content =
+                  takeWhile1 ctt
+                    where
+                      ctt '{' = False
+                      ctt '}' = False
+                      ctt '|' = False
+                      ctt _   = True
+
+-- * Utils
+
+-- | Write a spintax alternative
+--
+-- >λ>  writeSpintaxAlternative ["apple","apricot","banana","coconut"]
+-- "{apple|apricot|banana|coconut}"
+writeSpintaxAlternative :: [T.Text] -> T.Text
+writeSpintaxAlternative = writeSpintaxExpression "|"
+
+writeSpintaxExpression :: T.Text -> [T.Text] -> T.Text
+writeSpintaxExpression s l = "{" <> T.concat (L.intersperse s l) <> "}"
 
diff --git a/src/Text/Spintax/RandomPhrase.hs b/src/Text/Spintax/RandomPhrase.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Spintax/RandomPhrase.hs
@@ -0,0 +1,20 @@
+module Text.Spintax.RandomPhrase where
+
+import           Data.Either.Combinators (mapRight)
+import qualified Data.Text               as T
+import           Text.Spintax
+
+newtype RandomPhrase = RandomPhrase { unRandomPhrase :: T.Text }
+
+instance Show RandomPhrase where
+  show (RandomPhrase t) = show t
+
+-- | Generate random passphrase or unique id
+--
+-- >λ> randomPhrase "-" [["blacky","monk","gillespie","coltrane"],["apple","apricot","banana","coconut"],["kant","hegel","husserl","habermas"]]
+-- > Right "coltrane-coconut-kant"
+--
+randomPhrase :: T.Text -> [[T.Text]] -> IO (Either String RandomPhrase)
+randomPhrase s ls =
+  mapRight RandomPhrase <$> spintax (writeSpintaxExpression s $ writeSpintaxAlternative <$> ls)
+
