Hacker News new | past | comments | ask | show | jobs | submit login
Arduino-copilot makes it easy to use Haskell to program an Arduino (joeyh.name)
75 points by dwb on Jan 25, 2020 | hide | past | favorite | 44 comments



Author here.. Just as a preview of the next release of this, I've been teaching it how different pins of the arduino can be used. Some pins support digital IO, some also PWM, some analog input pins also support digital IO, but others don't.

So I can write a program like this and pick pins to use essentially at random:

    import Copilot.Arduino.Uno
    
    main :: IO ()
    main = arduino $ do
        pwm pin2 =: longer_and_longer
        delay =: constant 10
And without even needing to load it into a board, let alone hook up wires, the compiler can tell me it won't work:

   demo2.hs:5:9: error:
    • This Pin does not support PWM
    • In the first argument of ‘(=:)’, namely ‘pwm pin2’
      In a stmt of a 'do' block: pwm pin2 =: longer_and_longer
This is implemented using type level lists and type level programming, and user-defined type error messages.


When I first met arduino (and quickly more embedded boards afterwards), the hard part was not C[++], nor a toolchain around it – I’m a C guy since ‘00. It was the fact that this area has few de-facto standards on how things should cooperate. Basic tutorials have no notion of event loops, you just for-loop forever and poll pins. But once I’ve got few non-standard devices, like that pressure sensor I got somewhere in the city, it all falls apart, because e.g. the time required to talk to it via provided library consumes enough cycles for your entire process to be late on physics. And vice versa, your logging is not async and interferes with signal timeouts. You simply spend tens-thousands of cycles spin-locking on i/o and there is no way to fix that without rewriting “drivers” under some “os”. Idk if haskelling everything could help with that.

I decided to just move on to high-mhz stm’s or even -pi variants, where a whole different stratum of software lives (seemingly), but luckily project folded for unrelated reasons. My conclusion was that if you’re a software developer, don’t just go embedded, it is not what you used to and routes you can take are limited.

Edit: obviously missing some details here, and it was 7 years ago, would like if someone would elaborate on the state of the embedded world today.


I'm not familiar with Haskell and functional programming in general, so I'm curious if these codes in the OP make sense to Haskell people? Every lines of the codes are full of side effects (LEDs, buttons, delay...) and completely procedural.


Pardon the off-topic question, but I noticed you used the plural "codes" instead of "code", which has typically been used in American English. Traditionally, "codes" has been used in other dialects of English, but I've noticed an increasing number of Americans using the plural form.

Is this a thing now? Are we saying "codes" instead of "code"?


Interesting question. I'm Korean and actually I was asking myself if "codes" is correct English while typing. I don't know, but except for grammar "code" seems to be countable to me.

Note that Korean grammar (and maybe some other languages) doesn't have strict rules about plural forms.

Anyway "Codes" make sense to me when they refer "snippets".

edit: added some more thoughts.


Yes, I think it's something some American developers have picked up from others using "global" English. I heard the plural used a lot from devs in and from the Indian subcontinent, and now, I'm starting to hear native American devs use the plural form.


Code as in 'sourcecode' is uncountable IMO.

Code as in 'password' or 'cypher' or 'set of symbols' is instead countable.


The code is mostly pure Haskell (the do block, which most Haskellers are familiar with), with a few domain-specific elements (those led and delay “assignments” and the Stream type in this functional reactive programming framework arduino-copilot, whose meaning is specific to this Arduino application and most Haskellers have to guess).

The short answer is that I could guess what it means.

This `arduino-copilot` example really showcases one killer application of Haskell: writing eDSL (embedded domain-specific language) in Haskell.

Haskell allows really high level abstraction: in particular, you can overload the operator which combines different statements in Haskell. In C/C++, the semicolon combines subsequent statements, Haskell even allows you to overload (give a new meaning to) the semicolon “operator” (think of overloading ; instead of overloading +). That is, in Haskell we can interpret differently how to combine subsequent statements in different contexts (such as null-checking, exception handling, async/await style concurrency, looping, or procedural state passing, etc., see the example below).

However, when we “overload the semicolon operator”, we intuitively have some expectations:

1. We expect to be able to turn a value into a standalone statement (to inject a value as a single statement with that value, e.g., to turn the value “97” into the statement “97;”, and this operation is called return)

2. We expect to be able to use the result of a statement in the next statement as in

    str <- getLine;
    putStrLn str
This could be rewritten using a bind operation (denoted >>=) as getLine >>= putStrLn.

