Gleam, a statically typed language for the Erlang VM

Woot! I await!

But yeah, as long as you have recursive types (how else would you get a Cons style list type?!), then you can make a normal Y combinator. :slight_smile:

OK, it should work now with a new project. To fix the existing one swap v0.5.0 for 0.5.0 in rebar.config for stdlib.

1 Like

That fixed it! ^.^

And now…

error: Recursive type
- </home/overminddl1/elixir/gleam/tester/src/tester.gleam>:8:8
  |
8 |                     f(x(x))(v)
  |        ^ 

Uh, there’s no recursive type support?! o.O
And the error printer is a touch misaligned. ^.^;

Not yet! There’s definitely some rough patches in the type inference. I wonder if it would work better with some annotations.

For error printing I’m leveraging a Rust library called codespan, which is also a little rough around the edges. Since adopting it I’ve discovered this other library which I think may have been extracted from the Rust compiler. I’m going to try it soon and see if it fares better. https://github.com/rust-lang/annotate-snippets-rs

Well if you look at my TypedElixir it supports typed recursive functions without issue if you need to see any more simplified code compared to bigger compilers then I can fish it out of my playground repo. ^.^

It even trivially detects when a function only ever calls itself and thus becomes a no_return/Never/! return type. No special casing for that either, it’s just when the unification pass is complete and the function type didn’t end up getting resolved. It’s all in the type unificator. ^.^

EDIT: It supports both generic recursion, specific type recursion, and infinite recursion of both previous varieties. ^.^
Like def counter(x), do: counter(x) becomes a no_return/Never/! return type. :slight_smile:
Or even def counter(_x), do: counter(6.28), which types the argument, but the return still remains untyped, not even turned into a Generic type, it just remains an Unbound type.

This is really great! Being able to use records and the header file reduces a lot of boilerplate and reduces user error on the erlang/elixir side.

I’ve tried this out using rec_struct to convert the header files to structs and back to records but there’s still a ton of leg work to use this with my GraphQL project and switch over to using a REST endpoint to simplify things. This library and the new records will work really well if you don’t have a lot to setup/maintain though!

You mentioned earlier that in the future there might be a built in decoder/encoder… would that look something like this if I was using it on the elixir side?

It would be really nice to work with the FFI if you could ensure that there is a barrier where all the unsafe code will blow up outside of gleam, and that encoder/decoder is type safe.

gleam code

// src/resolvers/user

pub struct CreateUserArgs {
  verify_code: Int
  user_data: user.UserParams
}

// src/db/user

pub struct User {
  id: Int
  inserted_at: Int
  updated_at: Int
  name: String
}

elixir code

      ...
      #gql resolver 
      resolve(fn args_map, _context ->
        :resolvers@user@CreateUserArgs.encode!(args_map)
        |> :resolvers@user.create_user(args)
        |> case do
          {:ok, user} -> :db@user@User.decode(:map)
          error -> error
        end
      end)

anyhow I just wanted to leave some feedback as I iterate on my little web app side project!

Glad you’re enjoying the new record change! I agree that right now a REST API will be much easier to create.

I think it’ll look similar to your example. The API I was thinking was this:

// src/resolvers/user.gleam

pub struct CreateUserArgs {
  verify_code: Int
  user_data: user.UserParams
}
# use in Elixir

:resolvers@user.makeCreateUserArgs(params)

Where params can be either a keyword list or a map with the correct keys.

We can’t make it type safe because there will never be enough information at runtime in the dynamic code to be completely sure it is the right type, but we could add some simple guards for ints, floats, etc.

Your idea of a decoder is a good one, I hadn’t thought of that. Perhaps something like this

:resolvers@user.mapCreateUserArgs(user_args_struct) # => map
1 Like

Awesome, that’s great to hear :grin: Yeah as far as type safety I was thinking something that would return ok/error if the types didn’t match. Right now I think one option would be to use something like an embedded ecto schema to validate that the map passed in has all the fields required and the type is Int or nil, even if it had a runtime cost (maybe opt in for that?)

Do you happen to know if there’s a solid JSON parsing library that converts a request body into records? I was trying to figure out if it’s even possible at this point to parse an http post request inside of gleam without the FFI. It seemed like ejson might be able to do this but I only spent 10-15 mins looking around (and some of that erlang syntax is a bit alien to me still :joy: ).

If not my plan was to use Phoenix to accept the request, parse it as a map, then convert it into a record manually using the new 0.5 features and pass it through the FFI.

I would probably import a JSON decoder function like so:

import gleam/dynamic

pub external fn parse_json(String) -> Result(Dynamic, SomeCustomJsonErrorType)
  = "Elixir.Jason" "decode"

With this function we can turn a valid JSON string into a Ok(dynamic_data) or an error, which can then be converted into strongly typed data with the gleam/dynamic module https://github.com/gleam-lang/stdlib/blob/master/src/gleam/dynamic.gleam

