=Paper= {{Paper |id=Vol-2203/18 |storemode=property |title=Approaching Side-effects in Pure Functional Programming by Interpreting Data Structures as Recipes |pdfUrl=https://ceur-ws.org/Vol-2203/18.pdf |volume=Vol-2203 |authors=Michal Strba,Richard Ostertag |dblpUrl=https://dblp.org/rec/conf/itat/StrbaO18 }} ==Approaching Side-effects in Pure Functional Programming by Interpreting Data Structures as Recipes== https://ceur-ws.org/Vol-2203/18.pdf
S. Krajči (ed.): ITAT 2018 Proceedings, pp. 18–27
CEUR Workshop Proceedings Vol. 2203, ISSN 1613-0073, c 2018 Michal Štrba and Richard Ostertág



       Approaching side-effects in pure functional programming by interpreting data
                                    structures as recipes

                                                      Michal Štrba, Richard Ostertág

                  Faculty of Mathematics, Physics and Informatics, Comenius University, Mlynská dolina, Bratislava, Slovakia
                                            faiface@ksp.sk, ostertag@dcs.fmph.uniba.sk

      Abstract: We present a new approach to side-effects in             stand. The description is very dense, full comprehension
      pure functional programming. The approach is not novel             is not required.
      in every aspect but rather in combining of multiple already
      known concepts into a coherent method.
                                                                         1.1 Names and tokens
         The key concept in this approach is to view some data
      structures as describing a recipe of what should be done.          Tokens in Funky are generally separated by whitespace,
      A program expressed in form of this data structure is then         except for these special characters which are always
      passed to another program called ’interpreter’. An inter-          parsed as separate tokens, whether separated by whites-
      preter takes the recipe, examines it, and actually executes        pace or not:
      all the desired side-effects.
         Keeping simplicity, clarity, and avoiding introduction of       ( ) [ ] { } , ; \ #
      any unnecessary theory, the recipe data structures tend to
                                                                            Aside from these, all tokens are separated by whites-
      roughly conform to the CPS (continuation passing style)
                                                                         pace. Consequentially, identifiers may contain all kinds
      model and tend to contain function values inside of them.
                                                                         of symbols. For example, these are all valid identifiers:
      This means, that expressing them in code involves a lot of
                                                                         fold>, fold<, empty?, skip-whitespace. Dashes (-)
      anonymous functions and large “continuations” as argu-
                                                                         are used to separate words in function names instead of un-
      ments to other functions. Attempting to write such code in
                                                                         derscores or camel-case. However, type names start with
      any of the traditional functional languages, most notably
                                                                         upper-case letters and use camel-case.
      Haskell, results in a very messy, unpleasant, and hard to
                                                                            Tokens starting with a digit or a +/- sign followed by a
      read code. We also think that it’s the main reason why
                                                                         digit are numbers (valid or invalid). Any series of charac-
      techniques described in this paper weren’t developed ear-
                                                                         ters enclosed in single quotes is a character literal (valid or
      lier in these languages.
                                                                         invalid) and similarly, double quotes enclose a string literal
         Therefore, we created a new language that makes such
                                                                         (again, valid or invalid). Escaping in characters/strings
      code beautiful and pleasant to read. The subtle features of
                                                                         works as expected.
      the language that make this new approach to side-effects
      viable have far reaching implications, mostly by making it
      possible to write purely functional code that reads top to         1.2 Functions
      bottom.
                                                                         A function definition is signified by the func keyword,
         In this paper, we describe the Funky programming lan-
                                                                         which is followed by a function name, a colon, the type of
      guage, how it facilitates writing vertical code, how we can
                                                                         the function, equals sign and finally the function body –
      write interpreters for recipe data structures, and how we
                                                                         an expression. Function definitions cannot be nested and
      can use the language to transform and combine the recipes
                                                                         expressions are only allowed as function bodies, no ex-
      in purely functional code and thereby attain great expres-
                                                                         pressions outside functions are meaningful. For example:
      sivity in writing side-effecting programs.
                                                                         func sqrt : Float -> Float = \x x ^ 0.5
      1    Short introduction to Funky
                                                                         func max : Int -> Int -> Int =
                                                                             \x \y if (x >= y) x y
      Funky is a simple language with a small set of orthogo-
      nal features that combine very well. The language was              func flip : (a -> b -> c) -> b -> a -> c =
      designed and implemented by us and was the topic of our                \f \x \y
      bachelor’s thesis. This paper is a shortened version of that           f y x
      thesis. The parts left out in this paper are mainly the thor-
      ough description of the language. This shouldn’t cause                As we can see, function argument names are not spec-
      any trouble to people generally familiar with functional           ified before the equals sign, instead, all arguments are in-
      programming, for whom this paper is intended anyway.               troduced by abstractions. Funky is very consistent with
         Now we’ll briefly describe the important aspects of the         this – literally all variables in expressions are introduced
      language so that the following sections are easy to under-         by abstractions, there isn’t a single case otherwise.
Approaching side-effects in pure functional programming by interpreting data structures as recipes                                         19

         Abstractions are very concise – they consist only of a               In the case of need or a desire for clarification, any ex-
      backslash followed by the name of the bound variable fol-             pression can be type-annotated with a colon:
      lowed by the body of the abstraction (function). Multi-
      ple arguments are introduced simply by nesting abstrac-               ((x : Int) + (y : Int) : Int)
      tions. The body of an abstraction always spans until the
      end of the scope (for example, inside parentheses it spans            1.3 Records
      until the closing parenthesis), which prevents unnecessary
      parentheses in many cases.                                            Records are one of the three means of creating own types
         Function names consisting solely of special characters             in Funky (the other two are unions and aliases). Records
      (no letters or numbers) are infix functions. All infix func-          are similar to structs from C or records from Pascal. They
      tions have the same precedence, which is lower than the               are compound types, a single value containing multiple
      precedence of regular prefix function application and are             fields.
      all right-associative. This is to free the programmer from               A record definition is signified by the record keyword,
      the burden of manually specifying the precedence and as-              which is followed by the record name, a list of type vari-
      sociativity of infix functions. Right-associativity was cho-          ables required by the record (if any), and an equals sign
      sen because it’s more often useful than left-associativity.           followed by a comma-separated list of fields. All fields
         The type system is very similar to the one in Haskell.             must be type annotated.
      The main difference is that Funky has no type-classes and
                                                                            record Pair a b = first : a, second : b
      disallows higher-kinded types (type variables with argu-
      ments), which shall not be confused with higher-order                 record Person =
      types that are supported in Funky.                                        name : String,
         Quite unusually and very importantly, Funky supports                   age : Int, # trailing comma is allowed
      function overloading – defining multiple functions with
      the same name, but different types. For example:                      record Vec4D =
                                                                                x : Float,
      # map applies a function to all elements of                               y : Float,
      # a list                                                                  z : Float,
      func map : (a -> b) -> List a -> List b =                                 w : Float,
          \f
          fold< ((::) . f) [] # :: is cons                                     Funky generates a few functions per record: the con-
                                                                            structor and a getter and an updater for each field. For
      # map applies a function to the potential                             example, in case of the Person record, these functions get
      # content of a maybe                                                  generated:
      func map : (a -> b) -> Maybe a -> Maybe b =
          \f \maybe                                                         func Person : String -> Int -> Person
          switch maybe
          case nothing nothing                                              func name : Person -> String
          case just \x just (f x)                                           func name : (String -> String) -> Person ->
                                                                                Person
      # useless converts a list of maybe floats to
      # a list of maybe ints                                                func age : Person -> Int
      # ^ that's a useless comment                                          func age : (Int -> Int) -> Person -> Person
      func useless : List (Maybe Float) ->
      List (Maybe Int) =                                                       The constructor takes the values of the record fields in
          map (map int)                                                     order and returns an instance of the record. A getter sim-
                                                                            ply takes a record value and returns the given field. An
         This comes very handy in many situations – program-                updater is more peculiar. It takes a function mapping the
      mer doesn’t have to think about names too much and sim-               record field to a new value and a record value. Then it re-
      ilar behavior on different types may be assigned the same             turns a copy of the original record where the given field is
      function name. Also, as we’ll see, records are thereby al-            replaced by the result of applying the function to its orig-
      lowed to share field names, which is something that causes            inal value. This approach to updaters was chosen for two
      a lot of trouble in Haskell.                                          reasons: first is that this is usually what we want to do:
         Function overloading is not arbitrary – overloaded func-           to update a field according to its previous value; the sec-
      tions with colliding types are disallowed. Colliding types            ond reason is that updaters compose very well in this form
      are such types that can be specialized (their type variables          (they were inspired by Lenses from Haskell).
      can be substituted) into the same type. This is because it               For example, let’s work with these two records:
      would be impossible to determine, or even easily express,
      which of the colliding functions should be used in many               record Point = x : Int, y : Int
      situations.
20                                                                                                     Michal Štrba and Richard Ostertág

     record Segment =                                                func length : List a -> Int =
         start : Point,                                                  \list
         end   : Point,                                                  switch list
                                                                         case empty
       Say we have a variable seg which is a segment. We can                 0
     compose getters to access the X coordinate of the starting          case (::) \x \xs
     point:                                                                  1 + length xs

     (x . start) seg                                                   Each case is followed by the name of the alternative,
                                                                     which is followed by a function that accepts the argu-
        But we can also compose updaters to change the value         ments of the alternative and returns the final result of the
     of that coordinate and get an updated segment:                  switch/case structure. The arguments in the case body
                                                                     don’t have to be mentioned explicitly – the above function
     (start . x) (+ 4) seg                                           could’ve been written like this:

        To replace the value of a field with a value independent     func length : List a -> Int =
     of the previous value of the field, we use the const func-          \list
     tion (const x takes one argument and always returns x):             switch list
                                                                         case empty 0
     (start . x) (const 0) seg                                           case (::) const ((1 +) . length)

       Let’s define one more record:
                                                                     1.5 Aliases
     record Plan = segments : List Segment
                                                                     The simplest way of making type names is by aliasing.
                                                                     Alias simply defines another name for an existing type,
        The map function can also be used as an updater on a
                                                                     albeit a possibly more complex one. For example, the
     list, and so we can write this to update all Y coordinates of
                                                                     String type from the standard library is defined like this:
     all the end points of the segments in a plan:
                                                                     alias String = List Char
     (segments . map . end . y) (* 2) plan
                                                                       The alias is perfectly identical to the type on right side
       The whole main motivation for the creation of the huge
                                                                     of the equals side. They may be used interchangeably.
     and abstract Lens library in Haskell is avoided in Funky
                                                                     Aliases may also have type variables.
     simply by providing clever updater functions.


     1.4   Unions                                                    2 Vertical code

     Unions are just like data types in Haskell. Their definition    No feature is useful unless the benefits of its employment
     is signified by the union keyword followed by the name of       outweigh the costs – the feature must be viable. The new
     the union and a list of type variables, then an equals sign     approach to side-effects described in this paper is viable in
     followed by a | separated list of alternative forms. For        Funky, but not in Haskell, or any other traditional purely
     example (:: is cons, the list prepend function):                functional language. What makes the difference? Surpris-
                                                                     ingly, only two subtle syntactic differences, nothing major
     union Bool = true | false                                       at the first sight. Yet, these two syntactic differences make
                                                                     code that is otherwise ugly and messy in Haskell beauti-
     union Maybe a = nothing | just a
                                                                     ful and readable in Funky. Of course, some code that is
                                                                     beautiful and readable in Haskell is in its original form not
     union List a = empty | a :: List a
                                                                     possible in Funky at all. But, we’re not talking about just
        Funky generates a single constructor function for each       any code here: we’re talking about the kind of code that
     alternative, which takes all the arguments in the order and     makes the new approach to side-effects viable and that is
     returns an instance of the union. For example, these func-      – vertical code.
     tions get generated for the List union:                            Writing code that reads top to bottom and composes ver-
                                                                     tically has always been the domain of imperative program-
     func empty : List a                                             ming. “Do this, then do that, then repeat this until some
     func (::) : a -> List a -> List a                               other thing happens” is a very natural way of expressing
                                                                     programs. Programmer reads the code top to bottom, re-
       To get values out of a union value, Funky introduces a        membering the invariants as they occur, until they form a
     special switch/case structure that looks like this:             complete picture of the program in their mind.
Approaching side-effects in pure functional programming by interpreting data structures as recipes                                             21

         Functional programs tend to compose differently. In-               func n! : Int -> Int =
      stead of a series of statements, functional programs are                  \n
      mere expressions constructed mostly from function appli-                  if (n <= 0) 1 (n * n! (n - 1))
      cation and abstraction. An expression has no natural “or-
      der of execution”, it’s usually a function application whose             This particular code isn’t too bad, but it’s easy to con-
      arguments are yet other expressions. The arguments can                ceive situations where using the if as a simple, one-line
      be understood in any order. However, if the arguments are             conditional expression would be outright unacceptable.
      large expressions containing more function applications                  Funky introduces a special syntactic concept: the semi-
      with still more large expressions as their arguments, the             colon. All it does is it puts everything that’s after it (in the
      whole thing becomes very inconvenient to read and un-                 scope) inside parentheses. It works just like the $ function
      derstand. The reason is that humans operate with only a               in Haskell. So we can rewrite this:
      finite (and fairly small) amount of working memory. Un-
                                                                            if (n <= 0) 1 (n * n! (n - 1))
      derstanding an expression that is syntactically a wide and
      deep tree requires a lot of working cognitive memory, so
                                                                            into this:
      it’s hard.
         Of course, designers of functional languages are well              if (n <= 1) 1; n * n! (n - 1)
      aware of this. Many syntactic features present in those lan-
      guages are specially designed to tackle this issue. These             and then split it into multiple lines:
      features include: pattern matching, guards, let bindings,
      where bindings, or Haskell’s do notation. However, none               if (n <= 1)
      of these features solves the problem fully, because they                  1;
      don’t nest very well.                                                 n * n! (n - 1)
         The motivation for designing Funky arose from the har-
      mony experienced while playing with the pure λ -calculus                 A more involved example is the infamous FizzBuzz
      and from the frustration with existing functional lan-                problem. Here’s a function that returns the correct output
      guages. Experimenting with the pure λ -calculus gave us               for each number:
      the opportunity to step back – see the bigger picture. We
      saw that instead of adding new syntactic features like the            func fizzbuzz : Int -> String =
                                                                                \number
      ones described above, all that was needed was to improve
                                                                                if ((number % 15) == 0)
      the core – make writing ordinary expressions more concise                     "fizzbuzz";
      and make it possible to naturally split them into multiple                if ((number % 3) == 0)
      lines. It turned out that this was enough. Funky has no                       "fizz";
      pattern matching, guards, let binding syntax, nor anything                if ((number % 5) == 0)
      analogous to the Haskell’s do notation.                                       "buzz";
         The two subtle syntactic features that make it possible                string number
      are: concise trailing lambdas and the semicolon. We’ll
      demonstrate them on concrete examples: the if and the                    Here, if is used to express a series of cases terminated
      let function in Funky.                                                by a catch-all case. The built-in if structure in Haskell is
                                                                            not suitable for such purposes – one would have to use
                                                                            guards instead. But as we’ve already said, guards don’t
      2.1   if and let                                                      nest so well.
      In Funky, if is a simple function from the standard library              In contrary, Funky’s if function nests perfectly. Both
      (body omitted):                                                       “then” and “else” expressions may be arbitrarily large,
                                                                            nested, vertical expressions.
      func if : Bool -> a -> a -> a
                                                                            if condition (
         It takes a condition and two arguments of the same type:               then
      then and else, and returns back the correct one. It can be                ...
      used as a simple conditional expression, just like the if             );
      structure in Haskell, or the ternary operator in C:                   else
                                                                            ...
      func min : Int -> Int -> Int =
          \x \y                                                                Similarly to if, let bindings (variable assignments) are
          if (x < y) x y                                                    not a built-in language construct in Funky. Instead, let is
                                                                            a function from the standard library. Here’s its full defini-
        But if the expressions are more complex, this becomes               tion (including the body):
      hard to read. Let’s take the factorial function as an exam-
      ple (we’ll call it n! because Funky allows this):                     func let : a -> (a -> b) -> b = \x \f f x
22                                                                                                     Michal Štrba and Richard Ostertág

        The function let takes a value and then takes a function        The last argument to a semicolon kind vertical function
     to which it passes the value as an argument. Thanks to          can be usually understood as a continuation, analogous to
     the Funky’s concise function construction (lambda/back-         CPS (continuation passing style).
     slash), this function is a perfectly viable replacement for a      The if function is an example of a semicolon kind ver-
     let binding construct built directly into the language.         tical function.
                                                                        In some cases, the above form is violated and the return
     let "me stay by your"                                           type doesn’t match the last argument, while the function
     (\side reverse side ++ " " ++ side)                             is still used vertically. However, the signature usually fol-
                                                                     lows the mentioned form.
        Here we call the let function with two arguments: the
                                                                        The second kind we call trailing lambda kind vertical
     first one is the string "me stay by your" and the second
                                                                     functions. Their signatures have this general form:
     one is a function. Calling let passes the string as the ar-
     gument to the function and the whole expression evaluates
                                                                     ... -> (... -> V ) -> V
     to "ruoy yb yats em me stay by your".
        Thanks to the Funky’s trailing lambda syntax (function          The vertical function passes some arguments to the con-
     body spans until the end of scope), we can remove the           tinuation, which then takes on the same role as with the
     parentheses around the function:                                semicolon kind vertical functions. Again, some vertical
     let "me stay by your"                                           functions may violate this form, but those cases are rare.
     \side reverse side ++ " " ++ side                                  The let function is an example of a trailing lambda
                                                                     kind vertical function.
       Furthermore, we can split the expression into two lines,
     yielding a very readable piece of code:
                                                                     2.3 Viability
     let "me stay by your" \side
     reverse side ++ " " ++ side                                     The main reason why vertical functions along with their
                                                                     usage haven’t seen the light of the day in traditional func-
        Now, instead of thinking in terms of function applica-       tional languages, such as Haskell is that they’re hardly vi-
     tions and abstractions, we understand the code as an as-        able there. The reasons are very subtle. After all, Haskell
     signment to a variable (immutable, of course) followed by       supports anonymous functions and has the $ function anal-
     the resulting expression.                                       ogous to the Funky’s semicolon.
        To assign multiple variables, we can just stack let as-         To demonstrate this, let’s take a function for generating
     signments:                                                      all permutations of a list (the code contains some functions
                                                                     we haven’t discussed, and won’t discuss, in this paper, but
     let (filter prime? (count 2)) \primes                           the understanding of what the function does is irrelevant
     let (take-while (< 100) primes) \small-primes                   now):
     let (length small-primes)       \count
     string count ++ " small primes (< 100)"                         func permutations : List a -> List (List a) =
                                                                         \list
       This expression evaluates to: "25 small primes (<                 if (empty? list)
     100)".                                                                  (yield []; empty);
                                                                         pick (permutations (rest list)) \tail
                                                                         pick (insert (first list) tail) \perm
     2.2 Vertical functions                                              yield perm;
                                                                         empty
     Some function are suitable for composing code vertically,
     either with the help of the semicolon or trailing lambdas.
                                                                        In Haskell, the above code could be rewritten roughly
     We’ve seen both in the previous section on the if and the
                                                                     like this:
     let function. Other functions aren’t suitable for that. Now
     we’ll describe a small theory about these functions that al-    permutations list =
     low vertical composition. We call them vertical functions.       if null list
        There are two main kinds of vertical functions: those          then yield [] $ empty
     that utilize the semicolon and those that involve trailing        else
     lambdas. Both are characterized by their signature (type).         pick (permutations (rest list)) (\tail ->
        The first kind we call semicolon kind vertical functions.       pick (insert (first list) tail) (\perm ->
     Their signatures have this general form (where V is an ar-         yield perm $
     bitrary type and ... is an arbitrary sequence of -> appli-         empty))
     cations):
                                                                       In case if was a function in Haskell, instead of a built-in
     ... -> V -> V                                                   construct, we can improve the code a bit:
Approaching side-effects in pure functional programming by interpreting data structures as recipes                                         23

      permutations list =                                                   given task. In this paper, we’ll examine the interpreter for
       if (null list)                                                       command-line applications.
         (yield [] $ empty) $
       pick (permutations (rest list)) (\tail ->
       pick (insert (first list) tail) (\perm ->                            3.1 Interpreters
       yield perm $                                                         Funky’s runtime is currently written in Go. As interpreters
       empty))
                                                                            need to interact with the runtime, they too must be writ-
         We could improve the code a little bit more by removing            ten in Go. This may be expanded to more languages in
      the parentheses around anonymous functions and inserting              the future, for example, it would be useful to write Funky
      a few more dollars:                                                   interpreters in Java or C++.
                                                                               To write an interpreter we need to import the
      permutations list =                                                   "github.com/faiface/funky" package and call the
       if (null list)                                                       funky.Run function. This function does all the job re-
         (yield [] $ empty) $                                               garding command-line flags, reading, parsing, and compil-
       pick (permutations (rest list)) $ \tail ->                           ing source files, and returns a runtime value representing
       pick (insert (first list) tail) $ \perm ->                           the data structure, the recipe, back to the programmer.
       yield perm $
       empty                                                                package main

         The result is still fairly bad, though. The dollar signs all       import "github.com/faiface/funky"
      over the place stick out too much and the arrow at the end
      of the argument list of an anonymous function is similarly            func main() {
      detrimental to the overall aesthetics. It’s no wonder that                program := funky.Run("main")
      vertical functions weren’t in fact invented in Haskell. Of            }
      course, one would use pattern matching or the do notation               The funky.Run function takes one argument: the
      to write a similar function in Haskell. However, neither              name of the function containing the recipe value.
      of those features nests very well. The approach taken by              The return type of the funky.Run function is
      Funky is more general.                                                *runtime.Value. The runtime package is located
                                                                            at "github.com/faiface/funky/runtime".         The
      3    Side-effects and interpreters                                    *runtime.Value type provides several methods we can
                                                                            use to interact with the value:
      Funky’s approach to side-effects is unique among func-                func (*runtime.Value) Char() rune
      tional languages, but after exploring it, this fact comes             func (*runtime.Value) Int() *big.Int
      rather surprising. The approach is so obvious that it’s quite         func (*runtime.Value) Float() float64
      curious no other language (to our knowledge) has adopted              func (*runtime.Value) Field(i int)
      it before.                                                                *runtime.Value
         The idea is this: a program in Funky is just a value, a            func (*runtime.Value) Alternative() int
      data structure. Then there is a special program called in-            func (*runtime.Value) Apply(
      terpreter. This program interacts with the Funky’s runtime                arg *runtime.Value) *runtime.Value
      (evaluator) to examine this data structure, which serves the
      role of a recipe. A recipe then tells the interpreter what to         // these three are implemented using the
                                                                            // above six
      do. Contrary to ordinary recipes, these recipes are gen-
                                                                            func (*runtime.Value) Bool() bool
      erated, combined, recursive, and so on, all the goods of              func (*runtime.Value) List() []*runtime.Value
      functional programming.                                               func (*runtime.Value) String() string
         The Funky programming language itself has no intrinsic
      concept of side-effects. In fact, the interpreters themselves            The Char, Int, and Float methods are used to retrieve
      add no real concept of side-effects either. Everything still          values of Funky’s built-in types. The Field function re-
      remains just a value. The recipe is a value that can be trans-        turns the i-th field of a record, or the i-th argument to a
      formed and manipulated just like any other value. This is             union constructor. The Alternative method returns the
      where a lot of expressive power comes in as we’ll see.                index of the union constructor of the value. And lastly, the
         There isn’t just one interpreter. In fact, any Funky pro-          Apply function takes another runtime value and applies it
      grammer can make their own interpreters for their own                 to the function in the receiving runtime value and returns
      special purposes. Each interpreter works with a different             the result of this application.
      data structure describing the desired side-effects. One in-              All the above functions crash if they’re used on values
      terpreter is intended for command-line applications. An-              of wrong types. For example, calling Alternative on
      other one is for web servers. And yet another is for 2D               a record value crashes, and calling Char on a float value
      games. Each interprets a data structure specialized for the           likewise.
24                                                                                                    Michal Štrba and Richard Ostertág

        The last three functions are just for convenience because     For example, here’s a “cat” program, a program that
     booleans, lists, and strings are very widely used types.       simply copies the input to the output:
        The interpreter sometimes needs to fabricate new run-
                                                                    func main : IO =
     time values not originating in the Funky program. For
                                                                        getc \c
     example, when a command-line interpreter loads a char-             putc c;
     acter from the input, it needs to make a character value           main
     and pass it to the program. These functions are provided
     in the "github.com/faiface/funky/runtime" pack-                   This program has no done node, it’s an infinite data
     age for this purpose:                                          structure. Before we run it, though, we need an interpreter.
                                                                    Here it is (badly formatted, because the lack of horizontal
     func MkChar(c rune) *runtime.Value                             space):
     func MkInt(i *big.Int) *runtime.Value
     func MkFloat(f float64) *runtime.Value                         package main
     func MkRecord(fields ...*runtime.Value)
         *runtime.Value                                             import (
     func MkUnion(alt int, fields ...*runtime.Value)                    "bufio"
         *runtime.Value                                                 "io"
                                                                        "os"
     // these three are, again, implemented using                       "github.com/faiface/funky"
     // the above five                                                  "github.com/faiface/funky/runtime"
     func MkBool(b bool) *runtime.Value                             )
     func MkList(elems ...*runtime.Value)
         *runtime.Value                                             func main() {
     func MkString(s string) *runtime.Value                             program := funky.Run("main")
                                                                        in := bufio.NewReader(os.Stdin)
       Their meaning is clear, so we’ll avoid explaining that.          out := bufio.NewWriter(os.Stdout)
                                                                        defer out.Flush()
     3.2 Interactive command-line programs                          loop:
                                                                        for {
     The data structure serving the role of a recipe we chose for           switch program.Alternative() {
     simple interactive command-line programs is strikingly                 case 0: // done
     simple:                                                                    break loop
                                                                            case 1: // putc
     union IO =                                                                 out.WriteRune(program.Field(0).Char())
         done              |                                                    program = program.Field(1)
         putc Char IO      |                                                case 2: // getc
         getc (Char -> IO) |                                                    out.Flush()
                                                                                r, _, err := in.ReadRune()
        It is a kind of a linked list, with three types of nodes.               if err == io.EOF {
     One signals the end of the program: done. The next one                         break loop
     – putc – says that a character should be printed and the                   }
     program should continue in some way. The first argument                    program = program.Field(0).
     to putc is the character to be printed. The other argument                             Apply(runtime.MkChar(r))
     is the rest of the program – a continuation. The last node             }
     – getc – is requesting a character from the input. It has          }
     one argument: a function. The interpreter should read the      }
     character and pass it as an argument to this function. The        The interpreter enters a loop where it checks the pro-
     function then returns the rest of the program.                 gram node type and acts accordingly, always proceeding
        Funky always generates constructor functions for a          to the continuation until reaching the done node.
     union and these are the ones generated for IO (bodies omit-       Now we can run the “cat” program (user input is em-
     ted, internal to the compiler/runtime):                        phasized, funkycmd is the name of the interpreter):
     func done : IO                                                 $ funkycmd cat.fn stdlib/*.fn stdlib/
     func putc : Char -> IO -> IO                                       funkycmd/*.fn
     func getc : (Char -> IO) -> IO                                 hello, cat!
                                                                    hello, cat!
        The imporant thing to notice is that putc is a semicolon    do you cat?
     kind vertical function and getc is a trailing lambda kind      do you cat?
     vertical function. This makes it easy to compose interac-      you do cat!
     tive command-line programs in a natural, imperative-like       you do cat!
     style.                                                         ^D
Approaching side-effects in pure functional programming by interpreting data structures as recipes                                           25

         At the end, the user pressed the Ctrl+D combination to             This is a common pattern in Funky. It’s used to avoid cre-
      signal the end of file which caused the program to finish.            ating unnecessary helper functions in places where the re-
         We can’t do much with just done, putc, and getc, not               cursion needs to remember more arguments than the orig-
      at least conveniently. That’s why we’ll now show how to               inal function has. The recursion in our case has to remem-
      define more complex functions on top of the basic ones to             ber the accumulated string starting from an empty string.
      get a more powerful – and sometimes surprisingly power-               The |> function (x |> f = f x) passes the empty string
      ful – behavior.                                                       as the initial value to the recursion.
                                                                               With the help of print, println, and scanln, we can
                                                                            make more involved programs. Here’s a number guessing
      3.3   print, println, scanln                                          game:
      The first function with a more complex behavior that we’re
                                                                            func main : IO =
      going to tackle is print. The print function prints a
                                                                                println "Think a number from 1 and 100.";
      whole string instead of a single character as putc does                   100 |> 1 |> fix \loop \min \max
      (it doesn’t do it, but it instructs the interpreter to do it, and             let ((min + max) / 2) \mid
      similarly, print instructs the interpreter to print a string).                print (string mid ++ "? ");
                                                                                    scanln \response
      func print : String -> IO -> IO =                                             if (response == "less")
          \s \next                                                                      (loop min (mid - 1));
          if (empty? s)                                                             if (response == "more")
              next;                                                                     (loop (mid + 1) max);
          putc (first s);                                                           if (response == "yes")
          print (rest s);                                                               (println "Yay!"; done);
          next                                                                      println "Say one of less/more/yes.";
                                                                                    loop min max
        The print function takes a string and a continuation
      – the rest of the program. Then it recursively describes                 And here’s an example running of the program:
      how to print the string – print the first character and then
      continue printing the rest until we printed the whole string.         Think a number from 1 and 100.
      Alternatively, print can be defined with a right fold:                50? no
                                                                            Say one of less/more/yes.
      func print : String -> IO -> IO =                                     50? nope
          \s \next fold< putc next s                                        Say one of less/more/yes.
                                                                            50? less
        Now we can use print to create a convenience                        25? more
      println function, which additionally prints a newline at              37? more
      the end of the string:                                                43? less
                                                                            40? more
      func println : String -> IO -> IO =                                   41? more
          print . (++ "\n")                                                 42? yes
                                                                            Yay!
        The next function we’re going to define is scanln,
      which scans a whole line from the input (excluding the                3.4   ungetc, skip-whitespace, scan
      newline character) and passes its content. While print
      and println prepended some putc nodes to the contin-                  The print, println, and scanln functions only
      uation, scanln prepends some getc nodes, accumulates                  “prepend” operations to the continuation. But since IO is
      the line and passes it to the continuation, which accepts             a fully transparent data structure, we can define transfor-
      one argument: the line string. It’s a trailing lambda kind            mations that penetrate it, twist it around, or transform it in
      vertical function.                                                    any other way.
                                                                               The first and a very useful example of such a function
      func scanln : (String -> IO) -> IO =
                                                                            is ungetc. It is used to “push a character back on the in-
          \f
                                                                            put”, so that the next getc call will get it. The plain IO
          "" |> fix \loop \s
              getc \c                                                       data structure has no such functionality and we can’t get
              if (c == '\n')                                                any similar behavior just by sequencing done, putc, and
                  (f (reverse s));                                          getc. What we need is we need to write a function that
              loop (c :: s)                                                 takes a character and a continuation, then searches through
                                                                            the continuation until it finds the first getc node, and fi-
         The body makes use of the fix function (fix-point op-              nally passes the character to that node. Since IO is just a
      erator, fix f = f (fix f)) to insert inline recursion.                transparent data structure, this is quite easy:
26                                                                                                     Michal Štrba and Richard Ostertág

     func ungetc : Char -> IO -> IO =                                  It uses the skip-whitespace at the beginning, then it
         \c \io                                                      enters a loop where it accumulates the word until it reaches
         switch io                                                   a whitespace again. This whitespace wasn’t supposed to
         case done                                                   be read by scan, so it’s put back on the input by ungetc.
             done                                                      Here’s a simple calculator program demonstrating the
         case putc \d \jo
                                                                     scan function:
             putc d;
             ungetc c;                                               func main : IO =
             jo                                                          print "> ";
         case getc \f                                                    scan \x-str
             f c                                                         scan \op
                                                                         scan \y-str
        The ungetc function examines the top node of the con-            # float x-str returns Maybe Float
     tinuation and recursively propagates itself down the data           # hence the call to extract
     structure until it finds a getc node. When it does, it passes       let (extract (float x-str)) \x
     the character to the function of the getc node and turns it         let (extract (float y-str)) \y
     into its result.                                                    println (
        The ungetc function is particularly useful for imple-                if (op == "*") (string (x * y));
     menting the scan function. We’ve already implemented                    if (op == "/") (string (x / y));
                                                                             "invalid operation: " ++ op
     scanln, which scans whole lines. The scan function, on
                                                                         );
     the other hand, scans the next full word (a continuous se-          main
     quence of characters not containing any whitespace) on the
     input. To do that it first needs to skip all the whitespace       And here’s its running:
     preceding the word, then scan the word, but avoid scan-
     ning the first whitespace character after the word. We’ll       > 10 / 3
     see how ungetc comes to help with this.                         3.3333333333333335
        First, we’ll make a general function for skipping whites-    > ^D
     pace on the input:
                                                                     3.5   reverse-lines
     func whitespace? : Char -> Bool =
         \c                                                          The last example in our exploring of the possibilities of the
         any (c ==) [' ', '\t', '\n', '\r']                          IO data structure is a rather peculiar one: not practically
                                                                     useful, but quite showing of the potential present here.
     func skip-whitespace : IO -> IO =                                  The function is called reverse-lines and its job is
         \next                                                       to reverse all the lines on the output. Lines come to the
         getc \c                                                     output in two ways: either a sequence of putc nodes ter-
         if (whitespace? c) (
                                                                     minated by a putc of a newline, or a sequence of putc
             skip-whitespace;
             next                                                    nodes terminated by a getc node – all the pending output
         );                                                          must be shown to the user before requesting input and the
         ungetc c;                                                   user input will be entered by a newline.
         next
                                                                     func reverse-lines : IO -> IO =
                                                                         \io
        The skip-whitespace function continuously reads
                                                                         io |> "" |> fix \loop \s \(io : IO)
     characters from the input until it reaches a non-whitespace             switch io
     character. It wasn’t supposed to read this character, but it            case done
     needed to in order to determine whether to stop skipping                    done
     or not. So it uses ungetc to put it back on the input.                  case putc \c \jo
        With the help of skip-whitespace, here’s scan:                           if (c == '\n') (
                                                                                     println s;
     func scan : (String -> IO) -> IO =                                              loop "";
         \f                                                                          jo
         skip-whitespace;                                                        );
         "" |> fix \loop \s                                                      loop (c :: s);
             getc \c                                                             jo
             if (whitespace? c) (                                            case getc \f
                 ungetc c;                                                       print s;
                 f (reverse s)                                                   getc \c
             );                                                                  loop "";
             loop (c :: s)                                                       f c
Approaching side-effects in pure functional programming by interpreting data structures as recipes   27

         The reverse-lines function starts a loop using inline
      recursion with fix to accumulate the line to be reversed.
      It removes the original putc nodes from the data structure
      and keeps accumulating until reaching one of the above-
      mentioned conditions for terminating a line. When one
      of the conditions occurs, it transforms the accumulated re-
      versed line into a proper series of putc calls using print
      or println.
         Here’s the small program augmented by
      reverse-lines:

      func main : IO =
          reverse-lines;
          print " What's your name? ";
          scan \name
          println ("Hello, " ++ name ++ "!");
          done

         And here’s its running:

       ?eman ruoy s'tahW
      !lahciM ,olleH

         Unimportant to this paper, this reverse-lines func-
      tion isn’t perfect. For example, it fails to reverse the lines
      of the “cat” program, because that one has a getc before
      each putc and so no full line ever gets accumulated. The
      solution to this problem is left as an exercise to the reader.


      4 Conclusion

      Combining the concept of vertical code with the new ap-
      proach to side-effects in a language that makes it viable
      resulted in a whole new approach to structuring purely
      functional code. We saw that purely functional programs
      can be expressed in a way that is familiar to an imperative
      programmer. In Funky, this doesn’t come from artificially
      implanted syntactic features, but instead stems naturally
      from the core, general concepts in the language itself.
         At the moment, Funky is virtually unknown. We will
      make all the efforts to get the word out there, because we
      believe we’ve got something worthy in our hands.