Shorter Run Functions
Last time around, I discussed 'run' functions and how these are the "entrypoint" for using most monads. However, it's also useful to have a couple potential shortcuts up our sleeve. Today we'll go over a couple "shortcut" functions when you don't need everything the monad supplies.
Recall that with the Writer
and State
monads, the run
function produces two outputs. The first is the "result" of our computation (some type a
). The second is the stateful value tracked by the monad:
runWriter :: Writer w a -> (a, w)
runState :: State s a -> s -> (a, s)
There are times however, especially with State
, where the stateful value is the result. There are no shortage of functions out there that look like State s ()
. They have essentially no return value, but they update the tracked value:
doubleAndAddInput :: Int -> State Int ()
doubleAndAddInput x = do
modify (* 2)
modify (+ x)
Now let's think about running this computation. If we use runState
, we'll end up with a tuple where one of the elements is just the unit ()
.
>> runState (doubleAndAddInput 5) 6
((), 17)
Of course we can always just use snd
to ignore the first element of the tuple. But for the sake of code cleanliness it's nice to know that there are helper functions to skip this for you! Here are the exec
functions for these two monads. They will return only the tracked state value!
execWriter :: Writer w a -> w
execState :: State s a -> s -> s
So applying this with our example above, we would get the following:
>> execState (doubleAndAddInput 5) 6
17
On the flip side, there are also times where you don't care about the accumulated state. All you care about is the final result! In this case, the function you want for the State
monad is evalState
:
evalState :: State s a -> s -> a
So now we could supply a return value in our function:
doubleAndAddInput :: Int -> State Int Int
doubleAndAddInput x = do
prev <- get
put (2 * prev + x)
return (x + prev)
Then we can drop the accumulated state like so:
>> evalState (doubleAndAddInput 5) 6
11
For whatever reason, there doesn't seem to be evalWriter
in the library. Perhaps the logic here is that the accumulated Writer value doesn't affect the computation, so if you're going to ignore its output, it has no effect. However, I could imagine cases where you originally wrote the function as a Writer
, but in a particular usage of it, you don't need the value. So it's an interesting design decision.
Anyways, these functions also exist with monad transformers of course:
execStateT :: StateT s m a -> s -> m s
evalStateT :: StateT s m a -> s -> m a
execWriterT :: WriterT w m a -> m w
Next time we'll dig into monad transformers a bit more! In the meantime, learn more about monads by taking a look at our series on Monads and Functional Structures!
Running with Monads
I had a big stumbling block in learning monads. Perhaps unsurprisingly, this occurred because I was trying to take a particular monadic analogy too far in the college class where I first learned about them. I got the idea in my head that, "Monads are like a treasure chest", and my mental model went something like this:
- Monads are like a treasure chest.
- You can't directly access what's inside (the
a
inIO a
). - That is, unless you are already in that monad.
- In that case, the "bind" (
>>=
) operator let's us pass in a function that accesses the "inner value". - But the result we produce is re-wrapped in the monad!
And so my head was spinning in a loop trying to figure out how I could actually get into a monad in the first place.
However, the code I was looking at was not normal monadic code. It was IO
code. And I was conflating the IO
monad with all monads. Don't do this! The IO monad is, in fact, special! It does follow some of the same patterns as other monads. But this special property of "you can't access the inner value unless you're in IO" does not apply to other monads!
The majority of monads you will encounter can be accessed from pure code. The most common way of doing this is through a run
function. Here are three of the most common examples with the Reader
, Writer
and State
monads:
runReader :: Reader r a -> r -> a
runWriter :: Writer w a -> (a, w)
runState :: State s a -> s -> (a, s)
Most often, you'll need to supply an "initial" value, as we see with Reader
and State
. And many times you'll get the final stateful value as a second product of the function. This occurs in Writer
and State
.
Here's a simple example. We have a State
computation that adds 1 to the stored value, and then adds the previous stored value to the input, returning that. We can call into this stateful function from a totally pure function using runState
:
stateFunction :: Int -> State Int Int
stateFunction input = do
prev <- get
modify (+1)
return $ prev + input
callState :: Int -> (Int, Int)
callState x = runState (stateFunction (x + 5)) 11
...
>> callState 3
(19, 12)
>> callState 7
(23, 12)
With monad transformers, the concept of the "run" function is very similar. The functions now end with the suffix T
. The only difference is that it produces a value in the underlying monad m
:
runReaderT :: ReaderT r m a -> r -> m a
runWriterT :: WriterT w m a -> m (a, w)
runStateT :: StateT s m a -> s -> m (a, s)
Of course, there are exceptions to this pattern of "run" functions. As we learned about last time, Either
can be a monad, but we can also treat Either
values as totally normal objects. To access the inner values, we just need a case
statement or a pattern match. You don't need to take the "treasure box" approach. Or at least, with certain monads, the treasure box is very easy to unlock.
If we go back to IO for a second. There is no "run" function for IO. There is no runIO
function, or runIO_T
transformer. You can't conjure IO computations out of nothing (at least not safely). Your program's entrypoint is always a function that looks like:
main :: IO ()
To use any IO function in your program, you must have an unbroken chain of IO access going back to this main function, whether through the IO
monad itself or a transformer like StateT IO
. This pattern allows us to close off large parts of our program to IO computations. But no part of your program is firmly closed off to a State
computation. As long as you can generate the "initial" state, you can then access the State
monad via runState
.
So if you were struggling with the same stumbling block I was, hopefully this clears things up for you! If you want more examples of how monads work and how to apply these run functions, take a look at our series Monads and Functional Structures!
Using Either as a Monad
Now that February is over and we're into March, it's time for "Monads Month"! Over the course of the next month I'll be giving some helpful tips on different ways to use monads.
Today I'll start with a simple observation: the Either
type is a monad! For a long time, I used Either
as if it were just a normal type with no special rules. But its monadic behavior allows us to chain together several computations with it with ease!
Let's start from the beginning. What does Either
look like? Well, it's a very basic type that can essentially hold one of two types at runtime. It takes two type parameters and has two corresponding constructors. If it is "Left", then it will hold a value of the first type. If it is "Right" then it will hold a value of the second type.
data Either a b = Left a | Right b
A common semantic understanding of Either
is that it is an extension of Maybe
. The Maybe
type allows our computations to either succeed and produce a Just
result or fail and produce Nothing
. We can follow this pattern in Either
except that failures now produce some kind of object (the first type parameter) that allows us to distinguish different kinds of failures from each other.
Here's a basic example I like to give. Suppose we are validating a user registration, where they give us their email, their password, and their age. We'll provide simple functions for validating each of these input strings and converting them into newtype
values:
newtype Email = Email String
newtype Password = Password String
newtype Age = Age Int
validateEmail :: String -> Maybe Email
validateEmail input = if '@' member input
then Just (Email input)
else Nothing
validatePassword :: String -> Maybe Password
validatePassword input = if length input > 12
then Just (Password input)
else Nothing
validateAge :: String -> Maybe Age
validateAge input = case (readMaybe input :: Maybe Int) of
Nothing -> Nothing
Just a -> Just (Age a)
We can then chain these operations together using the monadic behavior of Maybe
, which short-circuits the computation if Nothing
is encountered.
data User = User Email Password Age
processInputs :: (String, String, String) -> Maybe User
processInputs (i1, i2, i3) = do
email <- validateEmail i1
password <- validatePassword i2
age <- validateAge i3
return $ User email password age
However, our final function won't have much to say about what the error was. It can only tell us that an error occurred. It can't tell us which input was problematic:
createUser :: IO (Maybe User)
createUser = do
i1 <- getLine
i2 <- getLine
i3 <- getLine
result <- processInputs (i1, i2, i3)
case result of
Nothing -> print "Couldn't create user from those inputs!" >> return Nothing
Just u -> return (Just u)
We can extend this example to use Either
instead of Maybe
. We can make a ValidationError
type that will help explain which kind of error a user encountered. Then we'll update each function to return Left ValidationError
instead of Nothing
in the failure cases.
data ValidationError =
BadEmail String |
BadPassword String |
BadAge String
deriving (Show)
validateEmail :: String -> Either ValidationError Email
validateEmail input = if '@' member input
then Right (Email input)
else Left (BadEmail input)
validatePassword :: String -> Either ValidationError Password
validatePassword input = if length input > 12
then Right (Password input)
else Left (BadPassword input)
validateAge :: String -> Either ValidationError Age
validateAge input = case (readMaybe input :: Maybe Int) of
Nothing -> Left (BadAge input)
Just a -> Right (Age a)
Because Either
is a monad that follows the same short-circuiting pattern as Maybe
, we can also chain these operations together. Only now, the result we give will have more information.
processInputs :: (String, String, String) -> Either ValidationError User
processInputs (i1, i2, i3) = do
email <- validateEmail i1
password <- validatePassword i2
age <- validateAge i3
return $ User email password age
createUser :: IO (Either ValidationError User)
createUser = do
i1 <- getLine
i2 <- getLine
i3 <- getLine
result <- processInputs (i1, i2, i3)
case result of
Left e -> print ("Validation Error: " ++ show e) >> return e
Right u -> return (Right u)
Whereas Maybe
gives us the monadic context of "this computation may fail", Either
can extend this context to say, "If this fails, the program will give you an error why."
Of course, it's not mandatory to view Either
in this way. You can simply use it as a value that could hold two arbitrary types with no error relationship:
parseIntOrString :: String -> Either Int String
parseIntOrString input = case (readMaybe input :: Maybe Int) of
Nothing -> Right input
Just i -> Left i
This is completely valid, you just might not find much use for the Monad
instance.
But you might find the monadic behavior helpful by making the Left
value represent a successful case. Suppose you're writing a function to deal with a multi-layered logic puzzle. For a simple example:
- If the first letter of the string is capitalized, return the third letter. Otherwise, drop the first letter from the string.
- If the third letter in the remainder is an 'a', return the final character. Otherwise, drop the last letter from the string. 3 (and so on with similar rules)
We can encode each rule as an Either
function:
rule1 :: String -> Either Char String
rule1 input = if isUpper (head input)
then Left (input !! 2)
else Right (tail input)
rule2 :: String -> Either Char String
rule2 input = if (input !! 2 == 'a')
then Left (last input)
else Right (init input)
rule3 :: String -> Either Char String
...
To solve this problem, we can use the Either
monad!
solveRules :: String -> Either Char String
solveRules input = do
result1 <- rule1 input
result2 <- rule2 result1
...
If you want to learn more about monads, you should check out our blog series! For a systematic, in depth introduction to the concept, you can also take our Making Sense of Monads course!
Treating Strings like Lists
We've spent the last month studying some of the intricacies of the different string types in Haskell. For this last article, I wanted to have a little bit of fun and consider how we might apply some of the ideas we learned from "list" functions back in January to these different string types.
The naive way to use these functions would be to transform your "Text" or "ByteString" back into a "String", run the list-based function, and then convert back. But it should hopefully be obvious now that that isn't such a great idea!
But these other string types still kind of seem like lists, so we should want to apply those functions. And because of this the authors for those packages included versions of the most common list based functions specifically geared for these types. For example, Text and ByteStrings have their own versions of functions like "map", "filter" and "fold".
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text as T
import qualified Data.ByteString as B
T.map :: (Char -> Char) -> T.Text -> T.Text
T.filter :: (Char -> Bool) -> T.Text -> T.Text
T.foldl :: (a -> Char -> a) -> a -> T.Text -> a
B.map :: (Word8 -> Word8) -> B.ByteString -> B.ByteString
B.filter :: (Word8 -> Bool) -> B.ByteString -> B.ByteString
B.foldl :: (a -> Word8 -> a) -> a -> B.ByteString -> a
Notice how for the ByteString
, instead of Char
being the underlying value, it's Word8
. Unfortunately, this makes it a little harder to do character-specific manipulations, but we can still try!
>> T.filter isLower "Hello World"
"ello orld"
>> B.map (+1) "abcde"
"bcdef"
The numeric property underlying ByteStrings means you can do some interesting mathematical things with them. For example, you can build a simple "Caesar Cypher" like so, if you assume that your input is all lower-case or spaces. The number 97 indicates the offset for the letter 'a' as a Word8
:
caesarCypher :: Int -> B.ByteString -> B.ByteString
caesarCypher shift = B.map shiftChar
where
shiftChar :: Word8 -> Word8
shiftChar 32 = 32 -- Space
shiftChar x = (((x - 97) + shift) `mod` 26) + 97
...
>> caesarCypher "hello there"
"mjqqt ymjwj"
Even more complicated functions like intercalate
and transpose
can be found in these libraries!
>> let bytestrings = ["One", "Two", "Three", "Four", "Five"] :: [B.ByteString]
>> B.transpose bytestrings
["OTTFF", "nwhoi", "eoruv", "ere", "e"]
>> let texts = ["Hello", "my", "friend"] :: [T.Text]
>> T.intercalate ", " texts
"Hello, my, friend"
So even if your program is using more advanced string types instead of the basic [Char]
, you can still get all the functional benefits of Haskell's many different ways of manipulating lists of values!
This is the end of our exploration of string types for now. But we'll be back with a new topic for March. So subscribe to our newsletter and keep coming back here if you want to stay up to date with the blog!
Fusion Powered Strings!
In the last few articles, we've gone through the different String types in Haskell, and I've made a few mentions of "efficiency". But what does this mean? It should be a bit more clear with Bytestrings. By using a more compact representation of the data, and operating on a lower level type like Word8
, our strings will take up less space in memory, and they'll have more continuity. This makes operations on them more efficient. But what is it, exactly, about Text
that makes it more efficient than using String
?
One part of the answer to that is the concept of fusion. Throughout the documentation on Data.Text
, you'll see the phrase "subject" to fusion. The short explanation is that GHC knows how to "fuse" Text operations together so that they happen more quickly and take less memory. Let's see a quick example of this.
Suppose we want to write a string manipulation function. This will drop the first 6 letters, add the letter 'a' on the beginning, append the word "goodbye" to the end, and then capitalize the whole thing. Here's what that function might look like using String
:
transformString :: String -> String
transformString s = map toUpper $ 'a' : (drop 6 s) ++ ", goodbye."
...
>> transformString "Hello Friend"
"AFRIEND, GOODBYE."
In the process of making our final string ("AFRIEND, GOODBYE."
), our program will actually make 4 different strings for each of the operations we run.
- First, it will drop the 6 letters, and allocate space for the string
"Friend"
. - Then, it will add the "a" to the front, giving us a new string
"aFriend"
. - Next, it will append "goodbye", and we'll have
"aFriend, goodbye."
. - Finally, it will uppercase everything, giving
"AFRIEND, GOODBYE."
.
Allocating all this memory seems a bit wasteful. After all, the first three strings were just intermediate computations! But Haskell can't modify these values in place, because values are immutable in Haskell!
However, the Text
type is specifically designed to work around this. It is written so that when compiled with optimizations, fusion functions can all be combined into a single step. So suppose we write the equivalent function with Text:
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text as T
transformString :: T.Text -> T.Text
transformString s = T.map toUpper $ T.append (T.cons 'a' (T.drop 6 s)) ", goodbye."
...
>> transformString (T.pack "Hello Friend")
"AFRIEND, GOODBYE."
If we compile this code with sufficient optimization, it will only allocate memory for the final Text
value. There will be no intermediate allocations, because the whole function is "fused"!
Fusion is a tricky subject, but if you're just starting out with Haskell, you don't need to worry too much about it. You should focus on the basics, like getting conversions right between these types. For more tips and tricks that will help you on your Haskell journey, download our free Beginners Checklist. It'll give you some more help and guide you to other resources that will help you learn!
Loading Different Strings
In the last couple of articles we've explored the Text
and ByteString
types. These provide alternative string representations that are more efficient when compared to the basic "list of characters" definition. But we've also seen that it can be a minor pain to remember all the different ways we can convert back and forth between them. And in fact, not all conversions are actually guaranteed to work the way we would like.
Now let's imagine our program deals with input and output. Now as a beginner, it's easy to think that there are only a couple primary functions for dealing with input and output, and that these only operate on String
values:
putStrLn :: String -> IO ()
getLine :: IO String
If your program actually makes use of Text
, you might end up (as I have in many of my programs) constantly doing something like T.pack <$> getLine
to read the input as a Text
, or putStrLn (unpack text)
to print it out.
When you're dealing with files, this makes it more likely that you'll mess up lazy vs. strict semantics. You'd have to know that readFile
is lazy, and readFile'
, an alternative, is strict.
readFile :: FilePath -> IO String
readFile' :: FilePath -> IO String
But you don't need to resort to always using the String
type! All of our alternative types have their own IO functions. You just have to know which modules to use, and import them in a qualified way!
With Text
, there are separate IO
modules that offer these options for us:
import qualified Data.Text as T
import qualified Data.Text.IO as TI
import qualified Data.Text.Lazy as TL
Import qualified Data.Text.Lazy.IO as TLI
TI.putStrLn :: T.Text -> IO ()
TI.getLine :: IO T.Text
TLI.putStrLn :: TL.Text -> IO ()
TLI.getLine :: IO TL.Text
The modules also contain functions for reading and writing files as well, all with the appropriate Text
types.
With ByteString
types, these functions live in the primary module, and they aren't quite as exhaustive. There is putStr
, but not putStrLn
(I'm not sure why).
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
B.putStr :: B.ByteString -> IO ()
B.getLine :: IO B.ByteString
BL.putStr :: BL.ByteString -> IO ()
BL.getLine :: IO BL.ByteString
So, when you're program is using the more sophisticated string types, use the appropriate input and output mechanisms to spare yourself the inefficiencies (both performance-wise and in terms of code clarity) that come with using String
as a go-between!
If you've never really written a Haskell program with input and output before, you'll need to have some idea of how Stack works so you can get all the pieces working. Take our free Stack mini-course to learn how everything works, and stay tuned for more tips on Strings and other useful areas of Haskell!
Taking a Byte out of Strings
Earlier this week we learned about the Text
type, which is a more efficient alternative to String
. But there's one more set of string-y types we need to learn about, and these are "ByteStrings"!
The Text
types capture a unicode representation of character data. But ByteString
is more low-level, storing its information at the "byte" level. A normal string is a list of the Char
type, but the fundamental underlying data structure of the ByteString
is a list of Word8
- an 8-bit (1 byte) unsigned integer!
This means that in the normal ByteString
library, the pack
and unpack
functions are reserved for converting back and forth between [Word8]
and the ByteString
, rather than a raw String
:
pack :: [Word8] -> ByteString
unpack :: ByteString -> [Word8]
This can make it seem as though it would be quite difficult to construct ByteStrings.
>> import qualified Data.ByteString as B
>> let b = B.pack [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]
>> b
"Hello world!"
>> B.unpack b
[72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]
There are two ways around this. Once again, we can use the "OverloadedStrings" extension:
>> :set -XOverloadedStrings
>> let b = "Hello world!" :: B.ByteString
>> :t b
b :: B.ByteString
There is also another module called Data.ByteString.Char8
. In this module, the pack
and unpack
functions are used for strings instead of Word8
chunks. Note though, that all characters you use will be truncated to 8 bits, so your results may not be correct, especially if you go far beyond the simple ASCII character set.
>> import qualified Data.ByteString.Char8 as BC
>> let b = BC.pack "Hello World!"
>> b
"Hello world!"
>> :t b
BC.ByteString
>> :t (BC.unpack b)
[Char]
Like Text
, there are both strict and lazy versions of ByteString
, which you convert using the same way.
>> import qualified Data.ByteString as B
>> import qualified Data.ByteString.Lazy as BL
>> :set -XOverloadedStrings
>> let b1 = "Hello" :: B.ByteString
>> let b2 = "World" :: BL.ByteString
>> let b3 = BL.fromStrict b1
>> let b4 = BL.toStrict b2
>> :t b3
BL.ByteString
>> :t b4
B.ByteString
The last item to make a note of is that we can convert directly between Text
and ByteString
. This is often desirable because of the transcription errors that can occur using String
as a go-between. It also allows strictness or laziness to be maintained, since the following functions live in both Data.Text.Encoding
and Data.Text.Lazy.Encoding
.
The trick is that we have to have some idea of what encoding we are using. Most often, this is UTF-8. In this case, we can use the following functions:
encodeUtf8 :: Text -> ByteString
decodeUtf8 :: ByteString -> Text
Let's see these sorts of functions in action:
>> import qualified Data.Text as T
>> import qualified Data.ByteString as B
>> import Data.Text.Encoding
>> let t = T.pack "Hello world"
>> let b = encodeUtf8 t
>> b
"Hello world"
>> :t b
B.ByteString
>> let t2 = decodeUtf8 b
>> t2
"Hello world"
>> :t t2
T.Text
Encoding a Text
as a ByteString
will always succeed. But decoding a ByteString
might fail. This is because the raw bytes someone uses might not be a representation of a valid set of characters. So decodeUtf8
can throw errors in special cases. If you're concerned about catching these, you can use one of the following functions:
decodeUtf8' :: ByteString -> Either UnicodeException Text
decodeUtf8With :: OnDecodeError -> ByteString -> Text
Other encodings exist besides UTF-8, but you also need to know if it is "big-endian" or "little-endian", indicating the ordering of the bytes in the underlying representation:
encodeUtf16LE :: Text -> ByteString
decodeUtf32BEWith :: OnDecodeError -> ByteString -> Text
It can be difficult to keep all these conversions straight. But here's the TLDR, with 4 different conversions to remember:
- String <-> Text - Use
Data.Text
, withpack
andunpack
. - String <-> ByteString - Use
Data.ByteString.Char8
, withpack
andunpack
- Text <-> ByteString - Use
Data.Text.Encoding
withencodeUtf8
anddecodeUtf8
- Strict and Lazy - Use the "Lazy" module (
Data.Text.Lazy
orData.ByteString.Lazy
) withfromStrict
andtoStrict
.
Like text
, bytestring
is a separate package, so you'll need to include in your project with either Stack or Cabal (or Nix). To learn how to use the Stack tool, sign up for our free Stack mini-course!
Con-Text-ualizing Strings
In the past couple weeks of string exploration, we've only consider the basic String
type, which is just an alias for a list of characters:
type String = [Char]
But it turns out that this representation has quite a few drawbacks. There are other string representations that are more compact and result in more efficient operations, which is paramount when you are parsing a large amount of data.
But since the type system plays such a strong role in Haskell, each of these different string representations must have its own type. Today we'll talk about Text
, which is one of these alternatives.
Rather than storing a raw list of the Char
type, a Text
object stores values as unicode characters. This allows many operations to be much faster.
But perhaps the first and most important thing to learn when you're a beginner is how to convert back and forth between a normal String
and Text
. This is done with the pack
and unpack
functions:
import Data.Text (Text, pack, unpack)
pack :: String -> Text
unpack :: Text -> String
Because the underlying representations are a bit different, not every String
can be converted into Text
in a comprehensible manner. But if you're sticking to the basic ASCII character set, everything will work fine.
>> let s = "ABCD" :: String
>> let t = pack s
>> t
"ABCD"
>> :t t
Text
>> unpack t
"ABCD"
>> :t (unpack t)
[Char]
The Text library has many different functions for manipulating Text
objects. For example, append
will combine two Text
items, and cons
will add a character to the front. Many of these overlap with Prelude functions, so it is usually best to import this module in a qualified way.
>> import qualified Data.Text as T
>> let t1 = T.pack "Hello"
>> let t2 = T.pack "World"
>> T.append t1 (T.cons ' ' t2)
"Hello World"
Naturally, Text
implements the IsString
class we talked about a little while ago. So if you enable OverloadedStrings
, you don't actually need to use pack
to initialize it! You can just use a string literal.
>> import qualified Data.Text as T
>> :set -XOverloadedStrings
>> let t = "Hello" :: T.Text
Now technically there are two different Text
types. So far, we've referred to "strict" Text
objects. These must store all of their data in memory at once. However, we can also have "lazy" Text
objects. These make use of Haskell's laziness mechanics so that you can stream data without having to store it all at once. All the operations are the same, they just come from the Data.Text.Lazy
module instead of Data.Text
!
>> import qualified Data.Text.Lazy as TL
>> let t1 = TL.pack "Hello"
>> let t2 = TL.pack "World"
>> TL.append t1 (TL.cons ' ' t2)
"Hello World"
There will often come times where you need to convert back and forth between these. The Lazy module contains functions for doing this.
>> import qualified Data.Text as T
>> import qualified Data.Text.Lazy as TL
>> let t1 = T.pack "Hello"
>> let t2 = TL.pack "World"
>> let t3 = TL.fromStrict t1
>> let t4 = TL.toStrict t2
>> :t t3
TL.Text
>> :t t4
T.Text
Because these types live in the text package, and this package is not included in base
, you'll need to know how dependencies work in use it in your projects. To learn more about this, take our free Stack mini-course! You'll learn how to make a project using Stack and add dependencies to it.
When Strings get Word-y
Earlier this week we explored the lines
and unlines
functions which allow us to split up strings based on where the newline characters are and then glue them back together if we want. But a lot of times the newline is the character you're worried about, it's actually just a normal space! How can we take a string that is a single line and then separate it?
Again, we have very simple functions to turn to: words
and unwords
:
words :: String -> [String]
unwords :: [String] -> String
These do exactly what you think they do. We can take a string with several words in it and break it up into a list that excludes all whitespace.
>> words "Hello there my friend"
["Hello", "there", "my", "friend"]
And then we can recombine that list into a single string using unwords
:
>> unwords ["Hello", "there", "my", "friend"]
"Hello there my friend"
This pattern is actually quite helpful when solving problems of the kind you'll see on Hackerrank. Your input will often have a particular format like, "the first line has 'n' and 'k' which are the number of cases and the case size, and then there are 'n' lines with 'k' elements in them." So this might look like:
5 3
1 2 3
4 5 6
7 8 9
10 11 12
13 14 15
Then words
is very useful for splitting each line. For example:
readFirstLine :: IO (Int, Int)
readFirstLine = do
firstNumbers <- words <$> getLine
case firstNumbers of
(n : k : _) -> return (n, k)
_ -> error "Invalid input!"
As with lines
and unlines
, these functions aren't inverses, properly speaking. With unwords
, the separator will always be a single space character. However, if there are multiple spaces in the original string, words
will work in the same way.
>> words "Hello there my friend"
["Hello", "there", "my", "friend"]
>> unwords (words "Hello there my friend")
"Hello there my friend"
In fact, words
will separate based on any whitespace characters, including tabs and even newlines!
>> words "Hello \t there \n my \n\t friend"
["Hello", "there", "my", "friend"]
So it could actually do the same job as lines
, but then reversing the process would require unlines
rather than `unwords.
Finally, it's interesting to note that unlines
and unwords
are both really just variations of intercalate
, which we learned about last month.
unlines :: [String] -> String
unlines = intercalate "\n"
unwords :: [String] -> String
unwords = intercalate " "
For more tips on string manipulation in Haskell, make sure to come back next week! We'll start exploring the different String types that exist in Haskell! If you're interested in problem solving in Haskell, you should try our free Recursion Workbook! It explains all the ins and outs of recursion and includes 10 different practice problems!
Line 'em Up!
Reading from files and writing to files is a very important job that you'll have to do in a lot of programs. So it's very much worth investing your time in learning functions that will streamline that as much as possible.
Enter lines
and unlines
. These two functions make it substantially easier for you to deal with the separate lines of a file (or any other string really). Fundamentally, these two functions are inverses of each other. The "lines" function will take a single string, and return a list of strings, while "unlines" will take a list of strings and return a single string.
lines :: String -> [String]
unlines :: [String] -> String
Our friend "lines" takes a string that has newline characters (or carriage returns if you're using Windows) and then break it up by the lines.
>> lines "Hello\nWorld\n!"
["Hello", "World", "!"]
Then "unlines" is able to take that list and turn it back into a single string, where the entries of the list are separated by newlines.
>> unlines ["Hello", "World", "!"]
"Hello\nWorld\n!\n"
One distinction you'll notice is that our final string from "unlines" has an extra newline character. So strictly speaking, these functions aren't total inverses (i.e. unlines . lines
/= id
). But they still essentially function in this way.
As mentioned above, these functions are extremely handy for file reading and writing. Suppose we are reading a file and want to break it into its input lines. A naive solution would be to produce a Handle
and then use iterated calls to hGetLine
. But this gets tedious and is also error prone when it comes to our end condition.
readFileLines :: FilePath -> IO [String]
readFileLines fp = do
handle <- openFile fp ReadMode
results <- readLinesHelper handle []
hClose handle
return results
where
readLinesHelper :: Handle -> [String] -> IO [String]
readLinesHelper handle prevs = do
isEnd <- hIsEOF handle
if isEnd
then return (reverse prevs)
else do
nextLine <- hGetLine handle
readLinesHelper handle (nextLine : prevs)
Boy that's a little messy…
And similarly, if we wanted to write each string in a list to a separate line in the file using hPutStrLn
, this is non-trivial.
writeFileLines :: [String] -> FilePath -> IO ()
writeFileLines strings fp = do
handle <- openFile fp WriteMode
mapM_ (hPutStrLn handle) strings
hClose handle
But these both get waaay easier if we think about using lines
and unlines
. We don't even have to think about the file handle! When reading a file, we just use lines
in conjunction with readFile
:
readLines :: FilePath -> IO [String]
readLines fp = lines <$> readFile fp
Then similarly, we use unlines
with writeFile
:
writeFileLines :: [String] -> FilePath -> IO ()
writeFileLines strings fp = writeFile fp (unlines strings)
And thus what were tricky implementations (especially for a beginner) are now made much easier!
Later this week we'll look at another set of functions that help us with this problem of splitting and fusing our strings. In the meantime, make sure to sign up for the Monday Morning Haskell newsletter if you haven't already! If you do, you'll get access to a lot of our great resources for beginning and intermediate Haskellers alike!
Classy Strings
In January we shared many different examples of helpful functions you can use with lists. In Februrary, we'll be focusing on Strings. Of course, Haskell's different string representations all use lists to some extent, so we've already seen several examples of list manipulation being useful with strings. But now we'll look more specifically at the different types that all us to describe string-like data.
Our first useful idea is the IsString class. This can apply to any type that we can derive from a String. The key function is fromString
.
class IsString a where
fromString :: String -> a
Making an instance of this type is easy enough as long as your data makes sense. Suppose we have a simple newtype
wrapper for a string.
newtype Person = Person String
Our IsString
instance is trivial:
instance IsString Person where
fromString = Person
But sometimes we might want something a little trickier. Suppose we want to give our person a first AND last name, given only a single string.
data Person = Person
{ firstName :: String
, lastName :: String
} deriving (Show)
Now our instance is non-trivial, but still simple if we remember our helpful list functions! We first take all the characters that are "not spaces" to get the first name. Then to get the last name, we drop these characters instead, and then drop all the spaces as well!
instance IsString Person where
fromString s = Person firstName lastName
where
firstName = takeWhile (not . isSpace) s
lastName = dropWhile isSpace (dropWhile (not . isSpace) s)
We can use this instance in the simple way, applying the fromString
function directly:
>> fromString "George Washington" :: Person
Person {firstName = "George", lastName = "Washington"}
But what's more interesting is that if we turn on the "Overloaded Strings" extension, we can use a string literal for this object in our code!
>> :set -XOverloadedStrings
>> let president = "George Washington" :: Person
This compiler extension is extremely useful once we get to other kinds of strings (Text
, ByteString
, etc.). But, combined with the IsString
class, it's a nice little quirk that can make our lives easier in certain circumstances, especially when we are running some kind of parsing program, or pasting in stringified data into our Haskell source when we don't want to bother reformatting it.
We'll be back next Monday with more string tricks! If you want to use tricks like these in a project, you need to learn how to use Stack first. To help with this, sign up for our free Stack mini-course!
To Infinity and Beyond!
We're at the end of January now, so this will be the last article on basic list utilities. For this last set of functions, we'll explore some items that are very helpful when you are using infinite lists in Haskell.
Infinite lists are kind of a cool construct because they only really exist due to Haskell's lazy evaluation mechanisms. Most other languages don't have them because eager evaluation would force you to use an infinite amount of space! But in Haskell, you only need to allocate space for the items in the list that actually get evaluated.
The two most basic functions for creating an infinite list are "repeat" and "cycle". The first makes an infinite number of the same element, while the second allows you to cycle through a specific series of elements.
repeat :: a -> [a]
cycle :: [a] -> [a]
But how exactly are these useful? For example, simply by trying to print them we'll create a mess of output that will make us force quit the program:
>> repeat 3
[3, 3, 3, 3, 3, ...
>> cycle [1, 2, 3, 4]
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, ...
Well one answer is to use take
, which we learned about last time. We can "take" a specific number of a single element.
>> take 5 (repeat 3)
[3, 3, 3, 3, 3]
Infinite lists are also great in conjunction with zip
, because this "shortens" the result to the size of the finite list. Suppose we want to match up a list with the index modulo 4:
>> let l1 = [5, 3, 1, -4, 6, 20]
>> zip l1 (cycle [0, 1, 2, 3])
[(5, 0), (3, 1) ,(1, 2) ,(-4, 3) ,(6, 0) ,(20, 1)]
One more way to generate an infinite list is to use iterate
. This allows us to continually apply a function against a starting value. We start with a
, and then the second element will apply the function once as f a
, and then the next value will be f (f a)
, and so on.
iterate :: (a -> a) -> a -> [a]
One use case for this might be to calculate the value of an investment using compound interest. Let's say you start with $10,000 and you get 5% interest every year. How much will you have after 5 years?
>> take 6 (iterate (* 1.05) 10000.0)
[10000.0, 10500.0, 11025.0, 11576.25, 12155.06, 12762.82]
You can also use fairly simple addition functions with iterate
. But you should also know that in most cases you can represent such an infinite list with a range!
>> take 5 (iterate (+3) 1)
[1, 4, 7, 10, 13]
>> take 5 [1,4..]
[1, 4, 7, 10, 13]
That's all for our exploration of basic list functions! We'll have a new topic next month, so subscribe to our monthly newsletter to stay on top of what we're studying! You can also sign up by downloading our free Beginners Checklist!
Taking and Dropping
In our first article on list functions, we looked at some boolean functions that allowed for filtering lists based on particular conditions. Of course, filter
is the primary function we might use for that. But sometimes, you're only concerned with how long the pattern is at the start of your list. You want to include some of the elements at the start, or else drop them. Today we'll look at variations of "take" and "drop" functions that let you do this.
The most basic versions of these functions allow you to specify the number of elements you're interested. The "take" function takes an integer and your list and produces the first "n" elements, while "drop" will give you the remainder of the list besides these elements:
take :: Int -> [a] -> [a]
drop :: Int -> [a] -> [a]
Getting the 5 smallest numbers in a list is easy when you combine take
and sort
.
>> let myList = [2, 5, 6, 3, 1, 9, 8, 7, 4]
>> take 5 (sort myList)
[1, 2, 3, 4, 5]
>> drop 5 (sort myList)
[6, 7, 8, 9]
This is simple enough, but sometimes you want to do something a little trickier. You want to take all the elements of the list from the front, but only as long as they satisfy a particular condition. In this case, you want takeWhile
and its comrade, dropWhile
. Each of these takes a predicate function instead of a number.
takeWhile :: (a -> Bool) -> [a] -> [a]
dropWhile :: (a -> Bool) -> [a] -> [a]
A couple simple use cases might be removing the "odd" numbers from the start of a list, or grabbing only the lowercase letters of a variable name that you've parsed.
>> let isOdd x = x `mod` 2 == 1
>> dropWhile isOdd [5, 7, 9, 2, 1, 3, 4]
[2, 1, 3, 4]
>> takeWhile isLower "variableNameInCamelCase"
"variable"
Now in the Data.List.Extra library, there are some additional variations that allow for taking and dropping from the end:
takeWhileEnd :: (a -> Bool) -> [a] -> [a]
dropWhileEnd :: (a -> Bool) -> [a] -> [a]
We'll cover more of these kinds of "extra" functions later in the year. But these two examples do the same thing as their counterparts, just operating from the end of the list instead of the start.
>> isOdd x = x `mod` 2 == 1
>> takeWhileEnd isOdd [2, 4, 1, 6, 7, 9]
[7, 9]
>> dropWhileEnd isOdd [2, 4, 1, 6, 7, 9]
[2, 4, 1, 6]
We'll soon be moving on from list manipulations to a new topic. To keep up to date, make sure you keep checking this blog every Monday and Thursday, or subscribe to our monthly newsletter! If you're just starting out learning Haskell, subscribing will give you access to our Beginners Checklist which will help you learn the basics and get going!
Math-y List Operations
Earlier this month we explored some functions related to booleans and lists. Today we'll consider a few simple helpers related to lists of numbers.
As in Python, Haskell allows you to easily take the product and sum of a list. These functions are very straightforward:
sum :: (Num a) => [a] -> a
product :: (Num a) => [a] -> a
...
>> sum [1, 2, 3, 4]
10
>> sum [-1, 1]
0
>> product [1, 2, 3, 4]
24
>> product [5, 6, -2]
-60
As with our boolean functions, I originally gave the type signatures in terms of lists, but they actually work for any "Foldable" type. So both the inner and outer type of our input are parameterized.
sum :: (Foldable t, Num a) => t a -> a
product :: (Foldable t, Num a) => t a -> a
...
>> sum $ Set.fromList [1, 2, 3]
6
Both of these functions also work with empty lists, since these operations have an identity to start with.
>> sum []
0
>> product []
1
The same cannot be said for the functions minimum
and maximum
. These require non-empty lists, or they will throw an error!
minimum :: (Foldable t, Ord a) => t a -> a
maximum :: (Foldable t, Ord a) => t a -> a
...
>> maximum [5, 6, -1, 4]
6
>> minimum [5, 6, -1, 4]
-1
>> maximum []
*** Exception: Prelude.maximum: empty list
And while they can be used with numbers, as above, they can also be used with any orderable type, such as strings.
>> maximum ["hello", "world"]
"world"
Remember what we mentioned a while ago with groupBy
and how many functions have another version with By
? This applies for our min and max functions. These allow us to provide a custom comparison operation.
maximumBy :: (Foldable t) => (a -> a -> Ordering) -> t a -> a
minimumBy :: (Foldable t) => (a -> a -> Ordering) -> t a -> a
As an example, let's consider that when comparing tuples, the first element is used to sort them. Only if there is a tie to we go to the second element.:
>> maximum [(1, 4), (2, 1), (2, 3)]
(2, 3)
However, we can instead use the second element as the primary comparison like so:
>> let f (x1, x2) (y1, y2) = if x2 == y2 then x1 `compare` y1 else x2 `compare` y2
>> maximumBy f [(1, 4), (2, 1), (2, 3)]
(1, 4)
>> minimumBy f [(1, 4), (2, 1), (2, 3)]
(2, 1)
This approach is also very helpful when you're dealing with more complex types, but you only care about comparing a single field.
As a final note, sort
and sortBy
follow this same pattern. The first one assumes an ordering property, the second allows you a custom ordering.
sort :: (Ord a) => [a] -> [a]
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
For more tricks like this, keep following the Monday Morning Haskell blog every Monday and Thursday! You can subscribe to get our monthly newsletter as a summary. This will also get you access to subscriber resources like our Beginners Checklist.
Changing and Re-Arranging
Any time I go to a wedding or some kind of family gathering where we take a lot of pictures, it can seem like it goes on for a while. It seems like we have to take a picture with every different combination of people in it. Imagine how much worse it would be if we needed the also get every different ordering (or permutation) of people as well!
Even if it got to that level, it would still be easy to write a Haskell program to handle this problem! There are a couple specific list functions we can use. The "subsequences" function will give us the list of every subsequence of the input list.
subsequences :: [a] -> [[a]]
This would be helpful if all we want to know is the different combinations of people we would get in the pictures.
>> subsequences ["Christina", "Andrew", "Katharine"]
[[], ["Christina"], ["Andrew"], ["Christina", "Andrew"], ["Katharine"], ["Christina", "Katharine"], ["Andrew", "Katharine"], ["Christina", "Andrew", "Katharine"]]
Note a couple things. First, our result includes the empty sequence! Second of all, the order of the names is always the same. Christina is always before Andrew, who is always before Katharine.
Now let's suppose we have a different problem. We want everyone in the picture, but we can order them anyway we want. How would we do that? The answer is with the "permutations" function.
permutations :: [a] -> [[a]]
This will give us every different ordering of our three people.
>> permutations ["Christina", "Andrew", "Katharine"]
[["Christina", "Andrew", "Katharine"], ["Andrew", "Christina", "Katharine"], ["Katharine", "Andrew", "Christina"], ["Andrew", "Katharine", "Christina"], ["Katharine", "Christina", "Andrew"], ["Christina", "Katharine", "Andrew"]]
Be wary though! These functions are mostly useful with small input lists. The number of subsequences of a list grows exponentially. With permutations, it grows with the factorial! By the time you get up to 10, you're already dealing with over 3 million possible permutations!
>> length (subsequences [1, 2, 3, 4])
16
>> length (subsequences [1, 2, 3, 4, 5])
32
>> length (subsequences [1, 2, 3, 4, 5, 6])
64
>> length (permutations [1, 2, 3, 4])
24
>> length (permutations [1, 2, 3, 4, 5])
120
>> length (permutations [1, 2, 3, 4, 5, 6])
720
If such cases are really necessary for you to handle, you might need to take advantage of Haskell's laziness and treat the result similar to an infinite list, as we'll cover later this month.
If you want to keep learning about random interesting functions, you should subscribe to Monday Morning Haskell! You'll get access to all our subscriber resources, including our Beginners Checklist.
Transposing Rows
In our last article we explored how groupBy
could allow us to arrange a Matrix (represented as an Array) into its rows.
myArray :: Array (Int, Int) Double)
myArray = listArray ((0, 0), (1, 1)) [1..4]
rows = groupBy (\((a, _), _) ((b, _), _) -> a == b) (assocs myArray)
...
>> rows myArray = [[((0, 0), 1), ((0, 1), 2)], [((1, 0), 3), ((1, 1), 4)]]
But what about a different case? Suppose we were most concerned about grouping the columns together? At first glance, it doesn't seem as though this would be possible using groupBy
, since the elements we want aren't actually next to each other in the assocs
list.
However, there is another simple list function we can use to complete this process. The transpose
function takes a list of lists, which we can think of as a "matrix". Then it transposes the matrix so that the rows are now the columns.
transpose :: [[a]] -> [[a]]
...
>> transpose [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Clearly we could use this function to achieve the result we want with our 2D array!
>> rows = groupBy (\((a, _), _) ((b, _), _) -> a == b) (assocs myArray)
>> transpose (rows myArray)
[[((0, 0), 1), ((1, 0), 3)], [((0, 1), 2), ((1, 1), 4)]]
This function, of course, works even if the input is not a "square" matrix.
>> transpose [[1, 2, 3, 4], [5, 6, 7, 8]]
[[1, 5], [2, 6], [3, 7], [4, 8]]
It even works if it is not rectangular! If some "rows" are shorter than others, than our result is still sound! Essentially, the first row in the result is "every first element of a list". The second row is every "second" element. And so on. Every element in the original list is represented in the final list. Nothing is dropped like you might have in zip
.
>> transpose [[1, 2, 3], [4], [5, 6], [], [7, 8, 9, 10]]
[[1, 4, 5, 7], [2, 6, 8], [3, 9], [10]]
Hopefully this is an interesting function that you hadn't thought of using before! If you want to keep seeing tricks like this, subscribe to our monthly newsletter so you can keep up to date with the different tricks we're exploring here. You'll also get access to our subscriber resources!
"Group" Theory
Last time around we saw the function "intercalate". This could take a list of lists, and produces a single list, separated by the list from the first argument. But today, we'll take a look at a couple functions that work in reverse. The "group" functions take something like a flat, single-level list, and turn it into a list of lists.
group :: (Eq a) => [a] -> [[a]]
groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
When you use the basic group
function, you'll take your list and turn it into a list of lists where each list contains successive elements that are equal.
>> group [1, 2, 2, 3, 4, 4, 4]
[[1], [2, 2], [3], [4, 4, 4]]
This is useful for answering a question like "What is the longest consecutive sequence in my list?" Like any list function, it can be used on strings.
>> group "Hello Jimmy"
["H", "e", "ll", "o", " ", "J", "i", "mm", "y"]
The groupBy
function provides more utility. It lets you pass in your own comparison test. For example, here's a rudimentary way to separate a raw CSV string. You "group" all characters that aren't strings, and then filter out the commas.
separateCSV :: String -> [String]
separateCSV s = filter (/= ",") (groupBy (\c1 c2 -> c1 /= ',' && c2 /= ',') s)
...
>> separateCSV "Hello,there,my,friend"
["Hello", "there", "my", "friend"]
Here's another example. Suppose you have an array representing a two-dimensional matrix:
myArray :: Array (Int, Int) Double
The assocs
function of the Array library will give you a flat list. But you can make this into a list of lists, where each list is a row:
myArray :: Array (Int, Int) Double)
rows = groupBy (\((a, _), _) ((b, _), _) -> a == b) (assocs myArray)
This is a common pattern you can observe in list functions. There's often a "basic" version of a function, and then a version ending in By
that you can augment with a predicate or comparison function so you can use it on more complicated data.
If you enjoyed this article, sign up for our mailing list to our newsletter so that you get access to all our subscriber resources. One example is our Beginners Checklist! This is a useful resource if you're still taking your first steps with Haskell!
In the Middle: Intersperse and Intercalate
This week we continue looking at some useful list-based functions in the Haskell basic libraries. Last time around we looked at boolean functions, this time we'll explore a couple functions to add elements in the middle of our lists.
These functions are intersperse
and intercalate
:
intersperse :: a -> [a] -> [a]
intercalate :: [a] -> [[a]] -> [a]
Using "intersperse" will place a single item in between every item in your list. For example:
>> intersperse 0 [1, 2, 1]
[1, 0, 2, 0, 1]
This can be used in strings, for example, to space out the letters:
>> intersperse ' ' "Hello"
"H e l l o"
The "intercalate" function is similar but "scaled up". Instead taking a single element and a list of elements as its two inputs, it takes a list and a "list of lists". This is even more flexible when it comes to the use case of separating strings. For example, you can comma separate a series of words:
>> intercalate ", " ["Hello", "my", "friend"]
"Hello, my, friend"
Using intercalate
combined with map show
can make it very easy to legibly print a series of items if you use commas, spaces, and newlines!
Another use case for intercalate could be if you are constructing a numeric matrix, but you want to add extra columns to pad on the right side. Suppose this is our starting point:
1 2 3 4
5 6 7 8
9 8 7 6
5 4 3 2
But what we want is:
1 2 3 4 0 0 0
5 6 7 8 0 0 0
9 8 7 6 0 0 0
5 4 3 2 0 0 0
Here's how we do this:
>> intercalate [0, 0, 0] [[1, 2, 3, 4], [5, 6, 7, 8], [9, 8, 7, 6], [5, 4, 3, 2]]
[ 1, 2, 3, 4, 0, 0, 0
, 5, 6, 7, 8, 0, 0, 0
, 9, 8, 7, 6, 0, 0, 0
, 5, 4, 3, 2, 0, 0, 0
]
The result ends up as just a single list though, so you can't really compose calls to intercalate
. Keep that in mind! Also, unlike the boolean functions from last time, these only work on lists, not any Foldable
object.
Make sure you subscribe to our monthly newsletter so you can stay up to date with the topic each month! Subscribing will give you access to all our subscriber resources, including our Beginners Checklist, so don't miss out!
Booleans in Lists
As announced last week, this year we'll be doing short form articles twice a week highlighting useful library functions. The focus area for the month of January is simple list functions. We'll start with some simple functions that interact with booleans.
Like any other language, Haskell has operators for dealing with booleans like "and" and "or":
(&&) :: Bool -> Bool -> Bool
(||) :: Bool -> Bool -> Bool
But these only work with two booleans. If you have a variable-sized list of items, you can't directly apply these operators. In a language like Java, you'd write a for loop to deal with this. Here's how you would take "or" over a whole list.
boolean[] myBools;
result = false;
for (boolean b : myBools) {
result |= b;
}
In Haskell, you'd write a recursive helper instead. But you get this for free with the and
and or
functions:
and :: [Bool] -> Bool
or :: [Bool] -> Bool
Here's how it might look in GHCI:
>> and [True, True, True]
True
>> and [True, False, True]
False
>> or [True, False, True]
True
>> or [False, False, False]
False
Most of the time though, you won't just be dealing with booleans. You'll have a list of other objects, and you'll want to ask a question like, "Do these all satisfy some condition?" If you can express that condition with a predicate function, you can check that condition for all items in the list using any
and all
. The first is like "or", the second is like "and".
any :: (a -> Bool) -> [a] -> Bool
all :: (a -> Bool) -> [a] -> Bool
Here they are in action with numbers:
>> any (\i -> i `mod` 2 == 0) [2, 4, 7, 8]
True
>> all (\i -> i `mod` 2 == 0) [2, 4, 7, 8]
False
These functions are equivalent to mapping the predicate and then using one of our earlier functions:
>> or (map (\i -> i `mod` 2 == 0) [2, 4, 7, 8])
True
>> all (map (\i -> i `mod` 2 == 0) [2, 4, 7, 8])
True
Finally, these functions aren't just for lists. They generalize to any foldable type:
and :: (Foldable t) => t Bool -> Bool
or :: (Foldable t) => t Bool -> Bool
any :: (Foldable t) => (a -> Bool) -> t a -> Bool
all :: (Foldable t) => (a -> Bool) -> t a -> Bool
...
>> and (Set.fromList [True, False])
False
Be sure to subscribe to our newsletter so you can stay up to date with the latest news! Subscribing will give you access to all our subscriber resources, including our Beginners Checklist!
New in ‘22!
Welcome to a new year of Monday Morning Haskell! I wanted to start the year off with a quick overview of my plans for the year. In 2021, I focused a bit less on blog content and quite a bit more on course content, my open source project Haskellings, as well as video and streaming content on our YouTube and Twitch pages.
For 2022 though, I'm renewing my focus on written content! The format will be a little different though. Instead of a lengthier piece once a week, I'm now going to be writing shorter-form articles twice a week, Monday AND Thursday. (So now you get Thursday morning Haskell in addition to Monday morning!)
Each of these articles will zero in on a few specific library functions. These will often be functions I hadn't necessarily known about during my first iterations of learning Haskell. In many cases I started to find some interesting uses as I ventured more into problem-solving on HackerRank over the last few months.
Each month will have a different theme. In January, we'll be exploring helpful list functions that are maybe a bit out of the ordinary.
Video Content
Now, speaking of problem-solving on HackerRank...
I'm going to continue with weekly streams whenever possible on my Twitch page, usually on Monday evenings. These will focus on solving technical problems on sites like HackerRank using Haskell. I want to explore the process of how to improve at these kinds of problems and see how this translates to other areas of my programming.
I plan on taking some of these sessions and making more YouTube videos out of them, but outside of this I probably won't be focusing too much on Haskell video content.
Announcement: Monday Morning Learning
Now in addition to all of this, I'm launching a new companion site, called Monday Morning Learning! Content on this site will go beyond Haskell and explore the general process of how to learn and improve at a new skill. If you enjoy Monday Morning Haskell, there's a good chance that you'll like this content as well!
Programming as a more general skill is definitely one of the focus areas of this site, but I'll also be exploring the learning process for completely different areas like playing games such as Chess and Go, or even becoming a better writer or making music.
This new site will have weekly content in both written and video form. In addition to my Haskell streams, I'll also do separate streams for learning-specific content.
I won't post too much more about Monday Morning Learning on this page, since I want to keep things Haskell-focused over here. But if you want to support these efforts, make sure to subscribe to all our pages!
Sign up for our Monday Morning Haskell newsletter!
Subscribe for notifications on our YouTube Channel!
For Monday Morning Learning, follow us on our newsletter, YouTube Channel, and Twitch page!