Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

  1. Home
  2. Haskell Dictionary
  3. Strings ([Char]) / words / lines

Strings ([Char]) / words / lines

In Haskell, String is a type alias for [Char] (a list of Char). This means all list operation functions can be used directly on strings. Functions like words (split on whitespace), lines (split on newlines), and their inverses unwords and unlines allow concise text processing.

Syntax

-- -----------------------------------------------
--  String is a type alias for [Char]
-- -----------------------------------------------

-- A string literal is equivalent to a list of Char
"abc" == ['a', 'b', 'c']   -- True

-- A single character is of type Char (single quotes)
-- A string is a list of Char (double quotes)
'a'       :: Char
"abc"     :: String   -- which means [Char]

-- -----------------------------------------------
--  Splitting and joining strings
-- -----------------------------------------------

-- words — splits on whitespace (spaces, tabs, newlines)
-- Result is [String]
words "Yuji Itadori"         -- ["Yuji", "Itadori"]

-- lines — splits on '\n'
lines "Yuji\nMegumi\nNobara" -- ["Yuji", "Megumi", "Nobara"]

-- unwords — joins [String] with a single space
unwords ["Yuji", "Itadori"]  -- "Yuji Itadori"

-- unlines — joins [String] appending '\n' to each element
unlines ["Yuji", "Megumi"]   -- "Yuji\nMegumi\n"

-- -----------------------------------------------
--  length / reverse / take / drop
-- -----------------------------------------------

-- String is a list, so general list functions apply
length "Sukuna"              -- 6
reverse "Sukuna"             -- "anuKuS"
take 5 "Ryomen Sukuna"       -- "Ryome"
drop 7 "Ryomen Sukuna"       -- "Sukuna"

Function List

Function / OperatorType SignatureDescription
wordsString -> [String]Splits a string into a list of words on whitespace.
linesString -> [String]Splits a string into a list of lines on '\n'.
unwords[String] -> StringJoins a list of strings with a single space.
unlines[String] -> StringJoins a list of strings, appending '\n' to each element.
length[a] -> IntReturns the number of characters (Char count) in the string.
reverse[a] -> [a]Returns a new string with the characters in reverse order.
take nInt -> [a] -> [a]Takes the first n characters from the string.
drop nInt -> [a] -> [a]Returns the string after dropping the first n characters.
concat[[a]] -> [a]Concatenates a [String] with no separator.
concatMap(a -> [b]) -> [a] -> [b]Applies a function to each element and concatenates the results.
elem cChar -> String -> BoolReturns True if character c is found in the string.
filter(a -> Bool) -> [a] -> [a]Returns a string keeping only the Char elements that satisfy the predicate.
map(a -> b) -> [a] -> [b]Returns a new string by applying a function to each Char.
null[a] -> BoolReturns True if the string is empty.

Sample Code

jjk_string_basic.hs
-- jjk_string_basic.hs — Sample demonstrating basic String / Char operations
-- Uses Jujutsu Kaisen character names as sample data
-- to demonstrate words / lines / unwords / unlines and more
--
--   Compile and run:
--     ghc jjk_string_basic.hs -o jjk_string_basic && ./jjk_string_basic

module Main where

-- -----------------------------------------------
--  Constant definitions
-- -----------------------------------------------

-- Full name of a sorcerer (space-separated)
itadoriFullName :: String
itadoriFullName = "Yuji Itadori"

sukunaFullName :: String
sukunaFullName = "Ryomen Sukuna"

-- Member list joined by newlines
teamRaw :: String
teamRaw = "Yuji Itadori\nMegumi Fushiguro\nNobara Kugisaki"

-- Technique name list separated by spaces
techniqueRaw :: String
techniqueRaw = "Divergent Fist Black Flash Mahoraga"

-- -----------------------------------------------
--  Verifying that String is [Char]
-- -----------------------------------------------

-- Extract the first character of a string (head works on [Char])
firstChar :: String -> Char
firstChar s = head s

-- Returns the character count of a string
charCount :: String -> Int
charCount = length

