Haskell beginner: Data decl. errors - haskell

Hello I am trying to write a very simple function in Haskell. However I can't get "ghci" to accept my code.
data Field = A1 Int deriving (Show)
data FieldList = FL [Field] | Name String deriving (Show)
t :: Field
t = A1 1
u :: Int -> FieldList
u 0 = FL []
u n = FL [t]:(u (n-1))
And the error I get is this:
test.hs:9:7:
Couldn't match expected type `FieldList' with actual type `[a0]'
In the expression: (FL [t]) : (u (n - 1))
In an equation for `u': u n = (FL [t]) : (u (n - 1))
Can someone point me in the right direction?
Thanks!

Looking at the last line:
u n = FL [t]:(u (n-1))
u has the type Int -> FieldList. n is an Int, so (n - 1) is also an Int. u (n-1) would therefor be a FieldList.
Function application has a higher precedence than operators, so the above line is equivalent to:
u n = (FL [t]) : (u (n - 1) )
FL [t] is a FieldList.
However, (:) has the type a -> [a] -> [a]. You can see the types don't match, so that is what is causing the problem.
What you probably want to do is build up the list of Fields (having type [Field]), and then turning that into a FieldList. Here is some stub code:
u :: Int -> FieldList
u n = FL (uHelper n)
uHelper :: Int -> [Field]
uHelper = ... -- write this function

The error says (FL [t]) : (u (n - 1)) which says that you are trying the List cons function on
FL [t] which is not a list hence you cannot cons with it.
I am not sure why you have created a FieldList as a new data type which allows a FieldList to be either a List of Field OR a string (which is created using Name constructor) which sort of doesn't make logical sense.
What you can do is make FieldList as:
type FieldList = [Field]
And then your function would become:
u :: Int -> FieldList
u 0 = []
u n = t : (u (n-1))

There are two problems with your code:
The first argument of list cons (:) is an element, not a list, thus: t : ... not [t] : ...
You must unwrap the FieldList first to get [Field]. Then you can prepend t to it.
You want your last line to be
u n = case u (n-1) of FL xx -> FL (t:xx)
That would of course fail to pattern match if the field list is a Name so I would agree with Ankur that there might be a problem with the design...

Related

Additional pattern matching inside case

