What about Nickel/Dhall Lang

Hi there!

First and foremost, no flame here. I have the feeling that when we talk about Yaml, complains are popping up and poisoning the original topic. I don’t want yet another talk about GUI vs Yaml, because mainly I don’t care. I use both, they both have their pros and cons. Spread the love! :smiling_face_with_three_hearts:

Now about the main topic here. I installed UI Lovelace Minimalist and I like it. I have a dev background, and I’m more interested for the GUI to edit it with Yaml files. I would love to make some extra magic to my code before being interpreted by Home Assistan. Here is a small list of what I refer as magic:

  • Counting unique light. entities in a subview to add a small counter of lights without having to maintain it explicitly
  • Functions! Templates! I mean easy templates, not the one that requires you to refresh the yaml and refresh your browser to see that you gave the wrong type in the yaml.
  • Make some query to the HomeAssistant API to find some specific sensor within an area. Why not actually generating all the views from something generic (what does actually Mushroom Strategy Dashboard)

The fact is I hate Yaml. I’ll not enumerate everything I dislike about it but mainly the missing strong type policy makes templating a real pain unless we understand how lovelace cards are designed.
But I don’t think Home Assistant should drop Yaml support for some nerds like me, and I’m so happy to have such an accessible software in OpenSource. Again, spread the love like you say :wink:

That’s why I was wondering about a community based Dhall or Nickel support. The best of both words: no dev from the HomeAssistant core team to support it since it transcompiles in Yaml, and the dev way available for everybody that opts in for.
I can’t find any proper mention in the discourse. Now the public talk is open for its implementation.

What do you think about it?

PS: It’s funny, because I never used Dhall or Nickel. I am a big fan of Nix though. But isn’t HomeAssistant configuration a perfect example of what these languages are designed for?

I avoid the GIU for a lot of things and use the home assistant helper extension in VSCode. It’s pretty good at picking up configuration syntax errors. Though its auto indentation is a PITA.

Jsonnet would be a mean f’n thing to have for dashboards.

On the subject of VSCode, yes the extension does a lot of things. But I have a pretty weak hardware, VSCode is too heavy for my RPi (or at least I have the feeling that I ask too much of this little board), and I still hate Yaml :face_with_hand_over_mouth: I would like something strict by design, not by extension. Still good work for this extension though.

Jsonnet would be a great default implementation. Like everybody is writing Json, but use additional abstraction using Jsonnet functions. That actually could be a great way for HomeAssistant to extends their current design without refactoring a lot of things.

I want something more up in abstraction. I really like the Dhall language for its simplicity to read. Maybe I’m biased 'cause I did some Elm back in the days, and I really love the Nix and Elm approach to scream at compilation when the config isn’t right instead of checking it at runtime.
I began to use Dhall, I’ll see if it works at well as I want. I’ll keep you updated if it fails or if it is a very great way to generate configuration.

EDIT : About Jsonnet… I mean, Home Assistant config is in Yaml, not Json. So obviously it is not a drop-in replacement. I was in a tunnel vision about Dhall or Nickel, so I completely missed the point here where Home Assistant config is not written in Json ^^’

Hi again!

I came back with my thoughts about what I asked. I think I’ll move to Nickel instead of Dhall, as it gave me the best experience so far. They both give the same outcome but in different manners, and I don’t think Dhall is appropriate for Home Assistant configuration. Let me explain.

I used for both a folder for the type declaration, and another for the actual content of my dashboard. Moreover, obviously I will not paste every like of codes from my dashboard as it would be painful to read, so just small snippets will be visible here.
Finally, note that I began to discover these language specifically for this use case, so I’m no expert. Maybe I’m totally wrong, and so please explain it to me if I am.

Dhall

Dhall looks like Elm or Haskell when it comes to style.

let Dashboard : Type = ../config/dashboard/dashboard.dhall in

{ title = "Dashboard"
, theme = "minimalist-desktop"
, background = "var(--background-image)"
, views = 
    [ ./views/main.dhall
    ]
, kiosk_mode = 
    { mobile_settings = 
        { hide_header = True
        }
    }
} : Dashboard

For me, it is straight forward to read. You can opt in to a strongly type implementation, hence de : Dashboard that is described in the config folder.

However there is a huge caveat for this language : Dhall expects everything in an array to be strongly structured the same way, it means type but also content.

For Home Assistant where there is a lot of different cards with different options:

  1. It would mean that every cards should declare all the parameters a card could have, which is a huge boilerplate.
  2. 2 cards should never have the same parameter with 2 different types otherwise it looses some of the strong type policy we were looking for
  3. The only way we can bend this to be at least readable is to create and maintain a default empty card, and merge it with every other cards we wanted to add, which is very painful. Check Record complete