-- -----------------------------------------------
--  Entry point
-- -----------------------------------------------
main :: IO ()
main = do
  -- ----- Verify that String is [Char] -----
  putStrLn "===== String is [Char] ====="
  putStrLn $ "itadoriFullName           : " ++ itadoriFullName
  putStrLn $ "head itadoriFullName      : " ++ [firstChar itadoriFullName]
  putStrLn $ "length itadoriFullName    : " ++ show (charCount itadoriFullName)
  putStrLn $ "reverse sukunaFullName    : " ++ reverse sukunaFullName
  putStrLn $ "take 6 sukunaFullName     : " ++ take 6 sukunaFullName
  putStrLn $ "drop 7 sukunaFullName     : " ++ drop 7 sukunaFullName

  -- ----- words / unwords -----
  putStrLn "\n===== words / unwords ====="
  let techWords = words techniqueRaw
  putStrLn $ "techniqueRaw              : " ++ techniqueRaw
  putStrLn $ "words techniqueRaw        : " ++ show techWords
  putStrLn $ "unwords techWords         : " ++ unwords techWords

  -- ----- lines / unlines -----
  putStrLn "\n===== lines / unlines ====="
  let members = lines teamRaw
  putStrLn $ "teamRaw (with newlines)    :"
  putStr teamRaw
  putStrLn "\n"
  putStrLn $ "lines teamRaw             : " ++ show members
  putStr    $ "unlines members           :\n" ++ unlines members

  -- ----- filter / map -----
  putStrLn "===== filter / map ====="
  -- Remove all space characters
  let noSpace = filter (/= ' ') itadoriFullName
  putStrLn $ "filter (/= ' ') ...       : " ++ noSpace
  -- Convert all characters to lowercase (simple ASCII conversion)
  -- Uses ord / chr for a basic conversion
  let toLowerAscii c
        | c >= 'A' && c <= 'Z' = toEnum (fromEnum c + 32)
        | otherwise              = c
  let lowered = map toLowerAscii itadoriFullName
  putStrLn $ "map toLower ...           : " ++ lowered

  -- ----- Search for a character with elem -----
  putStrLn "\n===== elem ====="
  putStrLn $ "'Y' `elem` itadori        : " ++ show ('Y' `elem` itadoriFullName)
  putStrLn $ "'z' `elem` itadori        : " ++ show ('z' `elem` itadoriFullName)

  -- ----- concat / concatMap -----
  putStrLn "\n===== concat / concatMap ====="
  let surnames = ["Itadori", "Fushiguro", "Kugisaki"]
  putStrLn $ "concat surnames           : " ++ concat surnames
  -- Extract the initial of each name to build an initials string
  let initials = concatMap (\name -> [head name, '.', ' ']) surnames
  putStrLn $ "initials                  : " ++ initials
ghc jjk_string_basic.hs -o jjk_string_basic && ./jjk_string_basic
[1 of 1] Compiling Main             ( jjk_string_basic.hs, jjk_string_basic.o )
Linking jjk_string_basic ...
===== String is [Char] =====
itadoriFullName           : Yuji Itadori
head itadoriFullName      : Y
length itadoriFullName    : 13
reverse sukunaFullName    : anuKuS nemouyR
take 6 sukunaFullName     : Ryomen
drop 7 sukunaFullName     : Sukuna

===== words / unwords =====
techniqueRaw              : Divergent Fist Black Flash Mahoraga
words techniqueRaw        : ["Divergent","Fist","Black","Flash","Mahoraga"]
unwords techWords         : Divergent Fist Black Flash Mahoraga

===== lines / unlines =====
teamRaw (with newlines)    :
Yuji Itadori
Megumi Fushiguro
Nobara Kugisaki

lines teamRaw             : ["Yuji Itadori","Megumi Fushiguro","Nobara Kugisaki"]
unlines members           :
Yuji Itadori
Megumi Fushiguro
Nobara Kugisaki

===== filter / map =====
filter (/= ' ') ...       : YujiItadori
map toLower ...           : yuji itadori

===== elem =====
'Y' `elem` itadori        : True
'z' `elem` itadori        : False

===== concat / concatMap =====
concat surnames           : ItadoriFushiguroKugisaki
initials                  : I. F. K.
jjk_string_split.hs
-- jjk_string_split.hs — Implements a function to split a string on an arbitrary delimiter
-- Because words / lines only handle fixed delimiters,
-- this implements a general-purpose version using recursion to understand how words works
--
--   Compile and run:
--     ghc jjk_string_split.hs -o jjk_string_split && ./jjk_string_split

module Main where

-- -----------------------------------------------
--  Split function for an arbitrary delimiter
-- -----------------------------------------------

-- splitOn sep str — splits str into [String] using sep as delimiter
-- Consecutive separators produce empty string tokens
splitOn :: Char -> String -> [String]
splitOn _   ""  = [""]
splitOn sep str =
  let (token, rest) = break (== sep) str
  in  token : case rest of
                []     -> []
                (_:xs) -> splitOn sep xs

-- joinWith sep strs — joins [String] using sep as delimiter
joinWith :: Char -> [String] -> String
joinWith _   []     = ""
joinWith _   [x]    = x
joinWith sep (x:xs) = x ++ [sep] ++ joinWith sep xs

-- -----------------------------------------------
--  Entry point
-- -----------------------------------------------
main :: IO ()
main = do
  -- ----- Split a comma-delimited technique name list -----
  let csvRow = "Divergent Fist,Black Flash,Mahoraga,Hollow Purple"
  let techniques = splitOn ',' csvRow
  putStrLn "===== splitOn ',' ====="
  putStrLn $ "Input  : " ++ csvRow
  putStrLn $ "Split  : " ++ show techniques

  -- ----- Rejoin to recover the original string -----
  let rejoined = joinWith ',' techniques
  putStrLn $ "Rejoined : " ++ rejoined
  putStrLn $ "Matches original: " ++ show (csvRow == rejoined)

  -- ----- Split a slash-delimited path-style string -----
  let path = "jujutsu/tokyo/itadori"
  let segments = splitOn '/' path
  putStrLn "\n===== splitOn '/' ====="
  putStrLn $ "Input  : " ++ path
  putStrLn $ "Split  : " ++ show segments

  -- ----- Comparison with words (differs in handling consecutive spaces) -----
  let spaced = "Yuji  Itadori"   -- two spaces
  putStrLn "\n===== words vs splitOn ' ' ====="
  putStrLn $ "Input                    : \"" ++ spaced ++ "\""
  putStrLn $ "words spaced            : " ++ show (words spaced)
  putStrLn $ "splitOn ' ' spaced      : " ++ show (splitOn ' ' spaced)
  -- words treats consecutive spaces as a single separator
  -- splitOn produces an empty string token between consecutive separators
