├── .gitignore ├── EvaluatorExamples.hs ├── README.md ├── Repl.hs └── build /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.hi 3 | repl 4 | -------------------------------------------------------------------------------- /EvaluatorExamples.hs: -------------------------------------------------------------------------------- 1 | module EvaluatorExamples where 2 | 3 | import Data.Char 4 | import Data.List 5 | 6 | -- Some simple evaluator examples: 7 | 8 | 9 | -- Capitalizes each word in the given input 10 | capitalizer :: String -> String 11 | capitalizer = 12 | unwords . map capitalize . words 13 | where capitalize (h:t) = toUpper h : (toLower <$> t) 14 | capitalize _ = [] 15 | 16 | 17 | -- Does really really simple calculations 18 | simpleCalc :: String -> String 19 | simpleCalc expr = 20 | case words expr of 21 | [x, "+", y] -> show $ read x + read y 22 | [x, "-", y] -> show $ read x - read y 23 | [x, "*", y] -> show $ read x * read y 24 | [x, "/", y] -> show $ read x / read y 25 | _ -> "That's too hard! :(" 26 | 27 | 28 | -- Finds emojis related to the given input 29 | emojiFinder :: String -> String 30 | emojiFinder expr 31 | | foundIn ["smile", "face"] = "😀 😃 😄 😁 😆 😅 😂" 32 | | foundIn ["car", "transport"] = "🚗 🚕 🚙 🏎 🚓 🚑" 33 | | foundIn ["cat", "meow", "purr"] = "😺 😸 😻 😼 🙀" 34 | | foundIn ["ball", "sport"] = "⚽️ 🏀 🏈 ⚾️ 🎾 🎱" 35 | | foundIn ["food"] = "🍏 🍎 🍐 🍊 🍋 🍌 🍉 🍇 🍓 🍒 🍑" 36 | | otherwise = "👾 ⁉️" 37 | where foundIn = not . null . intersect keywords 38 | keywords = words expr 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-repl-in-haskell 2 | > How to make your very own REPL in Haskell 3 | 4 | This is a step-by-step tutorial on how to create a very simple **REPL** in Haskell. The goal of this walkthrough is to provide a basic understanding of REPLs and to show how easy and straightforward it can be to build one. We will create our REPL by individually constructing its four main components, and then composing them together. 5 | 6 | Prior knowledge or experience with Haskell is not required to grasp the basic principles of this walkthrough, 7 | although some basic understanding of functional programming will probably make it a lot easier to understand 8 | the example code. 9 | 10 | This tutorial has also been published as a [blog post on open.bekk.no](http://open.bekk.no/creating-a-repl-in-haskell). 11 | 12 | ## What's a REPL 13 | REPL stands for Read-Eval-Print-Loop, and as the name implies, its job is to: 14 | 1. **Read** the given input, and parse it into a data structure that can be evaluated. 15 | 2. **Evaluate** the parsed expression, based on a set of rules. 16 | 3. **Print** the result of the evaluated expression. 17 | 4. **Loop** (repeat) until signaled to stop. 18 | 19 | REPLs are most commonly associated with programming languages, where they are used as an interactive 20 | shell for interpreting the code written in that language. Most languages often come with such REPLs already bundled 21 | into their toolkit (e.g. Ruby's [IRB](https://en.wikipedia.org/wiki/Interactive_Ruby_Shell), or Haskell's [GHCi](https://wiki.haskell.org/GHC/GHCi)). 22 | These days, there's also an ever-growing number of online REPLs that support several different languages, 23 | such as [repl.it](https://repl.it/site/about). 24 | 25 | In these cases, the **read** and **evaluate** parts are focused around interpreting the input 26 | based on the language providing the REPL. However, a REPL doesn't necessarily have 27 | to be connected to a fully fleshed out programming language (or any language at all). Although that is the 28 | most common use case, one can argue that a REPL can pretty much be anything you want it to be, as long as 29 | it can **read**, **evaluate** and **print** whatever you throw at it in a repeating **loop**. 30 | 31 | So by that definition, let's get started on creating our very own, very simple, REPL! 32 | 33 | 34 | ## How to make a REPL 35 | As stated earlier, a REPL consists of four steps. We will start by implementing each of these steps as separate functions and finally compose them together to create our REPL. 36 | 37 | ### Read 38 | The first step is reading and parsing the input. Since we want to keep things simple, we'll ignore the parsing bit and stick to just reading the input as a regular string: 39 | ```Haskell 40 | read' :: IO String 41 | read' = getLine 42 | ``` 43 | For this, all we need is the `getLine` function. This will read the input and return it as an `IO String`. The `IO` wrapper is there to indicate that the string was produced by an IO action (i.e. input/output), which in this case is some input provided by the user. 44 | 45 | Note that the *tick* in the name of our `read'` function has no special purpose other than to differentiate it from Haskell's built-in `read` function. 46 | 47 | Let's go ahead and add a few more lines here for convenience: 48 | ```Haskell 49 | read' :: IO String 50 | read' = putStr "REPL> " 51 | >> hFlush stdout 52 |     >> getLine 53 | ``` 54 | We've added two additional actions here: `putStr "REPL> "` simply prints **REPL>** at the start of the prompt, and `hFlush stdout` is to immediately flush the stream just to make sure that nothing is stuck in the buffers. Finally we combine all three IO actions together with the `>>` operator (which you can read as *and-then*). 55 | 56 | ### Eval 57 | The next step is to evaluate the input that we have read. The easiest implementation would be to just skip this part entirely, and let the function return its given input. So let's do that for now: 58 | ```Haskell 59 | eval' :: String -> String 60 | eval' input = input 61 | ``` 62 | 63 | ### Print 64 | No need for anything fancy here. The `putStrLn` will print the given string to the console: 65 | ```Haskell 66 | print' :: String -> IO () 67 | print' = putStrLn 68 | ``` 69 | The `IO ()` type in the signature indicates that we are not returning anything, but still performing an IO action (i.e printing to the console). 70 | 71 | ### Loop 72 | The last step is to create a repeating loop around our three previous steps: 73 | ```Haskell 74 | if (input == ":quit") 75 | then return () 76 | else print' (eval' input) >> loop' 77 | ``` 78 | Here, we simply check if the input equals `":quit"`, in which case we exit our REPL. Otherwise, we evaluate the input, 79 | print the result and restart the loop. 80 | 81 | ### Putting it all together 82 | 83 | Finally, let's go ahead and put everything together inside our `main` entry point: 84 | ```Haskell 85 | main :: IO () 86 | main = do 87 | input <- read' 88 | 89 | unless (input == ":quit") 90 | $ print' (eval' input) >> main 91 | ``` 92 | Firstly, the input string is extracted from the `IO String` value coming from our `read'` function, using the `<-` operator. It's then passed on to the looping logic we defined earlier. 93 | 94 | The `unless` function here works exactly like our `if`/`else` logic in the previous code. It will exit the program if `input == ":quit"`. The `$` is the *apply operator*, which is used here to avoid wrapping the right side in extra parentheses. 95 | 96 | So there we have it! A very simple, albeit rather useless REPL. You can find the complete code example in the [Repl.hs](Repl.hs) file. 97 | 98 | 99 | ## Running the REPL 100 | Compile the code by running `ghc -o repl Repl.hs`. Then start, and test out the REPL by running `./repl`. It should look something like this: 101 | ```bash 102 | $ ghc -o repl Repl.hs 103 | [2 of 2] Compiling Main ( Repl.hs, Repl.o ) 104 | Linking repl ... 105 | 106 | $ ./repl 107 | REPL> Hello REPL 108 | Hello REPL 109 | REPL> 1,2,3,🍌 110 | 1,2,3,🍌 111 | REPL> :quit 112 | $ 113 | ``` 114 | Due to the simple implementation of the `eval'` function, the REPL just repeats whatever is typed. 115 | 116 | The basic framework is however in place, and the type of evaluator that we place inside it will more or less determine the main purpose of the REPL. 117 | 118 | ## Playing around with evaluators 119 | There are a few examples of evaluators in the [EvaluatorExamples.hs](EvaluatorExamples.hs) file. You can try them out by calling them from the `eval'` function: 120 | ```Haskell 121 | eval' :: String -> String 122 | eval' input = simpleCalc input 123 | ``` 124 | This one turns our REPL into a very simple calculator. As we can see, the number of possibilities are endless when it comes to the types of REPLs you can create by simple changing the evaluator. 125 | 126 | Going back to the main use cases of REPLs, you can even add in the evaluator of your own programming language here and create an interactive shell for your language. For that you might also want a custom parser in the `read'` step though, so that the evaluator can work on a well defined data structure rather than a string. 127 | 128 | ## Additional functionality 129 | The fully fleshed out REPLs that come bundled with programming language toolkits, usually have a considerable list of additional features and extra functionality. Even though these will not be covered in this tutorial, most of them are quite simple to implement and can easily be composed into this REPL just like what we did with the four initial steps. A few examples are: 130 | - See a history of inputs and outputs. 131 | - Set variables that can be accessed and used in later commands to the REPL. 132 | - Special commands for debugging and error handling. 133 | 134 | Take a moment to consider how these features could be incorporated into out current REPL. 135 | 136 | ## Final thoughts 137 | There are many different tools and libraries out there for creating really powerful REPLs with lots of different features. One such example being [parsec](https://hackage.haskell.org/package/parsec), a library containing a handful of useful functionality for reading and parsing input effectively. 138 | 139 | The main purpose of this tutorial is however to provide a basic understanding of how REPLs work under the hood, and to show how simple it is to create and assemble the four main building blocks that constitute a Read-eval-print-loop. 140 | -------------------------------------------------------------------------------- /Repl.hs: -------------------------------------------------------------------------------- 1 | import Control.Monad (unless) 2 | import EvaluatorExamples 3 | import System.IO 4 | 5 | main :: IO () 6 | main = do 7 | input <- read' 8 | 9 | unless (input == ":quit") 10 | $ print' (eval' input) 11 | >> main 12 | 13 | 14 | read' :: IO String 15 | read' = putStr "REPL> " 16 | >> hFlush stdout 17 | >> getLine 18 | 19 | 20 | eval' :: String -> String 21 | eval' input = 22 | input 23 | -- Try the following ones from `EvaluatorExamples.hs`: 24 | -- capitalizer input 25 | -- simpleCalc input 26 | -- emojiFinder input 27 | 28 | 29 | print' :: String -> IO () 30 | print' = putStrLn 31 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Build 4 | # 5 | # Running `./build` will build the REPL executable, 6 | # which you can then access by running `./repl` 7 | # 8 | 9 | ghc -o repl Repl.hs 10 | --------------------------------------------------------------------------------