Need help in representing the List-like type in gleam!

Hello, I was learning gleam and I was in the process of creating an exercise in it.

The exercise is known as Flatten Array. Basically in this question, I will have to take something like this

x = [1, 2, [3, 4, 5, 6], 7]

and the result should be:

x = [1, 2, 3, 4, 5, 6, 7]

Now, in order to solve this, I looked up the (gleam docs)[gleam/list - gleam_stdlib] for flatten method, and it takes an object of type List(List(a)).

So it assumes that the list will be of the format

x = [[1], [2], [3, 4, 5, 6], [7]]

As you can see that subsequent type is a List(List(int)).

But if you look into x, the type is different.
How, can I represent this type in gleam?

Any help will be greatly appreciated!

1 Like

I do not know Gleam at all. Still, the following might prove useful.


In Haskell, x = [1, 2, [3, 4, 5, 6], 7] would be ill-typed. Simple as that.

So lists are out. You need another data type. Perhaps the following.

data ArbitrarilyNested a = One a | Several [ArbitrarilyNested a]

Then you can define

x = Several [One 1, One 2, Several [One 3, One 4, One 5, One 6], One 7]

flatten would be like this:

flatten :: ArbitrarilyNested a -> [a]
flatten = \case
  One x -> pure x
  Several xs -> xs >>= flatten

Playground.

This definitely is a somewhat weird scenario though. I cannot think of real-world applications of such a type right now.

1 Like

@MatthijsBloms analogy to Haskell checks out.
x = [1, 2, [3, 4, 5, 6], 7] doesn’t compile in Gleam, all members of the list have to have the same type.

Not all exercises make sense to implement in all languages, this might be an exercise that’s not fitting for Gleam. If you look around I’m pretty sure that you will find that other statically typed functional language tracks don’t implement this exercise. (just checked Haskell and F#) In statically typed OO languages like Java you can implement it with the built in List class. You let the type of the list elements be Object, the parent class of all classes: java/Flattener.java at main · exercism/java · GitHub. But even then this is very dubious style and should be avoided whenever possible.
On the other hand, the way I understand the purpose of the problem specifications, it’s fine to let the students implement solutions that are awkward to realize in a language, to let them experience that the pros and cons of languages (comparing how difficult it is to solve exercise X in different languages). So I’m interested what people with more experience in statically typed functional languages / Gleam think about this. :)

1 Like

Actually I was implementing the exercise for flattening the list and stumbled upon this… so kinda got confused. I think this exercise is not possible to implement in gleam.

But, doesn’t this behave as a restriction. I mean JS, python can work on these types of lists but gleam cannot?

Yes, type systems are generally a restrictive feature in any language. They prevent the programmer from writing a certain category of programs. This is often helpful, as it prevents a certain category of bugs, but it does mean that some bug-free programs are not allowed anymore. That is one of the fundamental trade-offs between statically and dynamically typed languages.

3 Likes

I showed you that it can be implemented in Haskell, and strongly suspect it can be implemented in Gleam as well. However, like I and @fap noted, this kind of problem is probably not a good fit for Gleam. Not because of restrictions on the languages, but because of idiomaticity.

It only looks easy to do in these languages because these languages are not in the habit of clearly stating assumptions ahead of time. My Haskell solution above and my Python solution (modulo str weirdness) are essentially the same.


I agree with everything @senekor writes above, but also fear it might leave one with a wrong impression of what static typing means. For some extra information on this, read e.g. «No, dynamic type systems are not inherently more open» and «Types as axioms, or: playing god with static types».

3 Likes

The way Python “works” on these data types is a whole lot weirder that it would seem at first glance. Python’s list is both a sequence type, and a container type. It does not really contain any of its member objects, rather it contains pointers to those objects. See this for some more details on what that means (and a nice related post on names), and some big gotchas in working with Python lists.

Chiming in to agree with Matthijs. It looks “easy” in Python, but that’s a trap. :smile:
My solution is quite similar to his, although I am both less efficient, since I do not use a generator, and less explicit by hiding my function call in the returned list comprehension.

Like the Java List class that fap references above, it is necessary in Python to type check the objects a Python list “contains” before one can operate on them. Unlike Java, this is considered par for the course in Python, due to its dynamic/ducktype nature. Python assumes you’ll check or guard for the types you are looking for - it won’t do it for you, which can lead to all sorts of runtime issues if you don’t program defensively.

2 Likes

Sorry for reviving an old topic, but as somebody who struggled for a bit with a similar problem, I want to share my solution so it might be useful for a newcomer to the language (as a newcomer my self).

Looking at the documentation there is a function that takes an Int and returns a list: the list.range function, so to solve this you can do:
let one_to_two = list.range(1, 2)
let seven = list.range(7, 7)
let x = list.flatten([one_to_two, [3, 4, 5, 6], seven])

There must be a better way to solve this problem.

Edit: It turns out there is, and I was doing everything wrong, haha
a more idiomatic way to do this is to declare lists directly
let one_to_two = [1,2]
let seven = [7]
let x = list.flatten([one_to_two], [3, 4, 5, 6], seven])
and of course you can not create new variables
let x = list.flatten([1,2],[3,4,5,6],[7])
so flatten will take a list of list(int) and it will return a single list(int)
meaning that the types would be
flatten:list(list(int))
x:list(Int)
so, as far as you satisfy the type system, everything should be straightforward.

Hello! I’d be happy to help find a better solution for you :)

What is it that you’re looking to do?

1 Like

Hey, thanks for making such a beautiful language :).
It turns out I was doing everything wrong, haha; now that I have read more documentation, I figured that out.
My problem was fighting the type system to work with the expected inputs that functions expect, of course, this is mainly caused because I have to learn more about the language.
I guess I kind of miss the .into function that rust has, and that leaves me with a question,
I see that gleam/int-float-string has helper functions to do type transformations; if I want to work with the type system, is it better to transform everything into a dynamic and check whether it can be transformed back to the type I want?

You can should be able to do this using gleam/list.flat_map as follows:

flat_map([2, [3, 4, 5], 6], fn(item) {
  case item {
    [..list] -> list
    number -> [number]
  }
})

Edit: I originally though this was possible, but later I checked and it’s not, so the only alternative I see is create a recursive function to do this.