Because of the limited amount of resources, I need to propose a question here. I have been struggling with functional programming, the endless Haskell tutorials don't really help me. So what I want to achieve, in Clean language, is to split a string like " car cow cat "
to a list of strings ["car","cow","cat"]
. Can you provide me a detailed answer (does not have to be complete code), on how to iterate through this string, and especially the part when the newly constructed strings are added to the list?

- 105
- 1
- 12
-
Not answering your question specifically, but you can use build in functions from `Data.List.Split`: `filter (not .null) $ splitOn " " " car cat dog cow moop"` returns `["car","cat","dog","cow","moop"]` – Arnon Oct 14 '14 at 09:41
-
There might be a library function available in Clean similar to Haskell's `words` function. – jev Oct 14 '14 at 09:53
-
@Arnon Well thanks for the answer, I have ideas about what to use, but since this is my first time with functional programming, I may need more help, if possible. – Gergo Szucs Oct 14 '14 at 10:00
2 Answers
I'm going to offer a simple solution. There are infinitely better ways of doing this in Haskell, but it's the simplest I can think for someone new in functional programming, without using any specifically Haskell function like takeWhile, or even any folds and maps...
You basically want to simulate iterating over a list, so here is what I suggest:
Define a function that will take a string and a split-by character. This function will return a list of strings -
spliton :: String -> Char -> [String]
To move over the list, we'll want to gobble up characters until we hit one of our splitting characters. We'll also want to save the word we've saved up until now, and the entire list of words. For that, we'll define a subfunction that will save the states
spliton' :: String -> Char -> String -> [String] -> [String]
spliton' [] _ sofar res = res ++ [sofar]
I've also included the simplest clause - an empty string. When our string is empty, we'll just want to return what we have saved so far.
Now lets move on to our actual recursive function: If we hit our split character, we'll add the string we have saved so far to the list and restart with an empty current-state string If we don't hit the split character, we'll add the character to the current-state string
spliton' (currchar:rest) splitby sofar res | currchar==splitby = spliton' rest splitby "" (res++[sofar]) | otherwise = spliton' rest splitby (sofar++[currchar]) res
So, to summarize our code:
spliton :: String -> Char -> [String]
spliton source splitchar = spliton' source splitchar [] []
spliton' :: String -> Char -> String -> [String] -> [String]
spliton' [] _ sofar res = res ++ [sofar]
spliton' (currchar:rest) splitby sofar res
| currchar==splitby = spliton' rest splitby "" (res++[sofar])
| otherwise = spliton' rest splitby (sofar++[currchar]) res
Note: This will not however get rid of the empty string - meaning if you have many superfluous spaces - you'll get them added to the list. I'll leave you to think how to handle that case - hope this can help you get started.

- 2,237
- 15
- 23
-
there was a problem with formatting in the question. check it out, I think your code will need to be updated. – Will Ness Oct 14 '14 at 11:16
-
Thanks, though I have to program in Clean, I might be able to do it now. – Gergo Szucs Oct 14 '14 at 11:39
Let's split this up in several sub-problems:
- Make a list of characters from the string so that we can easily apply pattern matching.
- Scrape the initial part of the list (as long as possible with only spaces or only not-spaces), and only keep it when it is not whitespace.
- Repeat the second step while the list is non-empty.
The first thing can be done using fromString
. For the second and third step, we define a helper function:
scrape :: [Char] -> [String]
scrape [] = []
scrape cs=:[c:_]
| isSpace c = scrape (dropWhile isSpace cs)
| otherwise = [toString word:scrape rest]
where
(word,rest) = span (not o isSpace) cs
The first alternative is the base case to match the empty list. The second alternative matches the whole list cs
with a first element c
. If the first character is a space, we recursively (step 3) call the same function on the same list without the initial part of spaces. If the first character is not a space, we use span :: (a -> Bool) [a] -> ([a], [a])
to split the list in the initial part that is a word, and the rest. We store the word using toString
as a string, and recursively call scrape
for the rest of the list.
Now, we only need a wrapper to make this a function with the type String -> [String]
:
split :: String -> [String]
split s = scrape (fromString s)
where
scrape :: [Char] -> [String]
scrape [] = []
scrape cs=:[c:_]
| isSpace c = scrape (dropWhile isSpace cs)
| otherwise = [toString word:scrape rest]
where
(word,rest) = span (not o isSpace) cs
Note that you can easily abstract from the delimiter, by passing a character d
and replacing isSpace c
with c == d
and (not o isSpace)
by ((<>) d)
. Alternatively, you can choose to not pass a character d
but a function isDelim :: Char -> Bool
. You then get isDelim c
and (not o isDelim)
, respectively.