Hopefully, the code is commented well enough.
-- I have 2 data types:
data Person = Person { firstName :: String, lastName :: String, age :: Int }
deriving (Show)
data Error = IncompleteDataError | IncorrectDataError String
deriving (Show)
-- This function should take a list a pairs like:
-- fillPerson [("firstName","John"), ("lastName","Smith"), ("dbdf", "dff"), ("age","30"), ("age", "40")]
-- And fill the record with values of the fields with corresponding names.
-- It ignores the redundant fields.
-- If there are less then 3 meaningful fields, it should throw an error IncompleteDataError
-- If the field age doesn't have a number, if should return IncorrectDataError str, where str — is the value of age.
fillPerson :: [(String, String)] -> Either Error Person
fillPerson [] = Left IncompleteDataError
fillPerson (x:xs) = let
-- Int stores number of fields
helper :: [(String, String)] -> Person -> Int -> Either Error Person
helper _ p 3 = Right p
helper [] _ _ = Left IncompleteDataError
helper ((key, value):xs) p n = case key of
"firstName" -> helper xs p{firstName=value} (n + 1)
"lastName" -> helper xs p{lastName=value} (n + 1)
-- how to return IncorrectDataError str here?
-- I need to store reads value :: [(Int, String)]
-- if the String is not empty, return Left IncorrectDataError value
-- but how to write this?
"age" -> helper xs p{age=read value::Int} (n + 1)
_ -> helper xs p n
in
helper (x:xs) Person{} 0
You have an association list; use lookup to get each name, or produce an IncompleteDataError if the lookup fails. maybe converts each Nothing to a Left value and each Just value to Right value.
-- lookup :: Eq a => a -> [(a,b)] -> Maybe b
-- maybe :: b -> (a -> b) -> Maybe a -> b
verifyInt :: String -> Either Error Int
verifyInt x = ... -- E.g. verify "3" == Right 3
-- verify "foo" == Left IncorrectDataError
fillPerson kv = Person
<$> (get "firstName" kv)
<*> (get "lastName" kv)
<*> (get "age" kv >>= verifyInt)
where get key kv = maybe (Left IncompleteDataError) Right $ lookup key kv
Since get :: String -> [(String, String)] -> Either Error String, the Applicative instance for functions ensures that fillPerson :: [(String, String)] -> Either Error Person. If any call to get returns Left IncompleteDataError, the result of Person <$> ... will do so as well; otherwise, you'll get a Right (Person ...) value.
The problem that you have is trying to do all the things at once in a single recursive function, interleaving several different concerns. It’s possible to write that way, but better to follow the format of #chepner’s answer and break things down into pieces. This is a supplement to their answer re. the verification of age. With the addition of an import:
-- readMaybe :: Read a => String -> Maybe a
import Text.Read (readMaybe)
And a helper function to turn Maybe “failures” (Nothing) into the corresponding Either (Left):
maybeToEither :: a -> Maybe b -> Either a b
maybeToEither x = maybe (Left x) Right
Here is a solution that does all the verification you describe:
fillPerson store = do -- Either monad
-- May fail with ‘IncompleteDataError’
f <- string "firstName"
l <- string "lastName"
-- May fail with ‘IncompleteDataError’ *or* ‘IncorrectDataError’
a <- int "age"
pure Person
{ firstName = f
, lastName = l
, age = a
}
where
string :: String -> Either Error String
string key = maybeToEither IncompleteDataError (lookup key store)
int :: String -> Either Error Int
int key = do
value <- string key -- Reuse error handling from ‘string’
maybeToEither (IncorrectDataError value) (readMaybe value)
You can make this more compact using RecordWildCards, although this is less advisable because it’s not explicit, so it’s sensitive to renaming of fields in Person.
fillPerson store = do
firstName <- string "firstName"
lastName <- string "lastName"
age <- int "age"
pure Person{..} -- Implicitly, ‘firstName = firstName’ &c.
where
…
Applicative operators are more common for this type of thing, and preferable in most cases as they avoid unnecessary intermediate names. However, one caveat of using positional arguments rather than named fields is that it’s possible to mix up the order of fields that have the same type (here, firstName and lastName).
fillPerson store = Person
<$> string "firstName"
<*> string "lastName"
<*> int "age"
where
…
It’s also possible to make this definition point-free, omitting store from the parameters of fillPerson and making it instead a parameter of string and int, using liftA3 Person <$> string "firstName" <*> … (the (r ->) applicative); in this particular case I wouldn’t choose that style, but it may be a worthy exercise to try to rewrite it yourself.
As to your question:
-- I need to store reads value :: [(Int, String)]
-- if the String is not empty, return Left IncorrectDataError value
-- but how to write this?
You can write:
"age" -> case reads value of
[(value', "")] -> helper xs p{age=value'} (n + 1)
_ -> Left (IncorrectValueError value)
However there are a number of problems with your code:
It starts with a Person whose fields are undefined, and will raise exceptions if accessed, which would be fine if you guaranteed that they were all filled in, but…
It tracks the number of fields set but not which fields, so you can set firstName three times and end up returning an invalid Person.
So if you want to do this in a single definition, here’s how I would restructure it—keep the recursive helper, but make each equation handle one condition, using an accumulator with Maybes for each of the fields, updating them from Nothing to Just as you find each field.
fillPerson' :: [(String, String)] -> Either Error Person
fillPerson' = fillFields (Nothing, Nothing, Nothing)
where
fillFields
-- Accumulator of firstName, lastName, and age.
:: (Maybe String, Maybe String, Maybe Int)
-- Remaining input keys to check.
-> [(String, String)]
-- Final result.
-> Either Error Person
-- Set firstName if not set.
fillFields (Nothing, ml, ma) (("firstName", f) : kvs)
= fillFields (Just f, ml, ma) kvs
-- Set lastName if not set.
fillFields (mf, Nothing, ma) (("lastName", l) : kvs)
= fillFields (mf, Just l, ma) kvs
-- Set age if not set, failing immediately if not a valid number.
fillFields (mf, ml, Nothing) (("age", a) : kvs)
| all (`elem` ['0'..'9']) a
= fillFields (mf, ml, Just (read a)) kvs
| otherwise
= Left (IncorrectDataError a)
-- Ignore redundant firstName.
fillFields acc#(Just{}, ml, ma) (("firstName", _) : kvs)
= fillFields acc kvs
-- Ignore redundant lastName.
fillFields acc#(mf, Just{}, ma) (("lastName", _) : kvs)
= fillFields acc kvs
-- Ignore redundant age.
fillFields acc#(mf, ml, Just{}) (("age", _) : kvs)
= fillFields acc kvs
-- Ignore extra fields.
fillFields acc (_ : kvs)
= fillFields acc kvs
-- If all fields are present anywhere in the input,
-- we can finish early and successfully.
fillFields (Just f, Just l, Just a) _
= Right Person
{ firstName = f
, lastName = l
, age = a
}
-- If any field is missing at the end, fail.
fillFields __ []
= Left IncompleteDataError
Note how the structure of the code is very brittle: if we change Person at all, many lines of this definition will have to change. That’s why it’s better to break the problem down into smaller composable parts and put them together.
This does, however, serve as an example of how to translate an “imperative” loop into Haskell: write a recursive function with an accumulator for your “mutable” state, make a recursive call (possibly updating the accumulator) to loop, and stop the recursion to exit the loop. (In fact, if you squint, this is essentially a translation of an imperative program into an explicit control graph.)

Haskell: Exception <<loop>> on recursive data entry

So I'm trying to make a little program that can take in data captured during an experiment, and for the most part I think I've figured out how to recursively take in data until the user signals there is no more, however upon termination of data taking haskell throws Exception: <<loop>> and I can't really figure out why. Here's the code:
readData :: (Num a, Read a) => [Point a] -> IO [Point a]
readData l = do putStr "Enter Point (x,y,<e>) or (d)one: "
entered <- getLine
if (entered == "d" || entered == "done")
then return l
else do let l = addPoint l entered
nl <- readData l
return nl
addPoint :: (Num a, Read a) => [Point a] -> String -> [Point a]
addPoint l s = l ++ [Point (dataList !! 0) (dataList !! 1) (dataList !! 2)]
where dataList = (map read $ checkInputData . splitOn "," $ s) :: (Read a) => [a]
checkInputData :: [String] -> [String]
checkInputData xs
| length xs < 2 = ["0","0","0"]
| length xs < 3 = (xs ++ ["0"])
| length xs == 3 = xs
| length xs > 3 = ["0","0","0"]
As far as I can tell, the exception is indication that there is an infinite loop somewhere, but I can't figure out why this is occurring. As far as I can tell when "done" is entered the current level should simply return l, the list it's given, which should then cascade up the previous iterations of the function.
Thanks for any help. (And yes, checkInputData will have proper error handling once I figure out how to do that.)
<<loop>> basically means GHC has detected an infinite loop caused by a value which depends immediately on itself (cf. this question, or this one for further technical details if you are curious). In this case, that is triggered by:
else do let l = addPoint l entered
This definition, which shadows the l you passed as an argument, defines l in terms of itself. You meant to write something like...
else do let l' = addPoint l entered
... which defines a new value, l', in terms of the original l.
As Carl points out, turning on -Wall (e.g. by passing it to GHC at the command line, or with :set -Wall in GHCi) would make GHC warn you about the shadowing:
<interactive>:171:33: warning: [-Wname-shadowing]
This binding for ‘l’ shadows the existing binding
bound at <interactive>:167:10
Also, as hightlighted by dfeuer, the whole do-block in the else branch can be replaced by:
readData (addPoint l entered)
As an unrelated suggestion, in this case it is a good idea to replace your uses of length and (!!) with pattern matching. For instance, checkInputData can be written as:
checkInputData :: [String] -> [String]
checkInputData xs = case xs of
[_,_] -> xs ++ ["0"]
[_,_,_] -> xs
_ -> ["0","0","0"]
addPoint, in its turn, might become:
addPoint :: (Num a, Read a) => [Point a] -> String -> [Point a]
addPoint l s = l ++ [Point x y z]
where [x,y,z] = (map read $ checkInputData . splitOn "," $ s) :: (Read a) => [a]
That becomes even neater if you change checkInputData so that it returns a (String, String, String) triple, which would better express the invariant that you are reading exactly three values.

Haskell Continuation passing style index of element in list

There's a series of examples I'm trying to do to practice Haskell. I'm currently learning about continuation passing, but I'm a bit confused as to how to implement a function like find index of element in list that works like this:
index 3 [1,2,3] id = 2
Examples like factorial made sense since there wasn't really any processing of the data other than multiplication, but in the case of the index function, I need to compare the element I'm looking at with the element I'm looking for, and I just can't seem to figure out how to do that with the function parameter.
Any help would be great.
first let me show you a possible implementation:
index :: Eq a => a -> [a] -> (Int -> Int) -> Int
index _ [] _ = error "not found"
index x (x':xs) cont
| x == x' = cont 0
| otherwise = index x xs (\ind -> cont $ ind + 1)
if you prefer point-free style:
index :: Eq a => a -> [a] -> (Int -> Int) -> Int
index _ [] _ = error "not found"
index x (x':xs) cont
| x == x' = cont 0
| otherwise = index x xs (cont . (+1))
how it works
The trick is to use the continuations to count up the indices - those continuations will get the index to the right and just increment it.
As you see this will cause an error if it cannot find the element.
examples:
λ> index 1 [1,2,3] id
0
λ> index 2 [1,2,3] id
1
λ> index 3 [1,2,3] id
2
λ> index 4 [1,2,3] id
*** Exception: not found
how I figured it out
A good way to figure out stuff like this is by first writing down the recursive call with the continuation:
useCont a (x:xs) cont = useCont a xs (\valFromXs -> cont $ ??)
And now you have to think about what you want valFromXs to be (as a type and as a value) - but remember your typical start (as here) will be to make the first continuation id, so the type can only be Int -> Int. So it should be clear that we are talking about of index-transformation here. As useCont will only know about the tail xs in the next call it seems natural to see this index as relative to xs and from here the rest should follow rather quickly.
IMO this is just another instance of
Let the types guide you Luke
;)
remarks
I don't think that this is a typical use of continuations in Haskell.
For once you can use an accumulator argument for this as well (which is conceptional simpler):
index :: Eq a => a -> [a] -> Int -> Int
index _ [] _ = error "not found"
index x (x':xs) ind
| x == x' = ind
| otherwise = index x xs (ind+1)
or (see List.elemIndex) you can use Haskells laziness/list-comprehensions to make it look even nicer:
index :: Eq a => a -> [a] -> Int
index x xs = head [ i | (x',i) <- zip xs [0..], x'== x ]
If you have a value a then to convert it to CPS style you replace it with something like (a -> r) -> r for some unspecified r. In your case, the base function is index :: Eq a => a -> [a] -> Maybe Int and so the CPS form is
index :: Eq a => a -> [a] -> (Maybe Int -> r) -> r
or even
index :: Eq a => a -> [a] -> (Int -> r) -> r -> r
Let's implement the latter.
index x as success failure =
Notably, there are two continuations, one for the successful result and one for a failing one. We'll apply them as necessary and induct on the structure of the list just like usual. First, clearly, if the as list is empty then this is a failure
case as of
[] -> failure
(a:as') -> ...
In the success case, we're, as normal, interested in whether x == a. When it is true we pass the success continuation the index 0, since, after all, we found a match at the 0th index of our input list.
case as of
...
(a:as') | x == a -> success 0
| otherwise -> ...
So what happens when we don't yet have a match? If we were to pass the success continuation in unchanged then it would, assuming a match is found, eventually be called with 0 as an argument. This loses information about the fact that we've attempted to call it once already, though. We can rectify that by modifying the continuation
case as of
...
(a:as') ...
| otherwise -> index x as' (fun idx -> success (idx + 1)) failure
Another way to think about it is that we have the collect "post" actions in the continuation since ultimately the result of the computation will pass through that code
-- looking for the value 5, we begin by recursing
1 :
2 :
3 :
4 :
5 : _ -- match at index 0; push it through the continuation
0 -- lines from here down live in the continuation
+1
+1
+1
+1
This might be even more clear if we write the recursive branch in pointfree style
| otherwise -> index x as' (success . (+1)) failure
which shows how we're modifying the continuation to include one more increment for each recursive call. All together the code is
index :: Eq a => a -> [a] -> (Int -> r) -> r -> r
index x as success failure
case as of
[] -> failure
(a:as') | x == a -> success 0
| otherwise -> index x as' (success . (+1)) failure

Neighborhood of a mathematical expression using Haskell

I'm trying to implement with Haskell an algorithm to manipulate mathematical expressions.
I have this data type :
data Exp = Var String | IVal Int | Add Exp Exp
This will be enough for my question.
Given a set of expression transformations, for example :
(Add a b) => (Add b a)
(Add (Add a b) c) => (Add a (Add b c))
And an expression, for example : x = (Add (Add x y) (Add z t)), I want to find all expressions in the neighborhood of x. Given that neighborhood of x is defined as: y in Neighborhood(x) if y can be reached from x within a single transformation.
I am new to Haskell. I am not even sure Haskell is the right tool for this job.
The final goal is to get a function : equivalent x which returns a set of all expressions that are equivalent to x. In other words, the set of all expressions that are in the closure of the neighborhood of x (given a set of transformations).
Right now, I have the following :
import Data.List(nub)
import Data.Set
data Exp = IVal Int
| Scalar String
| Add Exp Exp
deriving (Show, Eq, Ord)
commu (Add a b) = (Add b a)
commu x = x
assoc (Add (Add a b) c) = (Add a (Add b c))
assoc (Add a (Add b c)) = (Add (Add a b) c)
assoc x = x
neighbors x = [commu x, assoc x]
equiv :: [Exp] -> [Exp]
equiv closure
| closure == closureUntilNow = closure
| otherwise = equiv closureUntilNow
where closureUntilNow = nub $ closure ++ concat [neighbors x|x<-closure]
But It's probably slower than needed (nub is O(n^2)) and some terms are missing.
For example, if you have f = (x+y)+z, then, you will not get (x+z)+y, and some others.
Imports, etc. below. I'll be using the multiset package.
import Control.Monad
import Data.MultiSet as M
data Exp = Var String | IVal Int | Add Exp Exp deriving (Eq, Ord, Show, Read)
A bit of paper-and-pencil work shows the following fact: expressions e1 and e2 are in the congruence closure of your relation iff the multiset of leaves are equal. By leaves, I mean the Var and IVal values, e.g. the output of the following function:
leaves :: Exp -> MultiSet Exp
leaves (Add a b) = leaves a `union` leaves b
leaves e = singleton e
So this suggests a nice clean way to generate all the elements in a particular value's neighborhood (without attempting to generate any duplicates in the first place). First, generate the multiset of leaves; then nondeterministically choose a partition of the multiset and recurse. The code to generate partitions might look like this:
partitions :: Ord k => MultiSet k -> [(MultiSet k, MultiSet k)]
partitions = go . toOccurList where
go [] = [(empty, empty)]
go ((k, n):bag) = do
n' <- [0..n]
(left, right) <- go bag
return (insertMany k n' left, insertMany k (n-n') right)
Actually, we only want partitions where both the left and right part are non-empty. But we'll check that after we've generated them all; it's cheap, as there's only two that aren't like that per invocation of partitions. So now we can generate the whole neighborhood in one fell swoop:
neighborhood :: Exp -> [Exp]
neighborhood = go . leaves where
full = guard . not . M.null
go m
| size m == 1 = toList m
| otherwise = do
(leftBag, rightBag) <- partitions m
full leftBag
full rightBag
left <- go leftBag
right <- go rightBag
return (Add left right)
By the way, the reason you're not getting all the terms is because you're generating the reflexive, transitive closure but not the congruence closure: you need to apply your rewrite rules deep in the term, not just at the top level.

What can be improved on my first haskell program?

Here is my first Haskell program. What parts would you write in a better way?
-- Multiplication table
-- Returns n*n multiplication table in base b
import Text.Printf
import Data.List
import Data.Char
-- Returns n*n multiplication table in base b
mulTable :: Int -> Int -> String
mulTable n b = foldl (++) (verticalHeader n b w) (map (line n b w) [0..n])
where
lo = 2* (logBase (fromIntegral b) (fromIntegral n))
w = 1+fromInteger (floor lo)
verticalHeader :: Int -> Int -> Int -> String
verticalHeader n b w = (foldl (++) tableHeader columnHeaders)
++ "\n"
++ minusSignLine
++ "\n"
where
tableHeader = replicate (w+2) ' '
columnHeaders = map (horizontalHeader b w) [0..n]
minusSignLine = concat ( replicate ((w+1)* (n+2)) "-" )
horizontalHeader :: Int -> Int -> Int -> String
horizontalHeader b w i = format i b w
line :: Int -> Int -> Int -> Int -> String
line n b w y = (foldl (++) ((format y b w) ++ "|" )
(map (element b w y) [0..n])) ++ "\n"
element :: Int -> Int -> Int -> Int -> String
element b w y x = format (y * x) b w
toBase :: Int -> Int -> [Int]
toBase b v = toBase' [] v where
toBase' a 0 = a
toBase' a v = toBase' (r:a) q where (q,r) = v `divMod` b
toAlphaDigits :: [Int] -> String
toAlphaDigits = map convert where
convert n | n < 10 = chr (n + ord '0')
| otherwise = chr (n + ord 'a' - 10)
format :: Int -> Int -> Int -> String
format v b w = concat spaces ++ digits ++ " "
where
digits = if v == 0
then "0"
else toAlphaDigits ( toBase b v )
l = length digits
spaceCount = if (l > w) then 0 else (w-l)
spaces = replicate spaceCount " "
Here are some suggestions:
To make the tabularity of the computation more obvious, I would pass the list [0..n] to the line function rather than passing n.
I would further split out the computation of the horizontal and vertical axes so that they are passed as arguments to mulTable rather than computed there.
Haskell is higher-order, and almost none of the computation has to do with multiplication. So I would change the name of mulTable to binopTable and pass the actual multiplication in as a parameter.
Finally, the formatting of individual numbers is repetitious. Why not pass \x -> format x b w as a parameter, eliminating the need for b and w?
The net effect of the changes I am suggesting is that you build a general higher-order function for creating tables for binary operators. Its type becomes something like
binopTable :: (i -> String) -> (i -> i -> i) -> [i] -> [i] -> String
and you wind up with a much more reusable function—for example, Boolean truth tables should be a piece of cake.
Higher-order and reusable is the Haskell Way.
You don't use anything from import Text.Printf.
Stylistically, you use more parentheses than necessary. Haskellers tend to find code more readable when it's cleaned of extraneous stuff like that. Instead of something like h x = f (g x), write h = f . g.
Nothing here really requires Int; (Integral a) => a ought to do.
foldl (++) x xs == concat $ x : xs: I trust the built-in concat to work better than your implementation.
Also, you should prefer foldr when the function is lazy in its second argument, as (++) is – because Haskell is lazy, this reduces stack space (and also works on infinite lists).
Also, unwords and unlines are shortcuts for intercalate " " and concat . map (++ "\n") respectively, i.e. "join with spaces" and "join with newlines (plus trailing newline)"; you can replace a couple things by those.
Unless you use big numbers, w = length $ takeWhile (<= n) $ iterate (* b) 1 is probably faster. Or, in the case of a lazy programmer, let w = length $ toBase b n.
concat ( (replicate ((w+1)* (n+2)) "-" ) == replicate ((w+1) * (n+2)) '-' – not sure how you missed this one, you got it right just a couple lines up.
You do the same thing with concat spaces, too. However, wouldn't it be easier to actually use the Text.Printf import and write printf "%*s " w digits?
Norman Ramsey gave excellent high-level (design) suggestions; Below are some low-level ones:
First, consult with HLint. HLint is a friendly program that gives you rudimentary advice on how to improve your Haskell code!
In your case HLint gives 7 suggestions. (mostly about redundant brackets)
Modify your code according to HLint's suggestions until it likes what you feed it.
More HLint-like stuff:
concat (replicate i "-"). Why not replicate i '-'?
Consult with Hoogle whenever there is reason to believe that a function you need is already available in Haskell's libraries. Haskell comes with tons of useful functions so Hoogle should come in handy quite often.
Need to concatenate strings? Search for [String] -> String, and voila you found concat. Now go replace all those folds.
The previous search also suggested unlines. Actually, this even better suits your needs. It's magic!
Optional: pause and thank in your heart to Neil M for making Hoogle and HLint, and thank others for making other good stuff like Haskell, bridges, tennis balls, and sanitation.
Now, for every function that takes several arguments of the same type, make it clear which means what, by giving them descriptive names. This is better than comments, but you can still use both.
So
-- Returns n*n multiplication table in base b
mulTable :: Int -> Int -> String
mulTable n b =
becomes
mulTable :: Int -> Int -> String
mulTable size base =
To soften the extra characters blow of the previous suggestion: When a function is only used once, and is not very useful by itself, put it inside its caller's scope in its where clause, where it could use the callers' variables, saving you the need to pass everything to it.
So
line :: Int -> Int -> Int -> Int -> String
line n b w y =
concat
$ format y b w
: "|"
: map (element b w y) [0 .. n]
element :: Int -> Int -> Int -> Int -> String
element b w y x = format (y * x) b w
becomes
line :: Int -> Int -> Int -> Int -> String
line n b w y =
concat
$ format y b w
: "|"
: map element [0 .. n]
where
element x = format (y * x) b w
You can even move line into mulTable's where clause; imho, you should.
If you find a where clause nested inside another where clause troubling, then I suggest to change your indentation habits. My recommendation is to use consistent indentation of always 2 or always 4 spaces. Then you can easily see, everywhere, where the where in the other where is at. ok
Below's what it looks like (with a few other changes in style):
import Data.List
import Data.Char
mulTable :: Int -> Int -> String
mulTable size base =
unlines $
[ vertHeaders
, minusSignsLine
] ++ map line [0 .. size]
where
vertHeaders =
concat
$ replicate (cellWidth + 2) ' '
: map horizontalHeader [0 .. size]
horizontalHeader i = format i base cellWidth
minusSignsLine = replicate ((cellWidth + 1) * (size + 2)) '-'
cellWidth = length $ toBase base (size * size)
line y =
concat
$ format y base cellWidth
: "|"
: map element [0 .. size]
where
element x = format (y * x) base cellWidth
toBase :: Integral i => i -> i -> [i]
toBase base
= reverse
. map (`mod` base)
. takeWhile (> 0)
. iterate (`div` base)
toAlphaDigit :: Int -> Char
toAlphaDigit n
| n < 10 = chr (n + ord '0')
| otherwise = chr (n + ord 'a' - 10)
format :: Int -> Int -> Int -> String
format v b w =
spaces ++ digits ++ " "
where
digits
| v == 0 = "0"
| otherwise = map toAlphaDigit (toBase b v)
spaces = replicate (w - length digits) ' '
0) add a main function :-) at least rudimentary
import System.Environment (getArgs)
import Control.Monad (liftM)
main :: IO ()
main = do
args <- liftM (map read) $ getArgs
case args of
(n:b:_) -> putStrLn $ mulTable n b
_ -> putStrLn "usage: nntable n base"
1) run ghc or runhaskell with -Wall; run through hlint.
While hlint doesn't suggest anything special here (only some redundant brackets), ghc will tell you that you don't actually need Text.Printf here...
2) try running it with base = 1 or base = 0 or base = -1
If you want multiline comments use:
{- A multiline
comment -}
Also, never use foldl, use foldl' instead, in cases where you are dealing with large lists which must be folded. It is more memory efficient.
A brief comments saying what each function does, its arguments and return value, is always good. I had to read the code pretty carefully to fully make sense of it.
Some would say if you do that, explicit type signatures may not be required. That's an aesthetic question, I don't have a strong opinion on it.
One minor caveat: if you do remove the type signatures, you'll automatically get the polymorphic Integral support ephemient mentioned, but you will still need one around toAlphaDigits because of the infamous "monomorphism restriction."

Resources