Learn Functional Reactive Programming On Your Arduino

Everyone loves learning a new programming language, right? Well, even if you don’t like it, you should do it anyway, because thinking about problems from different perspectives is great for the imagination.

Juniper is a functional reactive programming language for the Arduino platform. What that means is that you’ll be writing your code using anonymous functions, map/fold operations, recursion, and signals. It’s like taking the event-driven style that you should be programming in one step further; you write a=b+3 and when b changes, the compiler takes care of changing a automatically for you. (That’s the “reactive” part.)

functionalIf you’re used to the first-do-this-then-do-that style of Arduino (and most C/C++) programming, this is going to be mind expanding. But we do notice that a lot of microcontroller code looks for changes in the environment, and then acts (more or less asynchronously) on that data. At that level of abstraction, something like Juniper looks like a good fit.

Changing up the programming paradigm for Arduino is an ambitious project, especially considering that it was started by two undergraduates [Caleb Helbling] and [Louis Ades] as a senior design project. It’s also brand new, so there’s not much of a codebase out there yet. Time, and your participation, will tell if it’s useful. But one thing’s for sure, once you’ve programmed in a reactive language, you’re not going to be able to look at a delay loop the same again.

What’s the wierdest language you’ve ever programmed a microcontroller in?

(The XKCD comic’s alt-text reads “Functional programming combines the flexibility and power of abstract mathematics with the intuitive clarity of abstract mathematics”.)

