A pinch of magic🧙♂️
Pragmatic Maciej
Posted on July 3, 2020
The word "function" in programming has two meanings. In one meaning function is just a sub-program, procedure, some part of the code with a name. In second meaning it is mathematical function, so-called "pure" function, which takes an argument and return some value. The first kind of functions can "do" real things, the second can't.
There is a type of programming paradigm which uses only pure functions, it is called functional programming. But how it is possible that program made by useless functions which really cannot do nothing works? There needs to be something involved there! And yes it is! Lets name it for now - a pinch of magic.
What does this React component?
React is declarative library for bulding user interfaces. Read more
function header(text) {
return <h1 class="header">{text}</h1>
}
Can we say our header
function does something? No, it does nothing. In isolation this function has no impact, it returns some React objects which we make by JSX syntax. Don't believe me? Run this function outside React, it will only return data, no DOM will be manipulated. They say React is declarative, and yes this function is declarative, for the same input it will return the same output, it doesn't produce any effect.
Some magic then happens and our h1
is rendered in the browser. The magic here is - React. React runtime takes our declarative composition of functions/components and gives them a meaning, it renders declared elements in the page!
Note. In contrary to React, we commonly say that jQuery is imperative library. And it is, as every jQuery selector and mutation is relying on the current state of the DOM.
What does this Fluture function?
Fluture is JS library for declarative async operations. Read more
const getPackageName = file => (
node (done => { readFile (file, 'utf8', done) })
.pipe (chain (encase (JSON.parse)))
.pipe (map (x => x.name))
)
Function getPackageName
has inside imperative readFile
call. But still it is doing nothing. The thing which this function does is just combining some data. It is a pure function, it defines our async operation. You can think about that as declarative Promise.
How then, we can make this function to do something? Fluture gives us a tool for execution of such a function. Our magic wand in this case has a name fork
.
getPackageName ('package.json')
.pipe (fork (console.error) (console.log))
What does this Haskell function?
Haskell is purely functional programming language. Read more
readInDir :: String -> String -> IO String
readInDir dir file = readFile (dir ++ "/" ++ file)
Pay attention what this function is returning. It is returning data of the type IO String
. That means that our readInDir
declares some effect, it return information about what effect should be executed by the wizard. In isolation it is just a function returning data, readFile
even though it looks like it does something, it does nothing. It returns a message, command describing what we want to do. Maybe surprisingly, but this function has more in common with function which adds numbers, than with reading the file.
Who is the wizard then? Haskell runtime is a wizard. It takes the effect definitions and execute them! And yes Haskell has side effects, but they are in control of the runtime, not in the hands of the programmer, programmer can only define the effect description in the returned data. Haskell runtime gives pure functions a purpose.
Note very important to mention is that part of the code where developer use IO in Haskell can be considered as imperative code. The main difference is that we have explicit IO type tracking and no side-effects but wanted effects.
What does this Elm function?
Elm is a delightful language for reliable webapps. Read more
createUser : State -> ( State, Cmd Msg )
createUser state =
( { state | save = RemoteData.Loading }
, sendPostRequest user
)
Again let's put attention into what this function returns. We have a pair, first item represents a state, and second represents a command. The function createUser
returns the new state and the command. Again both things are only data, no effects are done. In Elm, we describe what our function does by commands which are represented by Cmd
type. In Elm magic is executed by Elm runtime, which takes our new state and apply view function with a new state, it also takes a command description and apply the effect.
The effect system
All examples I have shown can be grouped as so-called effect systems. We have some framework, library, language which allows for declarative writing of the code, and takes away from the programmer the effect execution part. It is not in responsibility of the programmer to call the effect directly, programmer responsibility is about declaring the effect, and describing it by the data structures deliberately picked for this purpose. In Elm example it was Cmd, in Haskell it was IO, in React it was React element who defines the DOM.
Note. Elm also has equivalent for React element. In Elm we compose Html type elements in order to modify the DOM.
Note. Effect system term is not strictly related to pure functional code.
Effects to the border
The idea is highly related with common concept - push effects to the border. The concept defines programming the code without effects, and having effects in some specific part of the code. Examples which I have shown are exactly implementation of this concept, but they highly differs on the place of the implementation. In Elm and Haskell effect system is deeply integrated with the runtime, one write declaration which is interpreted by the compiler and executed by the runtime. In React its library which makes the effect, so the language doesn't support our effect-less style of programming. That said, keeping all rules of effect system at the library level needs more discipline, as compiler doesn't support our moves.
The simple example of using the effect system wrongly is doing direct ajax call, or DOM manipulation inside React component. You can do that, React will not block such movement, but the code can have unpredictable results.
The problem with React is that React introduce the runtime, but has no compiler which can verify correctness. The same shot in the foot is not possible in for example pure language like Elm which has both - compiler and the runtime.
Note React team was/is researching compiler possibility for React. But I am not aware about the current state of affairs.
Note Svelte is a JS framework which introduce both runtime and the compiler. But compiler is not focuses on the code correctness.
The benefit
As always there is a trade-off, but what is the purpose of such declarative code is less complexity of the code, less bugs, removing the time factor. Code is more predictable and better maintainable as there is no relation with a database, a file-system, some third party server or the document object model. Code manipulation is simpler as code parts don't include environment relationship, in result they can be moved freely.
Note. Additionally declarative static typed languages, like Haskell and Elm allow for tracking effects by explicit type definitions of functions producing them.
Making such magic
Yes. We will write in the next article abstraction which makes the declarative code do things. See you there!
If you like this article and want read more from me, follow me on dev.to and twitter.
Posted on July 3, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.