Don’t get me wrong, this is not a bad design choice from Dhall, it just does not fit for Home Assistant since a lot of cards are community based, therefore with no type control whatsoever.

Nickel

Nickel on the other hand looks a lot more like Nix. Well, I love the Nix way, but I don’t really like the language. But it is better than plain Yaml :stuck_out_tongue:

let Dashboard = import "../config/dashboard.ncl" in

{ 
    title = "Dashboard", 
    theme = "minimalist-desktop", 
    kiosk_mode.mobile_settings.hide_header = true,
    views = [
        import "./views/main/default.ncl",
    ], 
} | Dashboard

Regarding Nickel, there is 2 major ways to have a validated data structure: Types and Contracts. Nothing very fancy here.

  • Types would behave like something that MUST be exactly the same. So no more field, and strictly same structure and inner types.
  • Contracts is more flexible, where you can allow extra fields, add type and more verifications. It is more a “at least” implementatinon, if it makes sense.

Contracts could be used to have a flexible data structure and put more validations to every models.

let View = import "./view.ncl" in

let 
    KioskMode = {
        mobile_settings = {
            hide_header | Bool,
        }
    }
in
{
    title | String,
    theme 
        | String
        | optional,
    background 
        | String
        | default = "var(--background-image)",
    kiosk_mode | KioskMode,
    views | Array View,
    ..
}

Here, you can see that I allow extra fields using .. but not in KioskMode. Some parameters are optional, meaning if not set, they will be not rendered in the Yaml. Some parameters have a default value.

To me, both of those are a lot less human readable than yaml.

I think it depends on people. I am actually used to read those kind of code, so I’m not bothered that much.

But it is not just for readability. I would love to finally have validations and strong typing for my GUI. Hence Nickel and its Contracts thing, giving a lot a flexibility but also locks a lot of errors as well. It is a pain at the beginning since Contracts need to be written explicitly, but it prevents so many bad behaviour with comprehensive error at compilation, not runtime.

For me, it is also all about maintainability. For those language, it is easy to create an anonymous function that will accept a very specific input, generating a whole bloc of Yaml. Let me show you:

let 
    pageTile = fun config => {
        type = "custom:mushroom-template-card",
        primary = config.name,
        secondary = "",
        icon = config.icon,
        layout = "vertical",
        icon_color = config.color,
        tap_action = {
            action = "navigate",
            navigation_path = config.name
                |> std.string.replace_regex "[éèêë]" "e"
                |> std.string.lowercase,
        },
    }
in
{
    type = "horizontal-stack",
    cards = [
        pageTile {
            name = "Lumières",
            icon = "mdi:lightbulb",
            color = "yellow",
        },
        pageTile {
            name = "Sécurité",
            icon = "mdi:shield",
            color = "green",
        },
        pageTile {
            name = "Lab",
            icon = "mdi:flask",
            color = "purple",
        },
        pageTile {
            name = "Réseau",
            icon = "mdi:network",
            color = "blue",
        },
    ],
},

With this example, you imagine pretty quickly how big is the Yaml output for not so much to actually declare. And for every small tweaks I would make in the futur, I have only one place to change instead of 4.
You could argue that templating makes it possible with Jinja but it is so much more complicated. I tried, but I find it so painful to use.

PS : I didn’t strongly typed the input of the pageTile, but I could have. I’m just experimenting for now so laziness is stronger :stuck_out_tongue:

Definitely which is why I pre conditioned the statement with “To me…”

I value readability more than strong typing too.

After playing with it for 1 week, I can definitely say that it is worth it, but not for everyone.

Pros:

  • It is strongly typed and compiled, meaning that you don’t have to reload a page to know when you did an error. Most of the time, it works really well
  • It makes refactoring fearless. Since you can pass variables and data structure, you can describes what you want from what you have. For instance, I created an area type that described an area (it can have temp, humidity or light sensor) and for every area, it creates a card with the appropriate data in it without having to duplicate it. And I can easily switch from one card template to another
  • I really love the idea of more freedom on how you generate your dashboard and quickly see the output. All my dashboard is in one file only when built, but I can separate everything easily from the Nickel project

Cons:

  • The language is immature. For now, I find it painful to use libraries et typing, forcing to import by relative path which make the reading hard.
  • There is a learning curve. I does not bother me, but still it exists.
  • Some of the processing must be done outside of Nickel, after the file is generated. For instance, if you do have to import your button template, since it is not plain Yaml but a function added specifically for HomeAssistant, you cannot create this entry from Nickel

I put my config on Github, so people can explore the code if they want :slight_smile:

1 Like

Strong typing is a great idea for coders of generative dashboards. Editing in the “code editor” is super painful and it’s buggy sometimes.