49 thoughts on “Learn Functional Reactive Programming On Your Arduino

  1. I’m not sure how well functional programming will work in the constrained performance environment of an AVR. With C, I can look at a block of code and make a good guess as to how many cycles and how much memory it’ll use. I don’t know if that’s true in a functional language.

      1. It’s more than that, if you think about it. OK, so the performance might be limited. Whatever. But the bigger question is *how exactly does it get implemented*?

        C exists because, in general, it maps “pretty well” to the basic operation of most computers. Not perfectly (no concept of carry/zero/overflow flags, which just about every computer has) but pretty well. You’ve got jumps and branches, often times you also have computed branches (which are the equivalent of switch statements), and calls and returns. You have temporary storage (registers), and permanent storage in an addressable array. Pretty basic.

        Functional programming *does not* map to the basic operation of computers at all. It maps better to how we *want* them to work (if this happens, do this, if that happens, do that) but they don’t *actually* work like that.

        So, for instance, in their Blink example, the “Io:toggle” function gets called every time the “timerSig” event gets emitted. Great. Fantastic. How does that get implemented? Is it in an interrupt? Is there a chance Io:toggle might miss a timerSig event (e.g. is it queued, or is there a flag)? How many functions can be tagged to a timerSig event? What’s the scaling in terms of resource cost?

        And really, there’s *no information* on how it gets implemented at all. Which, really, is very frustrating, because without knowing the environment’s limitations, it’s hard to imagine working within them.

        1. The documentation for the Io:toggle function is here:
          http://www.juniper-lang.org/docs/files/Io-jun.html#Io.toggle

          If you really want to get down and dirty you can view the source code of the standard library directly:
          https://github.com/calebh/Juniper/blob/master/Juniper/junstd/Io.jun#L82

          As you can see, the Io:toggle function pattern matches on input (which has type pinState). When the input is the low value constructor, the high value constructor is used to make the return value, and when the value constructor is high, the low value constructor is used to make the return value.
          http://www.juniper-lang.org/docs/files/Io-jun.html#Io.pinState
          https://github.com/calebh/Juniper/blob/master/Juniper/junstd/Io.jun#L34

          As for how Juniper works:
          Signals are secretly maybe values. The definition of a signal is very simple and can be found in the Prelude:
          http://www.juniper-lang.org/docs/files/Prelude-jun.html#Prelude.sig
          https://github.com/calebh/Juniper/blob/master/Juniper/junstd/Prelude.jun#L112

          All signal processing functions are just normal functions lifted into the Maybe monad! You can see this in action at
          https://github.com/calebh/Juniper/blob/master/Juniper/junstd/Signal.jun#L25

          So signals are actually ordinary values, just like integers or any other types.
          Juniper does not use interrupts anywhere at this point (getting the compiler to work was hard enough). Everything uses a polling based model.

          In some other FRP languages you might see an explicit separation of a language describing the signal graph or a signal graph might be constructed as a in-memory directed graph. This is not the case in Juniper – since signals are simply maybe values, this means that there is no signal graph constructed at any stage. Instead, the information about the signal graph is encoded in the call graph!

          We haven’t had a whole lot of time to work on documentation. Currently I am spending my time writing a paper on the language and haven’t had time to work on another tutorial.

          -Caleb

          1. Yeah, I’m a bit confused on the whole “no interrupt” thing. I can buy that for the Time portion, although it looks like, from the Time code, that if the system is busy, and the timer isn’t polled fast enough it won’t be regular (since it updates lastPulse to t, not to an increment of the current time).

            I’ll be honest, I’m always confused as to people who pay attention to *language* semantics more than *how the language gets implemented*. It doesn’t matter how powerful the semantics are if the implementation doesn’t work well.

            You’ve said before that it compiles to actual C++ code, right? It’d go a long way to helping people understand how the language works if you’d just post the compiled C++ code alongside the examples.

  2. One of main selling points of the Arduino environment is the plethora of libraries. Looking at the Juniper documentation, I couldn’t figure out if you could access the usual libraries, e.g. I2C, SPI, etc. I realize this is project done by two undergrads (nicely done BTW), but this is of limited utility if the only interfacing with the outside world is purely through bit twiddling.

    1. The nice thing about the AVR is that all of that is accomplished by reading/writing a limited number of registers. However, rewriting all of the nice wrappers that the Arduino folks have assembled strikes me as an unpleasant task.

      1. I dunno about that.. in most cases the arduino implementation is vastly suboptimal due to questionable ease of use choices. The nice thing about AVR is that all the chips have tons of commonality… so even if you wrote some assembly for a teensy it wouldn’t be too hard to port it to a larger one if you needed to include more features. So, while it’s true that it would be a decent amount of work… it would be fairly rewarding work for many people.

        Not that I write any assembly… just C and I’m particularly fond of my 350~ byte quadrature encoder for ATtiny13A I’d be curious if anyone could do it in less but wouldn’t be too surprised.

  3. Isn’t that just a software equivalent of Interrupt-On-Change routine mixed with finite state machine? It can be done with a switch-case statement in main loop and functions that actually do stuff called from it, with added functionality of no overhead…

  4. I don’t think we need YAPL (yet another programming language). Good enough is good enough. As someone who has programmed in Forth and has been trying to understand how C libraries interface to Lua, I nearly always come back to the well know languages for text based programming. I do think there is room for new programming paradigms but it is difficult to balance easy initial coding, understandability (being able to read the code and understand what it does), ease of debugging and efficiency. The tried and trusted languages have a pretty good balance.

      1. He didn’t quite say that. And while I hesitate recommending Forth in the 21st century, looking at the ‘blink’ example, I’d rather program that in Forth.

        Assembler, Forth (and to a lesser degree) C, break down in large projects or large (for Forth and Assembler larger than one or two really) teams. Since there won’t be large projects on Arduino, that’s not a problem in this space.

        Functional programming languages have their space and use, but I’m not convinced that 8 bit microcontrollers is where it’s at.

  5. Hey, y’all know about PLCs, right? Ladder logic? It’s mostly just a clunky (being aimed at electrical people, not software people) way of expressing outputs as functions of inputs.

    If you want to get an automation system certified as being safe, you pretty much have to use those functional kinds of technologies. No one trusts imperative code.

    1. The problem with ladder logic is it has limited scalability… you’d have to be a complete madman to design the code we write at my job in ladder. That said… safety critical things (like emergency stops) are often done in ladder just because it’s what the electricians understand.

      So in essence you end up with a PLC with control logic written in structured text or what have you and all the fault handling logic in ladder. So you get to have your complex logic while offloading any needed “trust” to the ladder logic.

  6. Just read through the intro and don’t see anything that cannot be done in modern C++ and the code would look just about the same. Modern C++ has lambdas which seems the only trick they are playing in this code. I’m willing to be enlightened.

      1. I visited a couple web sites on algebraic data types. They didn’t help. Didn’t follow upon the pattern matching. But I’ll be surprised if this is something really new that I haven’t seen before in all my decades. LOL

        1. You might be surprised then, and learning something new about languages can help you even in your preferred language. Pattern matching in particular is a very nice concept/experience. Perhaps you could learn Rust or SML to get your feet wet?

          1. Pattern matching isn’t new. I can go back to SNOBOL for it. That’s text matching but the concept is the same. For that matter C++ signature matching is the same concept.

            I suspected templates provide the algebraic data types but wasn’t 100% sure. Your other comment indicates they do.

      2. Actually, the C++ template language has that and more. In C++ you even have matching on type-integers, in the template language, and type specialization. I bet Juniper doesn’t let you do those. (C++’s handling of union types is lackluster, I admit)

        1. In functional languages there are a couple different ways to handle ad hoc polymorphism. The first is via modules (SML and OCaml) and the second is type classes (Haskell). After the more advanced Hindley-Milner type system is implemented in Juniper I will take a look at implementing type classes.

  7. To mime the GOML dude @bl from earlier…

    Whether polled or interrupt driven, aren’t we still logically discussing just another way of doing:

    If then

    Just because you have fancy types, pattern matches, etc…isn’t it just another form of abstraction but just a little further removed from machine code? You just aren’t referencing/checking the condition in the code, though polling something isn’t far removed IMHO.

    Like @W though, I’m willing to be enlightened as well, but haven’t yet seen reasons to move away from procedural for most things.

  8. How are you supposed to debug a problem with the data-flow timing through a graph when sub paths are different lengths and the signals linked to an eternal interrupt are not synchronised such that a=b+c but the states of b and c each have a time line (obviously) but due to the complexity required to calculate c compared to b they get out of sync so that a = b.time(x) + c.time(x+d) where d is the difference between the execution times for computing b and c when you really wanted a = b.time(x+d) + c.time(x+d) and even then what if b is rapidly changing, faster than c can be computed.

  9. Sounds to me like they are trying to make a MCU behave like an FPGA. It seems like it would be more efficient to map this to Verilog or VHDL and use small FPGAs like the ICE40 series.

  10. I managed to get Erlang going on a Sharp Zaurus a long time ago. It ran, sort of, but I had to remove a lot of the libraries to fit it into the memory. There is no reason why functional languages should not run on low resource systems and Erlang being soft realtime could be quite useful. The biggest issue would be memory management since badly written applications can take up a lot or memory in recursion, so a simple OS may crash and burn

  11. I really, really like the idea of this language. The idea seems a perfect fit for the Arduino platform at AVR related work. I could see this language being exceptionally useful for small IO work. Not to replace tried and true languages, but as an alternative for appropriate situations. I am also impressed by the makers, great work, from new developers taking on ambitious projects. I realize that you can probably do everything the language does in other mature languages (as pointed out by nearly every comment), but Juniper is not replacing the work done in huge software companies. I feel irritated by the overall negative tone and lack of support in the comments, although I may be reading into skepticism too much. Well done Caleb Helbling and Louis Ades!
    -Cheers!

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.