2

I have a list of strings:

[" ix = index"," ctr = counter"," tbl = table"]

and I want to create a tuple from it like:

[("ix","index"),("ctr","counter"),("tbl","table")]

I even tried:

genTuple [] = []
genTuples (a:as)= do
                   i<-splitOn '=' a
                   genTuples as
                   return i

Any help would be appriciated Thank you.

anujuhi01
  • 71
  • 7
  • Where are you stuck? What have you tried? – jub0bs Feb 13 '15 at 11:48
  • This is what I tried,I am unable to get a start `gentuples (a:as)= do if (head a /= "=")then i<-i:head a else genTuples a` – anujuhi01 Feb 13 '15 at 11:52
  • 1
    Write a function `String -> (String,String)` that takes only one definition string and splits it. Then `map` that over the whole list. – chi Feb 13 '15 at 12:11
  • Even better, write a function `String -> Maybe (String, String)`. Or define the behaviour on ill-formed inputs. – Zeta Feb 13 '15 at 12:41
  • I tried this: `splitTwo s = do let a = splitOn "=" s return (head a,last a) ` But it gives type error – anujuhi01 Feb 13 '15 at 12:51

2 Answers2

6

Haskell's type system is really expressive, so I suggest to think about the problem in terms of types. The advantage of this is that you can solve the problem 'top-down' and the whole program can be typechecked as you go, so you can catch all kinds of errors early on. The general approach is to incrementally divide the problem into smaller functions, each of which remaining undefined initially but with some plausible type.

What you want is a function (let's call it convert) which take a list of strings and generates a list of tuples, i.e.

convert :: [String] -> [(String, String)]
convert = undefined

It's clear that each string in the input list will need to be parsed into a 2-tuple of strings. However, it's possible that the parsing can fail - the sheer type String makes no guarantees that your input string is well formed. So your parse function maybe returns a tuple. We get:

parse :: String -> Maybe (String, String)
parse = undefined

We can immediately plug this into our convert function using mapMaybe:

convert :: [String] -> [(String, String)]
convert list = mapMaybe parse list

So far, so good - but parse is literally still undefined. Let's say that it should first verify that the input string is 'valid', and if it is - it splits it. So we'll need

valid :: String -> Bool
valid = undefined

split :: String -> (String, String)
split = undefined

Now we can define parse:

parse :: String -> Maybe (String, String)
parse s | valid s   = Just (split s)
        | otherwise = Nothing

What makes a string valid? Let's say it has to contain a = sign:

valid :: String -> Bool
valid s = '=' `elem` s

For splitting, we'll take all the characters up to the first = for the first tuple element, and the rest for the second. However, you probably want to trim leading/trailing whitespace as well, so we'll need another function. For now, let's make it a no-op

trim :: String -> String
trim = id

Using this, we can finally define

split :: String -> (String, String)
split s = (trim a, trim (tail b))
  where
    (a, b) = span (/= '=') s

Note that we can safely call tail here because we know that b is never empty because there's always a separator (that's what valid verified). Type-wise, it would've been nice to express this guarantee using a "non-empty string" but that may be a bit overengineered. :-)

Now, there are a lot of solutions to the problem, this is just one example (and there are ways to shorten the code using eta reduction or existing libraries). The main point I'm trying to get across is that Haskell's type system allows you to approach the problem in a way which is directed by types, which means the compiler helps you fleshing out a solution from the very beginning.

Community
  • 1
  • 1
Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207
  • For non-emptyness (based on lists), have a look at `NonEmpty` in `semigroups`, or use `data NonEmpty a = NE a [a]`. – Zeta Feb 13 '15 at 13:19
  • @anujuhi01, if you want the whole thing to fail if one of the elements of the list is malformed, just use `mapM` instead of `mapMaybe`. – dfeuer Feb 13 '15 at 14:27
  • Also, if you want to see what string failed in case of failure, you can switch from `Maybe` to `Either`, using `Right result` or `Left badness`, and then again use `mapM`. – dfeuer Feb 13 '15 at 15:52
0

You can do it like this:

import Control.Monda
import Data.List
import Data.List.Split

map ((\[a,b] -> (a,b)) . splitOn "=" . filter (/=' ')) [" ix = index"," ctr = counter"," tbl = table"]
vikingsteve
  • 38,481
  • 23
  • 112
  • 156