Skip to content

Add an alternative example to function intersection type - #15948

Closed
Defman21 wants to merge 1 commit into
elixir-lang:mainfrom
Defman21:patch-1
Closed

Defman21 wants to merge 1 commit into
elixir-lang:mainfrom
Defman21:patch-1

Conversation

@Defman21

Copy link
Copy Markdown

While I was reading about set theoretic types, I didn't fully understand the reasoning behind functions with multiple heads being ANDed in their type definition. The t-shirt example helped a bit, but what made it click for me is the example I've described in the Pull Request: a function that takes a list of integers or booleans and a function to map every value of the list to some new value. So I'd like to contribute such example to the documentation in case it would help someone as well :)

English is not my native language so the wording may not be top notch, feel free to re-word it.

@josevalim

Copy link
Copy Markdown
Member

Hi @Defman21! There are some caveats here. Let's convert your example to regular types. Imagine this function:

def weird_add(x, y) when is_integer(x) and is_integer(y), do: x + y
def weird_add(x, y) when is_boolean(x) and is_boolean(y), do: x and y

You could give it a type signature of shape:

$ integer(), integer() -> integer()
$ boolean(), boolean() -> boolean()

But you could also give this one:

$ integer() or boolean(), integer() or boolean() -> integer() or boolean()

The last one is not wrong but it is imprecise. In that version, we can pass integer(), boolean() as arguments, which will crash, but the type signature accepts it! In other words, a type signature does not guarantee that function will not raise for valid inputs. This is by definition in pretty much most mainstream type systems. It is the same in your example, not stricly wrong, but imprecise.

Note your example could also be satisfied by a function of shape (integer() or boolean() -> integer() or boolean()), which means you accept more general shapes (if you so want).

I will sleep on this and see if I can come up with a simpler example with fewer caveats.

@Defman21

Defman21 commented Sep 27, 2026 •

Copy link
Copy Markdown
Author

Hi José! First of all, thanks for taking your time explaining this stuff to me :)

The last one is not wrong but it is imprecise

And if I understand correctly, any function with one of the following signatures would satisfy the spec (if we combine all ors):

$ integer(), integer() -> integer()
$ integer(), integer() -> boolean()
$ integer(), boolean() -> integer()
$ integer(), boolean() -> boolean()
$ boolean(), integer() -> integer()
$ boolean(), integer() -> boolean()
$ boolean(), boolean() -> integer()
$ boolean(), boolean() -> boolean()

Which is much broader and have improper variants rather than a function that has both of these signatures:

$ integer(), integer() -> integer()
$ boolean(), boolean() -> boolean()

Which is precise (I've just watched your talk about precise typing @ ElixirConf 2026 :D).

Your weird_add is pretty good for explaining the difference between (a or b) -> (a or b) and ((a -> a) and (b -> b)).
But (a -> a) or (b -> b) (which has 2 type variants and would be satisfied by AT LEAST one) is more precise than (a or b) -> (a or b) (which has 4 type variants and would be satisfied by AT LEAST one). And (a -> a) and (b -> b) has only 1 type variant: a multi-head function of which one head only accepts an integer and returns an integer, and one head accepts a boolean and returns a boolean. And I assume it can have more heads, but it would still satisfy {a -> a, b -> b} & {a -> a, b -> b, c -> c}.

I mean, Elixir is kinda unique because you have multi-head functions with guards and I'm not sure it's possible to express such uniqueness in other mainstream languages and their type systems. I can only come up with something like (a: T, b: T) -> T where T = Integer or Boolean.

I'd be interested to see what example you can come up with, if any :)

@josevalim

josevalim commented Sep 27, 2026 •

Copy link
Copy Markdown
Member

But (a -> a) or (b -> b) (which has 2 type variants and would be satisfied by AT LEAST one) is more precise than (a or b) -> (a or b) (which has 4 type variants and would be satisfied by AT LEAST one).

Yes, correct. The biggest issue though is that Elixir cannot distinguish between a function of (int -> int) vs (bool -> bool) at runtime, so the or variant is not really useful. And yes, (a: T -> b: T) when T: int or bool would be another way to express it once w add parametric types (stricly speaking, we don't need parametric types at the root, because we can always convert it as an intersection of the arrows. for example, (a -> a) is the same as (int -> int) and (bool -> bool) and (string -> string) and ...).

All of those things are interesting but I think it is a bit out of scope of what we are trying to introduce in this section!

@Defman21

Copy link
Copy Markdown
Author

All of those things are interesting but I think it is a bit out of scope of what we are trying to introduce in this section!

Okay that's fair, it's just set theoretic types caught my attention because the whole documentation was an interesting read and I was stuck for a while with multiple function heads. Thank you again for explaining it in such great details! <3

@Defman21 Defman21 closed this Sep 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants