Chapter 3: Strings

3.1 Printing strings

In this chapter we’ll learn about the String type, use a few list processing functions, go over some basic syntax, and interact with the repl.

3.2 A first look at types

Strings are represented as lists of UTF-16 characters. They are enclosed in double quotes. Single quotes are reserved for characters.

When you query the type of a string in the repl, its composite nature becomes apparent:

Prelude> :type "Hello!"
"Hello!" :: [Char]

Sometimes you may see String in type signatures. This is really a type alias for [Char].

3.4 Top-level versus local definitions

Top-level declarations are declarations that are visible anywhere within a module. In other words, they aren’t nested within other functions.

You can use let, and lambdas to create definitions local to an expression.

The where keyword can be used to create definitions that are local to a declaration.

3.4.1 Exercises: Scope

  1. These lines of code are from a REPL session

    Prelude> x = 5
    Prelude> y = 7
    Prelude> z = x * y
    

    Is y in scope for z?

    • Yes, and here’s proof:

      ·∾ x = 5
      ·∾ y = 7
      ·∾ z = x * y
      ·∾ z
      35
      
  2. These lines of code are from a REPL session:

    Prelude> f = 3
    Prelude> g = 6 * f + h
    

    Is h in scope for g? Go with your gut here.

    • No, we haven’t defined h anywhere that I can see. Proof:

      ·∾ f = 3
      ·∾ g = 6 * f + h
      <interactive>:7:13: error: Variable not in scope: h
      
  3. This code sample is from a source file:

    area d = pi * (r * r)
    r = d / 2
    

    Is everything we need to execute area in scope?

    • Well, no. Since d isn’t defined, there should be some sort of error:

      ·∾ :l area.hs
      [1 of 1] Compiling Main             ( area.hs, interpreted )
      
      area.hs:2:5: error: Variable not in scope: d
        |
      2 | r = d / 2
        |     ^
      Failed, no modules loaded.
      
  4. This code is also from a source file:

    area d = pi * (r * r)
      where r = d / 2
    

    Now are r and d in scope for area?

    • In this case, yes. This is because r is now local to the area functions scope, the parameter d is visible to it:

      ·∾ :l tst.hs
      [1 of 1] Compiling Main             ( tst.hs, interpreted )
      Ok, one module loaded.
      ·∾ area 88
      6082.12337734984
      

3.5 Types of concatenation functions

  • (++) append to a string or list

  • concat concatenate a list of lists or strings

3.7 More list functions

  • (:) prepend to a list or string

  • head take the first element

  • tail take the last element

  • take take n elements

  • drop remove n elements from the beginning

  • (!!) get an element at an index

3.8 Chapter Exercises

3.8.1 Reading syntax

  1. For the following lines of code, read the syntax carefully and decide if they are written correctly. Test them in your REPL after you’ve decided to check your work. Correct as many as you can.

    1. Original: concat [[1,2,3],[4,5,6]],

      Prediction: This will work as intended.

      Result:

      ·∾ concat [[1,2,3],[4,5,6]]
      [1,2,3,4,5,6]
      
    2. Original: ++ [1,2,3] [4,5,6]

      Prediction: Since ++ needs parenthesis in order to be used prefix, this will fail.

      Result:

      ·∾ ++ [1,2,3] [4,5,6]
      <interactive>:2:1: error: parse error on input ‘++’
      
    3. Original: (++) "hello" " world"

      Prediction: This should output "hello world".

      Result:

      ·∾ (++) "hello" " world"
      "hello world"
      
    4. Original: ["hello" ++ " world]

      Prediction: Because this is missing a closing double quote, it will fail with a parse error.

      Result:

      ·∾ ["hello" ++ " world]
      <interactive>:4:21: error:
          lexical error in string/character literal at end of input
      
    5. Original: 4 !! "hello"

      Prediction: Since the arguments are in the wrong order, it will result in a type error.

      Result:

      ·∾ 4 !! "hello"
      <interactive>:6:1: error:
          • Non type-variable argument in the constraint: Num [a]
            (Use FlexibleContexts to permit this)
          • When checking the inferred type
              it :: forall a. Num [a] => a
      
    6. Original: (!!) "hello" 4

      Prediction: This should return 'o'.

      Result:

      ·∾ (!!) "hello" 4
      'o'
      
    7. Original: take "4 lovely"

      Prediction: take is missing an argument. 4 should be outside the quotes. This will result in some kind of error.

      Result:

      ·∾ take "4 lovely"
      <interactive>:8:6: error:
          • No instance for (Data.String.IsString Int)
              arising from the literal ‘"4 lovely"’
          • In the first argument of ‘take’, namely ‘"4 lovely"’
            In the expression: take "4 lovely"
            In an equation for ‘it’: it = take "4 lovely"
      
    8. Original: take 3 "awesome"

      Prediction: "awe"

      Result:

      ·∾ take 3 "awesome"
      "awe"
      
  2. Read the code and figure out which results came from which lines of code. Be sure to test them in the REPL.

    1. concat [[1 * 6], [2 * 6], [3 * 6]] –> d) [6,12,18]:

      ·∾ concat [[1*6],[2*6],[3*6]]
      [6,12,18]
      
    2. "rain" ++ drop 2 "elbow" –> c) "rainbow":

      ·∾ "rain" ++ drop 2 "elbow"
      "rainbow"
      
    3. 10 * head [1, 2, 3] –> e) 10:

      ·∾ 10 * head [1,2,3]
      10
      
    4. (take 3 "Julie") ++ (tail "yes") –> a) "Jules":

      ·∾ (take 3 "Julie") ++ (tail "yes")
      "Jules"
      
    5. concat [tail [1, 2, 3], tail [4, 5, 6], tail [7, 8, 9]] –> b) [2,3,5,6,8,9]

      ·∾ :{
       ⋮ concat [ tail [1,2,3]
       ⋮        , tail [4,5,6]
       ⋮        , tail [7,8,9]
       ⋮        ]
       ⋮ :}
      [2,3,5,6,8,9]
      

3.8.2 Building functions

  1. Write functions in your repl to transform these inputs to their corresponding outputs using only the list processing features mentioned in the chapter [#1]. Don’t write a general purpose function – these only need to work for the inputs provided.

    1. Given "Curry is awesome" return "Curry is awesome!"

    2. Given "Curry is awesome!" return "y"

    3. Given "Curry is awesome!" return "awesome!"

  2. Now take each of the above and rewrite it in a source file as a general function that can take different string inputs.

    a x = x ++ "!"
    b x = if x == "Curry is awesome!" then "y" else "n"
    c x = drop 9 x
  3. Write a function of type String -> Char that returns the third character in a String.

    thirdLetter x = x !! 2
  4. This should return the character from "Curry is awesome!" at the requested position (index + 1).

    
    letterIndex x = "Curry is awesome!" !! (x-1)
    
  5. Write a function called rvrs that takes the string "Curry is awesome" and returns "awesome is Curry". You’re expected only to slice and dice this particular string with take and drop, not write a general purpose function.

  6. Put rvrs in a module and run it.

Footnotes