When we package up (encapsulate) the above two operations (return and bind), we expect the two operations to play nicely with each other to satisfy some intuitive laws (e.g,. if you inject a value then use the value in a subsequent functional statement, it is the same as just calling the function on the value, so the return and bind cancels out, etc.). A mathematical formalization of this idea (the two operations with some intuitive laws) is named a monad, which happens to generalize many different computational contexts of combining statements such as null-checking, exception handling, async/await style concurrency, looping, or procedural state passing [1].

[1]: https://philipnilsson.github.io/Badness10k/posts/2017-05-07-...

The ability to reinterpret statement compositions is why Haskell is so great for embedding domain-specific languages (even procedural eDSL), as the arduino article shows, and this is why some people call Haskell the finest procedural language.


Trying to keep an open mind here about this - but what does Haskell offer on an _arduino_ aside from developer preference?

Surely C/C++ will be the better performer on such a tiny board... And usually you want to squeeze every drop of performance out of tiny boards


You are right C is a good choice for arduino, but if Python can be used with it, I believe Haskell can also be used in similar way.

It will bring many high level features like type safety, immutability, runtime error free code and code that is easy to refactor, once the Haskell learning curve is climbed. I like Haskell for its brevity and pseudo code like syntax which really runs like Python with all the necessary safety.

Given Haskell share in the overall eco-system of languages, I don’t expect it to have any significant impact. But this little steps are necessary to move forward Haskell.

I think the biggest stumbling block Haskell need to overcome is to have good documentation like Python. I believe if instead of just features, language research Haskell community needs to put extraordinary efforts in documentation. In spite of Haskell being an old language more than 50% of packages on hackage do not have proper documentation and unless that do not go up to 90% or more Haskell won’t be able to be used effectively by programmers to solve computing problems. We all stands on giants of shoulders and for that to happen need documentation. I believe people underestimated the power of good documentation. It should be twice or thrice more efforts than the language feature development itself.


My 2020 goal is to learn Haskell, so beginner here.

Is there a way other people can contribute to documentation ? Also how to generate documentation like found in Hoogle ?


Hm, I'm afraid to say that I can't find a good resource on how to write and build docs for Haskell libraries. Maybe that's a tutorial you could write once you've figured it out?! ;)

Here are a few links though:

* https://ndmitchell.com/downloads/slides-drive-by_haskell_con...

* https://haskell-haddock.readthedocs.io/en/latest/markup.html

* https://docs.haskellstack.org/en/stable/build_command/#synon...

Also, maybe join the #haskell-beginners channel on Slack: https://fpchat-invite.herokuapp.com/

There are other platforms too though, e.g. https://www.reddit.com/r/haskellquestions/, https://discourse.haskell.org/


The Haskell documentation generation tool is named haddock. It comes with GHC. I've never run it standalone, so I don't know how involved that is. But I frequently run it with the command "cabal haddock", which takes care of telling it where all your source files are and where to drop the output.


Thank for reply. I'll dig into it. Kind of documentation I am thinking about not only description of what method do, but also include example of 'how to use' it.


Doctests are often useful to briefly demonstrate how something works: http://hackage.haskell.org/package/doctest

They are a bit finicky to set up though.

Another good resource: http://yannesposito.com/Scratch/en/blog/Haskell-Tutorials--a...


I think the biggest stumbling block is going to be that most of my complexity is in wiring the Arduino to something else. That and you don't even start with a system that can log.

This is strictly for Arduino/embedded. For general purpose programming? I don't know.


> if Python can be used with it

Which Arduino models are people running Python on? Surely not the most common models?


No, the Uno has nowhere near enough RAM to be useful with Python. However, MicroPython runs beautifully on an ESP8266, which everyone should always use instead of an Uno, in my opinion.


I run Lua and compile C on my ESP8266's. I'm sure MicroPython could fit there as well.

But even MicroPython doesn't run on a Arduino Uno, much less Haskell.


Copilot is an EDSL that generates a C program, so the Haskell runtime is not in the final binary running on the Arduino.


I recently tried out uLisp which was trending on HN the other day on an Uno. It left me with ~500 bytes of memory and ~1000 bytes of storage. Unfortunately that’s not enough to do many interesting things!


Great points, thanks.


