diff --git a/part2.html b/part2.html index 50f9348..b9c5724 100644 --- a/part2.html +++ b/part2.html @@ -1749,7 +1749,7 @@ word: ever operationThatProducesTheResult var2

11.2.1 Examples

-

You can find useful IO operations in the standard library modules Prelude and System.IO

+

You can find useful IO operations in the standard library modules Prelude and System.IO

Here’s an IO operation that asks the user for a string, and prints out the length of the string.

query :: IO ()
 query = do
@@ -3572,14 +3572,14 @@ None of the above
 
  • Data.Text represents a sequence of Unicode characters, just like String, only more efficient. Used when dealing with text.
  • Data.ByteString represents a sequence of bytes. Used when dealing with binary data.
  • -

    Additionally, both of these types come in lazy and strict variants. The docs for Data.Text summarize the difference well:

    +

    Additionally, both of these types come in lazy and strict variants. The docs for Data.Text summarize the difference well:

    The strict Text type requires that an entire string fit into memory at once. The lazy Text type is capable of streaming strings that are larger than memory using a small memory footprint… Each module provides an almost identical API…

    All of these types (Text and ByteString, strict and lazy) offer pack and unpack functions for converting from and to plain Strings. The types also come with specialized versions of familiar list functions like reverse, take, map and so on.

    14.1.1 Examples with Text

    -

    Let’s go through a short GHCi session demonstrating the use of Data.Text. As the documentation says, the Data.Text module is designed to be imported qualified. We can convert a String into a Text with the function T.pack. Note how a value of type Text gets printed just like a String.

    +

    Let’s go through a short GHCi session demonstrating the use of Data.Text. As the documentation says, the Data.Text module is designed to be imported qualified. We can convert a String into a Text with the function T.pack. Note how a value of type Text gets printed just like a String.

    Prelude> import qualified Data.Text as T
     Prelude T> :t T.pack
     T.pack :: String -> T.Text
    @@ -3769,7 +3769,7 @@ None of the above
     

    14.3 Writing a HTTP Server: WAI and Warp

    Sometimes it feels like everything in the world happens over HTTP and Web Apis. Your web browser, your smartphone apps, your bank, your coffee pot, and even your doorbell, all talk to servers using the HTTP protocol.

    -

    Let’s look at how we can set up a simple HTTP server in Haskell. The standard low level components for this are called WAI and Warp. WAI, the Web Application Interface gives us a way to define how HTTP requests are handled. Warp is a simple HTTP server that runs the logic we have defined using WAI. That probably sounds a bit abstract right now, but a simple example will help.

    +

    Let’s look at how we can set up a simple HTTP server in Haskell. The standard low level components for this are called WAI and Warp. WAI, the Web Application Interface gives us a way to define how HTTP requests are handled. Warp is a simple HTTP server that runs the logic we have defined using WAI. That probably sounds a bit abstract right now, but a simple example will help.

    The file exercises/Examples/HelloServer.hs implements a HTTP server that always responds with “Hello World!”. You can try it out by going to the exercises/Examples directory and running it with stack runhaskell HelloServer.hs. After that you can visit http://localhost:3421 in your browser to see the response from the server.

    module Examples.HelloServer where
     
    @@ -3788,12 +3788,12 @@ None of the above
     application :: Application
     application request respond =
       respond (responseLBS status200 [] (BL.pack "Hello World!"))
    -

    Let’s look at the types in this example. There’s a lot going on here. First off, Application is a type alias for a thing that implements the logic of a web server. The run function from Warp can run Applications:

    +

    Let’s look at the types in this example. There’s a lot going on here. First off, Application is a type alias for a thing that implements the logic of a web server. The run function from Warp can run Applications:

    run :: Port -> Application -> IO ()
     type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived

    We’ll talk about what Request and Response are soon, but from this type we can see that an Application is an IO operation that takes as arguments a request of type Request, and an IO operation respond :: Response -> IO ResponseReceived. Arguments like respond are called callbacks in many contexts. They allow us to call back to the library who called the application. The Application operation has to produce the same special ResponseReceived type that respond. You can think of this type as a token proving that respond was called by the Application.

    That might sound intimidating: but looking at the code things are relatively simple: our server is an Application and takes two parameters: request and respond.

    -

    WAI uses lots of types like Port, Request, Response, Status to represent HTTP concepts. It’s useful to look these up in the documentation when you bump into them. For example Port is just an alias for Int. As another example, we can see the responseLBS function has the type

    +

    WAI uses lots of types like Port, Request, Response, Status to represent HTTP concepts. It’s useful to look these up in the documentation when you bump into them. For example Port is just an alias for Int. As another example, we can see the responseLBS function has the type

    responseLBS :: Status -> ResponseHeaders -> ByteString -> Response

    where Status is defined in Network.HTTP.Types.Status, ResponseHeaders is a type alias for [Header] from Network.HTTP.Types.Header, ByteString is a lazy ByteString, and the result type Response is defined by Network.WAI.

    Finally, note that we take a shortcut and use the function Data.ByteString.Lazy.Char8.pack to convert a String into a ByteString. This only works for ASCII text.

    @@ -3814,7 +3814,7 @@ None of the above

    After implementing a HTTP server we can participate in the global graph of applications talking to each other that’s called the internet. But what use is talking if we can’t remember? A real application needs to be able to persist data even when it is restarted. A common way to accomplish this is to use a database.

    There are many different kinds of databases, but arguably the most widely used simple database is SQLite. SQLite is a library that lets you store data in a file and process it using SQL, the Structured Query Language. With SQLite there is no need to run a separate database server a la PostgreSQL or MySQL.

    In case you’re not familiar with SQL, don’t worry, you won’t need to write any queries of your own in the exercises. If you’d like to learn a bit of SQL now, there are lots of tutorials on the web. See W3Schools, SQL Zoo or Codecademy.

    -

    There are many libraries for Haskell for using SQLite, but we’ll look at one called sqlite-simple here. Let’s explore the library in GHCi for a bit.

    +

    There are many libraries for Haskell for using SQLite, but we’ll look at one called sqlite-simple here. Let’s explore the library in GHCi for a bit.

    All the functions live inside Database.SQLite.Simple. You can open a database by giving a filename to open, which is an IO operation that produces a Connection.

    Prelude> import Database.SQLite.Simple
     Prelude Database.SQLite.Simple> :t open
    @@ -3841,7 +3841,7 @@ None of the above
           prevents the constraint ‘(FromRow r0)’ from being solved.
           Probable fix: use a type annotation to specify what ‘r0’ should be.
           -- rest of error omitted
    -

    Before we go on, let’s have a closer look at FromRow. If you’ve bumped into SQL before, you know that an SQL query returns a number of rows, and each row consists of a number of values (also called columns). To be able to interpret the result of an SQL query into Haskell data, we need a way to interpret these values and rows. Thus sqlite-simple defines two classes, FromField and FromRow, and a bunch of instances like the following. (You can find these instances from the docs or by asking GHCi with :info FromRow etc.)

    +

    Before we go on, let’s have a closer look at FromRow. If you’ve bumped into SQL before, you know that an SQL query returns a number of rows, and each row consists of a number of values (also called columns). To be able to interpret the result of an SQL query into Haskell data, we need a way to interpret these values and rows. Thus sqlite-simple defines two classes, FromField and FromRow, and a bunch of instances like the following. (You can find these instances from the docs or by asking GHCi with :info FromRow etc.)

    instance FromField Int
     instance FromField Bool
     instance FromField String
    @@ -4344,7 +4344,7 @@ TODO explain `some` and `many`
     

    There are multiple reasons for learning Applicatives, even if they do not provide any additional power over Monads. First off, as discussed in Lecture 13, the GHC standard library nowadays makes Applicative instances for all Monads compulsory. Thus a working Haskell programmer is bound to see lots of Applicative instances.

    Secondly, you’ll bump into Applicative operators a lot even in Monadic code. Expressions like f <$> x <*> y <*> z can be useful in many Monadic contexts. Also, since the Traversable type class is built in terms of Applicative, you’ll often use applicative operations with it.

    Thirdly, Applicatives are an excellent exercise in understanding functional design patterns. They marry the Functor pattern with the Monoid pattern, and Alternative brings in yet another Monoid-like dimension. Being able to efficiently work with Applicatives will make working with further abstractions like monad transformers or lenses easier.

    -

    Lastly, there are a couple of types that are Applicatives but not Monads. Validation is one example, and a very practical one at that. Without understanding of Applicative, we’d have no way to recognize and generalize operations over types like this. Another such type is ZipList.

    +

    Lastly, there are a couple of types that are Applicatives but not Monads. Validation is one example, and a very practical one at that. Without understanding of Applicative, we’d have no way to recognize and generalize operations over types like this. Another such type is ZipList.

    15.8.2 Applicatives in the Wild

    @@ -4573,7 +4573,7 @@ TODO explain `some` and `many` propLastFixed (NonEmpty xs) = last xs === head (reverse xs)
    *Examples.QuickCheck> quickCheck propLastFixed
     +++ OK, passed 100 tests.
    -

    There are other modifiers like this, for example Positive for positive numbers, NonNegative for non-negative numbers, or SortedList for a sorted list. Here’s an example of a more complex test. We check that the nth element of cycle xs is correct. Both of the modifiers are needed, since !! doesn’t work with negative inputs, and cycle [] is an error.

    +

    There are other modifiers like this, for example Positive for positive numbers, NonNegative for non-negative numbers, or SortedList for a sorted list. Here’s an example of a more complex test. We check that the nth element of cycle xs is correct. Both of the modifiers are needed, since !! doesn’t work with negative inputs, and cycle [] is an error.

    propCycle :: NonEmptyList Int -> NonNegative Int -> Property
     propCycle (NonEmpty xs) (NonNegative n) =
       cycle xs !! n === xs !! (mod n (length xs))
    @@ -4797,7 +4797,7 @@ TODO explain `some` and `many` Prelude> map fib [29,29,29,29,29] [832040,832040,832040,832040,832040] (7.54 secs, 2,440,860,632 bytes)
    -

    Now let’s bring in the module Control.Parallel.Strategies that defines ways to evaluate values in parallel. We’ll use the parList rseq strategy to evaluate all the elements of the list to WHNF, in parallel.

    +

    Now let’s bring in the module Control.Parallel.Strategies that defines ways to evaluate values in parallel. We’ll use the parList rseq strategy to evaluate all the elements of the list to WHNF, in parallel.

    Prelude> import Control.Parallel.Strategies
     Prelude Control.Parallel.Strategies> withStrategy (parList rseq) (map fib [29,29,29,29,29])
     [832040,832040,832040,832040,832040]
    @@ -4822,7 +4822,7 @@ TODO explain `some` and `many`
     
    Prelude Control.Concurrent> concurrency
     AABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABB

    The operation forkIO :: IO () -> IO ThreadId takes an IO operation and starts running it in the backgroud. It produces a ThreadId that can be used to e.g. terminate the thread.

    -

    If we want to add actual communication between threads, we can use abstractions like MVar (a mutable thread-safe variable) or Chan (a queue).

    +

    If we want to add actual communication between threads, we can use abstractions like MVar (a mutable thread-safe variable) or Chan (a queue).

    Here’s a simple example where one thread writes a value to an MVar and another one waits for them and prints them. An MVar works like a mailbox: it is either empty or full. Calling takeMVar on an empty box waits for the box to get filled (with a putMVar). Symmetrically, trying to putMVar into a full box waits until the box is empty.

    takeMVar :: MVar a -> IO a
     putMVar :: MVar a -> a -> IO ()
    @@ -4882,7 +4882,7 @@ AABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABB
     
     
  • Tooling