#Using a weird way of variant constructing - tell me if it's valid.

1 messages · Page 1 of 1 (latest)

errant briar
#

Hi guys!!!

Is it sensible to suggest this way of constructing types? (example below)

import gleam/option.{None, Some}
import grom/channel
import grom/client

const token = "l.u.c.y"

const guild_id = "768594524158427167"

pub fn main() {
  let client = client.Client(token:)

  let data =
    channel.CreateText(
      ..channel.new_create_text(named: "hello", in: guild_id),
      position: Some(100),
    )
    |> channel.CreateTextChannel

  client
  |> channel.create(using: data, because: None)
  |> echo
}

Essentially, I'm providing a function called new_create_text, which creates a new CreateText value, which is filled with a lot of option.Nones:

pub fn new_create_text(named name: String, in guild_id: String) -> CreateText {
  CreateText(guild_id, name, None, None, None, None, None, False, None, None)
}

This saves the user from typing all of these Nones.

Then I pipe it into a channel.CreateTextChannel constructor, which is of the channel.Create type:

pub type Create {
  CreateTextChannel(CreateText)
  CreateDmChannel(CreateDm)
  CreateVoiceChannel(CreateVoice)
  CreateCategoryChannel(CreateCategory)
  CreateAnnouncementChannel(CreateAnnouncement)
  CreateStageChannel(CreateStage)
  CreateForumChannel(CreateForum)
  CreateMediaChannel(CreateMedia)
}

This is because, well, I need to accept one type into the function (or make a lot of very similar-looking functions).

Would you improve on anything in this code?
If you'd like more context, either ask or see for yourself at https://tangled.sh/@folospior.me/grom

||src/channel.gleam for the library code||
||examples/src/examples.gleam for the example code||

silk anvil
#

Maybe a builder pattern would be a good fit?

errant briar
#

I thought so too, that's what I've been doing, but that would result in 51 functions purely for the pattern.

#

And that would be functions that'd be doing essentially the same thing

#
pub fn create_text_with_position(create_text, position) {
  CreateText(..create_text, position: Some(position))
}```
fossil thicket
#

It is common to do something like

channel.new_create_text(named: "hello", in: guild_id)
|> with_position(100)

I see this is for a lib, I suggest having all those functions to keep the API tidy

silk anvil
#

It probably also makes sense to split apart each of those types and their associated builder functions into separate files instead of having one file with a bunch of different types and constructor functions in it

silk anvil
#

If there's going to be a bunch of builder functions for each of those types, then having each type in a separate file means the builder function names can be shorter since they won't have to be prefixed with the type name to make them unique

#

I also just prefer writing stuff like create_text.new than channel.new_create_text but that's personal preference

river girder
azure holly
#

I think it might be more important how you consume and pattern match on those types than how you construct them

#

I doubt that constructors/builders should drive how you split (or not) your modules

#

It's also important to use types to avoid invalid state, maybe in that context does't make any sense to split

lean yoke
#

i would like to gently point out that the motivation for writing this is "saves the user from typing all of these Nones." but just take a moment to consider:

wibble.new()
|> wibble.with_foo(1)
|> wibble.with_bar(2)

vs

Wibble(foo: Some(1), bar: Some(2), baz: None)

and tell me which one really saves the typing 😅

silk anvil
#

it would save typing if 99% of the time you just use the base constructor shown earlier and dont typically set any of the None's