It's worth looking at the C code that's generated from a simple blink example in haskell. It's not very idiomatic, but neither is it very slow. Much of the fluff should get optimised away by the C compiler. I've been generally very impressed with how readable the output is, as generated code goes, and how performant it seems.

    #include <stdint.h>
    #include <stdbool.h>
    #include <string.h>
    static bool s0[(2)] = {(false), (true)};
    static size_t s0_idx = (0);
    bool s0_gen(void) {
      return (s0)[s0_idx];
    }
    bool delay_guard(void) {
      return true;
    }
    int16_t delay_arg0(void) {
      return 100;
    }
    bool digitalWrite_guard(void) {
      return true;
    }
    int16_t digitalWrite_arg0(void) {
      return 13;
    }
    bool digitalWrite_arg1(void) {
      return (s0)[s0_idx];
    }
    void step(void) {
      if ((delay_guard)()) {
        (delay)(((delay_arg0)()));
      };
      if ((digitalWrite_guard)()) {
        (digitalWrite)(((digitalWrite_arg0)()), ((digitalWrite_arg1)()));
      };
      ((s0)[s0_idx]) = ((s0_gen)());
      (s0_idx) = ((++(s0_idx)) % (2));
    }
    void setup()
    {
      pinMode(13, OUTPUT);
    }
    void loop()
    {
      step();
    }


Doing some constant folding, the C compiler would arrive at something like this:

    void loop(void) {
      delay(100);
      digitalWrite(13, s0[s0_idx]);
      s0[s0_idx] = s0[s0_idx];
      s0_idx = (++s0_idx) % 2;
    }
    void setup()
    {
      pinMode(13, OUTPUT);
    }
Only the s0[s0_idx] = s0[s0_idx] looks like extra work now. I don't know if a C compiler would optimise that away, but assuming it does not, one extra memory write per loop is not bad overhead.


My understanding is that the domain-specific language (which is embedded in Haskell) generates C code, which is then compiled to an arduino binary.

The key sentence:

> This whole example turns into just 63 lines of C code, which compiles to a 1248 byte binary, so there's plenty of room left for larger, more complex programs.

What the eDSL (embedded domain-specific language) offers is a higher level of abstraction: by using functional reactive programming, the programmer can reason about the code at a more intuitive level (instead of C, assembly, or machine code).

Usually people call this style of programming declarative instead of imperative: focusing on what to do instead of how to do it.

In a more interesting example in the article, we see right away that the LED is on when, and only when, the button is pressed or when it is “blinking”, with “a longer and longer” delay (with lower level details deferred to the counter function, or with the timing logic handled by the functional reactive programming framework—to appreciate the abstraction ability of FRP, try to imagine how to implement the same LED (which is on when pressed or with a longer and longer delay) in other procedural languages, likely different concerns will be interleaved).

When concerns are cleanly separated in Haskell, we understand what the code intends to do right away, making the code more maintainable. (And more fun to read and write!)

As a side bonus, Haskell has a strong and expressive type system, and a pervasive use of laziness to enable purity, allowing local equational reasoning (e.g., no need to worry about hidden mutable global state, so functions could be understood on their own, etc.), making the code even easier to reason about and to maintain then some other procedural languages (what you write is what you intuitively mean).

So I think the trade off is between developer productivity and performance (of the generated C code vs a handcrafted C code; very much like the trade off between a compiled machine code vs a handcrafted machine code).


What does C/C++ offer aside from developer preference, when compared to assembly?

Squeezing every drop out of microcontrollers likely includes much bit-banging. Once you've given up perfect instruction cycle counting, they're all high level languages!


It's not running Haskell on the Arduino, you are building a C program for Arduino using a DSL in Haskell.


The argument is still kind of the same. What benefits does this have (aside from developer preference) over using flat out C/C++? What is the performance like using the DSL in Haskell?


Not the OP but I suspect we’re not talking about any performance gains here, the arduino is still running binary. What you get by using a functional language like haskell is a way to structure your code differently and in many cases that could make your life a whole lot easier when you translate the solution that you have in your head to code. It is also easier to catch bugs


The advantage of CoPilot (the DSL this library is built upon) is that it builds verifiable constant-time constant-memory programs, which is quite important in some cases (like aerospace control programs). Whether that is an important property for Arduino is debatable of course.


Is developer preference not important?

What advantages does C have over processor specific assembly?


Developer preference is a very big deal. Almost all other factors are secondary.


If a developer wants to use a hammer for everything, because everything looks like nails to him, then I doubt in his professionalism. Sure, dev preferences matter, but only within the space dictated by the client requirements. Customers do not care if the thing they are using was created with C or Haskell. They care about price, performance and features. It might be harder to program in C++, but the final result can better fulfill the requirements.