ghc jjk_string_split.hs -o jjk_string_split && ./jjk_string_split
[1 of 1] Compiling Main             ( jjk_string_split.hs, jjk_string_split.o )
Linking jjk_string_split ...
===== splitOn ',' =====
Input  : Divergent Fist,Black Flash,Mahoraga,Hollow Purple
Split  : ["Divergent Fist","Black Flash","Mahoraga","Hollow Purple"]
Rejoined : Divergent Fist,Black Flash,Mahoraga,Hollow Purple
Matches original: True

===== splitOn '/' =====
Input  : jujutsu/tokyo/itadori
Split  : ["jujutsu","tokyo","itadori"]

===== words vs splitOn ' ' =====
Input                    : "Yuji  Itadori"
words spaced            : ["Yuji","Itadori"]
splitOn ' ' spaced      : ["Yuji","","Itadori"]

Overview

In Haskell, String is a type alias for [Char], so all list operation functions apply to strings directly. words treats consecutive whitespace as a single separator, while lines splits on '\n' boundaries. The inverse unwords joins with a single space, and unlines appends '\n' to each element before joining. General-purpose list functions such as filter, map, and concatMap can be applied directly to String, enabling a wide range of text processing without external libraries. For case conversion, use toUpper and toLower from Data.Char. For regex-based search and replacement, use packages in the Text.Regex family. For performance-critical workloads, Data.Text (from the text package) is worth considering, as it uses a different internal representation with better memory efficiency and throughput. For the basics of lists, see List Basics ([] and :). For string conversion with show and read, see show and read.

Common Mistake 1: Applying head to an empty string causes an exception

Because String is a [Char] list, calling head on an empty string "" throws a runtime exception (Prelude.head: empty list). Check for emptiness with null before using head, or handle it safely with pattern matching.

headEmpty.hs
module Main where

getInitial :: String -> Char
getInitial s = head s   -- Passing an empty string causes an exception

main :: IO ()
main = do
  print (getInitial "")
ghc headEmpty.hs -o headEmpty && ./headEmpty
headEmpty: Prelude.head: empty list

When the string may be empty, use pattern matching or a null check to handle it safely.

headEmptyOk.hs
module Main where

-- Handle an empty string safely with pattern matching
getInitial :: String -> Maybe Char
getInitial []    = Nothing
getInitial (c:_) = Just c

main :: IO ()
main = do
  print (getInitial "")           -- Nothing
  print (getInitial "Itadori")    -- Just 'I'
ghc headEmptyOk.hs -o headEmptyOk && ./headEmptyOk
Nothing
Just 'I'

Common Mistake 2: Confusing Char and String quotes

In Haskell, 'a' (single quotes) is a Char, while "a" (double quotes) is a String (= [Char]). Although they look similar, they are different types, and mixing them causes a compile error.

charStringMix.hs
module Main where

main :: IO ()
main = do
  -- 'Y' is Char, "uji" is String
  -- Trying to concatenate Char and String directly with ++ causes an error
  putStrLn ('Y' ++ "uji")
ghc charStringMix.hs -o charStringMix
Couldn't match type 'Char' with '[Char]'

Use the : operator to prepend a Char to a String, or wrap it in a list with [c].

charStringMixOk.hs
module Main where

main :: IO ()
main = do
  -- Use : to prepend a Char to the front of a String
  putStrLn ('Y' : "uji")          -- "Yuji"
  -- You can also wrap in a list and use ++
  putStrLn (['Y'] ++ "uji")       -- "Yuji"
ghc charStringMixOk.hs -o charStringMixOk && ./charStringMixOk
Yuji
Yuji

Common Mistake 3: Index access via !! is O(n)

Because String is a linked list, str !! n (accessing the nth character) is an O(n) operation that traverses from the head. Heavy use of index access on large strings can affect performance. When frequent random access is needed, Data.Text is worth considering.

indexAccess.hs
module Main where

main :: IO ()
main = do
  let name = "Fushiguro Megumi"
  -- !! is O(n) — traverses n elements from the head
  print (name !! 0)    -- 'F'
  print (name !! 10)   -- 'M' (traverses 10 elements from the head)
  -- Indexes near the end are especially costly
  print (name !! (length name - 1))   -- 'i'
ghc indexAccess.hs -o indexAccess && ./indexAccess
'F'
'M'
'i'

When processing elements sequentially, use map or zipWith. To extract a specific element, combine take and drop to avoid !!.

Overview

If you find any errors or copyright issues, please .