This is somewhat similar to writing an JSON decoder in Elm, though we don’t have a nice API for composing these conversion functions yet, so it’ll have to be combined with lost of result.map and result.then.

It’s likely unclear how exactly to use this module as I’ve not written documentation or examples, sorry about that! :frowning: It is on my TODO list for the near future.

1 Like

I’ve been looking for examples of gleam’s type system but all I really find is enums and external types. Would really like to see https://gleam.run/ have more examples and details about how to define types (as this is the main point is it not?). Also I’d like to see an FAQ answer common questions about the language direction and the current state of the language.

1 Like

What do you mean by defining types other than those? Enums, structs and external types are the only three ways of creating types in Gleam.

If you have some specific questions I would be happy to answer them and add them to the FAQ as required :slight_smile:

1 Like

Like Elm, is there no algebraic data types? No way to say type Foo = Bar | Baz | Blah?

Those are the ADT’s, he’s using a lot of rust naming conventions, and rust is definitely wrong as they aren’t enumerations, they are tagged variants, lol.

Yeah the language is a bit funky but it seems to be the term that mainstream languages such as Rust and Swift are using so I decided it was best to conform rather than use more niche (though possibly more accurate) terms such as ADT.

In rust an Enum means an ADT??? WTF, thats super confusing… For most people, an enumeration means a predefined set of values. I would definitely advise against overloading this term as rust has done.

For reference, here are the wikipedia definitions

Yeah I agree, lol. Enumerations are not ADT’s, I don’t know who conflated those but it was definitely a screwup that’s now stuck on multiple languages, it’s horrifying. :sweat_smile:

I just came across some rust while looking for union types in Go (which don’t exist and are not looking too good for them either). This language is early enough that it shouldn’t suffer from rust leaking into it…

It finally clicked for me given this conversation and the example at the end of that issue. Here is an example of any arbitrary JSON data in rust:

enum Value {
  Null,
  Bool(bool),
  Number(Number),
  String(String),
  Array(Vec),
  Object(Map<String, Value>),
}

The converging of these two events is what it took for me to really understand, which is an extremely high barrier to entry IMO. I highly suggest making a change away from this concept… But, if you do decide to keep rust’s weird decision, please document the HELL out of it. Perhaps the alternative of writing so much documentation will help you to realize its far less work to change it now than deal with this issue later.

Really all you have to do is change the word enum to type to make it work in gleam… (sorry if i did something wrong, but this should be close enough to illustrate my point I think)

type Value {
  Nil,
  Bool(Bool),
  Int(Int),
  Float(Float),
  List(Value),
  Object(Map(String, Value)),
}
2 Likes

So I tried doing this, not sure if this is the best place to post this… perhaps I still misunderstand, can you help?

pub enum TimeUnit {
  Hour
  Day
  Week
  Month
  Year
}

pub struct TimeStrategy {
  unit: TimeUnit
}

pub struct OtherStrategy {
}

pub enum Strategy {
  TimeStrategy
  OtherStrategy
}

pub struct Config {
  name: String
  strategies: List(Strategy)
}

pub fn create_config() {
  Config(
    name: "my_config",
    strategies: [
      TimeStrategy(unit: Hour),
      OtherStrategy
    ]
  )
}

The error is as follows:

error: Unexpected labelled argument
- </Users/mgwidmann/code/gleam/hello_world/src/config.gleam>:54:20
   |
54 |       TimeStrategy(unit: Hour),
   |                    ^^^^^^^^^^
   |

This argument has been given a label but the constructor does not expect any.
Please remove the label `unit`.

I tried playing with this and couldn’t get anything to work as I’m intending…

In this test, the Strategy enum should be represented as type Strategy = TimeStrategy | OtherStrategy, but that doesn’t appear to be how it works. Am I missing something?

From what I understand, you can see Gleam Structs as a special enum type that has just one constructor (the product of all its fields). But it’s still a separate type. Gleam doesn’t have extensible, polymorphic variants or any other fancy stuff that allows you to declare enums as sums of constructors from other types without wrapping them with a new name. But you can still do something like:


pub enum TimeUnit {
  Hour
  Day
  Week
  Month
  Year
}

pub struct TimeStrategy {
  unit: TimeUnit
}

pub struct OtherStrategy {
}

pub enum Strategy {
  MyStrategyEnumsTimeStrategy(TimeStrategy)
  MyStrategyEnumsOtherStrategy(OtherStrategy)
}

pub struct Config {
  name: String
  strategies: List(Strategy)
}

pub fn create_config() {
  Config(
    name: "my_config",
    strategies: [
      MyStrategyEnumsTimeStrategy(TimeStrategy(unit: Hour)),
      MyStrategyEnumsOtherStrategy(OtherStrategy)
    ]
  )
}
1 Like