Devs prefer one language over the other, because they consider it to be better for their use case - it is not because of religious reasons.

Clients typically don't have the technical expertise to dictate what technology the dev should use. If the client knows what technology they need, they should simply look for those devs who have expertise in that technology.

Half the languages that you see succeed today(the other half are those pushed by megacorps as the only developer platform), are because of dev preferences - not because of client requirements.


Yet they often choose wrong tool for the job. We often see games written in Java, big websites created in PHP, or processing huge amounts of data in Python. Here someone wants to do embedded in Haskell. These are not technical reasons. The reasons for these choices are frequently "this is the only tech stack I know" or "this is easier to learn".


Facebook has been written in php, and minecraft in Java - because that was the language that the developer preferred. Are you saying this was wrong? Both developers made billions of dollars from it.

If a client needs a particular technology then he should look for specialists in those technologies. Otherwise, just leave it to the developers preference.

And reasons such as 'this is the only tech stack I know' or 'this is easier to learn' are perfectly good reasons. Reasons for a lot of things are not technical. You think Mark Zuckerberg created Facebook for technical reasons? In fact familiarity with a tech stack and simplicity of learning are seen as key technical considerations when choosing a technology for development. You must be very immature to think these are not technical reasons.


They both made billions DESPITE their technology choice, and both systems had to undergo a serious rewrite later. There are dozens of other projects out there almost noone ever hears of because they turn out to be a disaster and are cancelled (I was consulting a few such cases which ended up in court, because developers were not able to deliver the product with the required level of capabilities and performance, despite using their preferred, but seriously suboptimal technology). Or they are even a business success but then their users are constantly cursing while using them. Users are often not the same people who buy software, so sometimes you can make business success despite technical inferiority of the product. Marketing often beats technical features.

> You must be very immature to think these are not technical reasons

Sorry, but this ends the discussion for me. I thought we were discussing on merits, not ad hominem arguments.


At some point you have to learn to take critical feedback about yourself and not shut yourself down or vehemently keep repeating the same thing using different words.

You are making immature statements like - Time to learn a technology and familiarity with a stack are not technical considerations. I expect a junior engineer to make such statements but not a senior engineer or a hiring manager. These are critical technical considerations when choosing a technology. Your statements show immaturity - period. If a director of engineering asks an engineering manager if he will consider the tech stack familiarity and learning curve for the team when choosing a tech stack and the manager responds in the negative, trust me - he will start looking for a new engineering manager. You are fixated on picking the "right technology" while your team loses momentum and attrition sets in. You sound like you think your technical decisions are unbiased, but Zuckerbergs decision to use PHP was biased. It is obvious why Zuckerberg used php. He was familiar with php and that was the fastest way for him to build a website. But according to you he made a terrible technical decision and he should have used something else. This is an immature statement. BTW, Facebook still runs on php.

A piece of advice, do not try to impose your biases - technical or otherwise on other engineers. They will do what they want to do. If you join a team and they do not share your technical biases - you have 2 options 1. Show them why the technical choices you propose will help those developers in the long run. 2. Join a different team which shares your bias

Badgering them with your opinions is not an option. Your options are - Show the developer why building Arduino copilot is not in his long term interests. The other option is - Galois is making tons of money by building copilot for safety critical embedded projects for NASA. May be you can also talk to NASA and get them to shut down the project. Otherwise you are just wasting your time and everyone else's time.


Performance may not matter that much, depending on the application. I've got some arduino capable boards monitoring temperatures once a minute, but I'm using lua because the documentation for my board was less handwavy for lua than for arduino (it's an esp8266 board; the c environment is a little weird).


If performance doesn't matter, then most likely this is a sign of using too powerful hardware for the job. This is ok in a hobby project, but if you wanted to produce and sell these on mass market, then your competitor would get a smaller board with a less powerful chip and win by the price (or earn more per unit sold).


True, though wifi and ethernet are quite demanding, so any uC capable of either will most likely be "too powerful" for things like doing simple I2C/SPI communication with a sensor or five and relaying it back.


Many of those tiny boards are better than most 60's computers, which had quite a few high level languages to choose from, before C was even born.

So keeping an open mind is actually the key factor here.


This looks like a great system to simplify Arduino programming.

However, this runs your program in a loop. You can add a delay to reduce power consumption, but it will reduce responsiveness. I am really looking for interrupt-based Arduino programming.


That looks pretty cool. Is there some comparison table exists about binary size, memory usage etc against other languages?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: