# The best way(s) to encode binary type parameter choices

**URL:** <https://discourse.julialang.org/t/the-best-way-s-to-encode-binary-type-parameter-choices/127434>\
**Category:** General Usage\
**Tags:** parametric-types, design-pattern\
**Created:** [March 27, 2025, 11:48pm UTC](https://discourse.julialang.org/t/the-best-way-s-to-encode-binary-type-parameter-choices/127434 "2025-03-27T23:48:57Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![brainandforce](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/brainandforce/32/211054_2.png) [@brainandforce](https://discourse.julialang.org/u/brainandforce)\
**Post date:** [March 27, 2025, 11:48pm UTC](https://discourse.julialang.org/t/the-best-way-s-to-encode-binary-type-parameter-choices/127434/1 "2025-03-27T23:48:57Z")

</div>

Say I have a parametric type `Foo{B}` and I want to restrict `B` to two possible values. I can think of a couple of ways to do this:

1. Have `B` take on a Boolean value and use an assertion in the inner constructor to verify this. This is what I usually see recommended when invariants have to be enforced on type parameters which are values.

```julia
struct Foo{B}
    function Foo{B}() where {B}
        @assert B isa Bool "B must be Boolean (got $B)"
        return new()
    end
end

```

However, it has one major disadvantage: no error is produced if an invalid type is constructed (as opposed to an instance of the type).

```julia-repl
julia> Foo{Int}()
ERROR: AssertionError: B must be Boolean (got Int64)
Stacktrace:
 [1] Foo{Int64}()
   @ Main ./REPL[2]:3
 [2] top-level scope
   @ REPL[5]:1

julia> Foo{Int}
Foo{Int64}

```

1. Restrict `B` being either `Val{true}` or `Val{false}`. This does cause an error if an invalid type is constructed.

```julia
struct Foo{B<:Union{Val{true},Val{false}}
end

```

```julia-repl
julia> Foo{Val{true}}
Foo{Val{true}}

julia> Foo{Val{false}}
Foo{Val{false}}

julia> Foo{Int}
ERROR: TypeError: in Foo, in B, expected B<:Union{Val{true}, Val{false}}, got Type{Int64}
Stacktrace:
 [1] top-level scope
   @ REPL[5]:1

julia> Foo{Val{2}}
ERROR: TypeError: in Foo, in B, expected B<:Union{Val{true}, Val{false}}, got Type{Val{2}}
Stacktrace:
 [1] top-level scope
   @ REPL[6]:1

```

However, it feels clunky (and possibly an abuse) to introduce `Val` here, and I’ve never seen a design pattern like this recommended anywhere.

1. Create types that only act as type parameters for `Foo{B}`, and restrict `B` to a supertype containing them. I’m not sure if `Bar` and `Baz` could be considered traits, but that’s what they feel like. This is the most explicit approach, and the one I’d personally favor (certainly if the parameter may take on more values). One could argue that something like this would clutter the namespace.

```julia
abstract type BarOrBaz
end

struct Bar <: BarOrBaz
end

struct Baz <: BarOrBaz
end 

struct Foo{B<:BarOrBaz}
end

```

Are there reasons I should favor one approach over the other?

---

<div class="post-metadata">

**Author:** ![PeterSimon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petersimon/32/25193_2.png) [@PeterSimon](https://discourse.julialang.org/u/PeterSimon)\
**Post date:** [March 28, 2025, 12:36am UTC](https://discourse.julialang.org/t/the-best-way-s-to-encode-binary-type-parameter-choices/127434/2 "2025-03-28T00:36:45Z")

</div>

How about this instead?

```julia
struct Foo{B} 
    function Foo{B}() where {B}
        if B isa Bool
            return new()
        else
            error("Type parameter $B is not a Bool")
        end
    end
end

```

---

<div class="post-metadata">

**Author:** ![brainandforce](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/brainandforce/32/211054_2.png) [@brainandforce](https://discourse.julialang.org/u/brainandforce)\
**Post date:** [March 28, 2025, 12:47am UTC](https://discourse.julialang.org/t/the-best-way-s-to-encode-binary-type-parameter-choices/127434/3 "2025-03-28T00:47:17Z")

</div>

Are you suggesting this so that `@assert` does not get disabled in certain circumstances?

---

<div class="post-metadata">

**Author:** ![PeterSimon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petersimon/32/25193_2.png) [@PeterSimon](https://discourse.julialang.org/u/PeterSimon)\
**Post date:** [March 28, 2025, 1:39am UTC](https://discourse.julialang.org/t/the-best-way-s-to-encode-binary-type-parameter-choices/127434/4 "2025-03-28T01:39:44Z")

</div>

Unlike your Case 1., my code disallows anything but `Bool` as the type parameter:

```julia
julia> Foo{true}()
Foo{true}()

julia> Foo{false}()
Foo{false}()

julia> Foo{-1}()
ERROR: Type parameter -1 is not a Bool
Stacktrace:
 [1] error(s::String)
   @ Base .\error.jl:35
 [2] Foo{-1}()
   @ Main .\REPL[1]:6
 [3] top-level scope
   @ REPL[4]:1

julia> Foo{1.0}()
ERROR: Type parameter 1.0 is not a Bool
Stacktrace:
 [1] error(s::String)
   @ Base .\error.jl:35
 [2] Foo{1.0}()
   @ Main .\REPL[1]:6
 [3] top-level scope
   @ REPL[5]:1

```

Yes, the reason I use `error` rather than `@assert` is because of the [warning about `@assert` in the Julia manual](https://docs.julialang.org/en/v1/base/base/#Base.@assert):

 ![image](https://global.discourse-cdn.com/julialang/original/3X/c/b/cbc22924ceb1414873c571d7424b0f92babb1d14.png)

The `else` clause takes care of the case where the type parameter is other than `Bool` which your code in your Case 1. did not address.

---

<div class="post-metadata">

**Author:** ![brainandforce](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/brainandforce/32/211054_2.png) [@brainandforce](https://discourse.julialang.org/u/brainandforce)\
**Post date:** [March 28, 2025, 7:50am UTC](https://discourse.julialang.org/t/the-best-way-s-to-encode-binary-type-parameter-choices/127434/5 "2025-03-28T07:50:51Z")

</div>

Well, I just realized I’ve been using `@assert` incorrectly in a lot of my code. Oops. Aside from `@assert` being disabled, our code appears to work identically, though?

```julia-repl
julia> struct Foo{B}
           function Foo{B}() where {B}
               @assert B isa Bool "B must be Boolean (got $B)"
               return new()
           end
       end

julia> Foo{1}()
ERROR: AssertionError: B must be Boolean (got 1)
Stacktrace:
 [1] Foo{1}()
   @ Main ./REPL[1]:3
 [2] top-level scope
   @ REPL[2]:1

julia> Foo{Int}()
ERROR: AssertionError: B must be Boolean (got Int64)
Stacktrace:
 [1] Foo{Int64}()
   @ Main ./REPL[1]:3
 [2] top-level scope
   @ REPL[3]:1

julia> Foo{1.0}()
ERROR: AssertionError: B must be Boolean (got 1.0)
Stacktrace:
 [1] Foo{1.0}()
   @ Main ./REPL[1]:3
 [2] top-level scope
   @ REPL[4]:1

```

---

<div class="post-metadata">

**Author:** ![ForceBru](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/forcebru/32/21389_2.png) [@ForceBru](https://discourse.julialang.org/u/ForceBru)\
**Post date:** [March 28, 2025, 9:03am UTC](https://discourse.julialang.org/t/the-best-way-s-to-encode-binary-type-parameter-choices/127434/6 "2025-03-28T09:03:36Z")

</div>

Why not constrain the type parameter to be Bool?

```julia
struct Foo{B<:Bool}
   function Foo{B}() where B<:Bool
      new{B}()
   end
end

```

Then `Foo{Bool}()` and `Foo{Bool}` work fine, while `Foo{Int64}` and the like is an error.

---

<div class="post-metadata">

**Author:** ![barucden](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/barucden/32/26154_2.png) [@barucden](https://discourse.julialang.org/u/barucden)\
**Post date:** [March 28, 2025, 9:11am UTC](https://discourse.julialang.org/t/the-best-way-s-to-encode-binary-type-parameter-choices/127434/7 "2025-03-28T09:11:29Z")

</div>

What OP wants to do is `Foo{true}()` or `Foo{false}()`, not `Foo{Bool}()`.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [March 28, 2025, 9:28am UTC](https://discourse.julialang.org/t/the-best-way-s-to-encode-binary-type-parameter-choices/127434/8 "2025-03-28T09:28:03Z")

</div>

> [@brainandforce](#):
>
> 1. Create types that only act as type parameters for `Foo{B}`, and restrict `B` to a supertype containing them

This one is maybe the best. Except I’d make the supertype a `Union` instead of an `abstract type` (that way both you and the compiler’s subtyping know there will not be more subtypes added later):

```julia
struct Bar end
struct Baz end
const BarOrBaz = Union{Bar, Baz}
struct Foo{B <: BarOrBaz} end

```

However keep in mind this design still allows invalid types such as `Foo{BarOrBaz}` or `Foo{Union{}}`.

One thing you could perhaps do is restrict the type parameter to be a subtype of a concrete type, so the only valid types would be `Foo{Union{}}` and `Foo{Concrete}`:

```julia
struct Concrete end
struct Foo{P <: Concrete} end

```

---

<div class="post-metadata">

**Author:** ![PeterSimon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petersimon/32/25193_2.png) [@PeterSimon](https://discourse.julialang.org/u/PeterSimon)\
**Post date:** [March 28, 2025, 1:51pm UTC](https://discourse.julialang.org/t/the-best-way-s-to-encode-binary-type-parameter-choices/127434/9 "2025-03-28T13:51:03Z")

</div>

> [@brainandforce](#):
>
> Aside from `@assert` being disabled, our code appears to work identically, though?

You’re right, I missed your distinction between constructing a type and an instance of a type in your original post.

---

<div class="post-metadata">

**Author:** ![brainandforce](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/brainandforce/32/211054_2.png) [@brainandforce](https://discourse.julialang.org/u/brainandforce)\
**Post date:** [March 31, 2025, 8:06pm UTC](https://discourse.julialang.org/t/the-best-way-s-to-encode-binary-type-parameter-choices/127434/10 "2025-03-31T20:06:42Z")

</div>

> [@nsajko](#):
>
> One thing you could perhaps do is restrict the type parameter to be a subtype of a concrete type, so the only valid types would be `Foo{Union{}}` and `Foo{Concrete}`:
> 
> ```julia-auto
> struct Concrete end
> struct Foo{P <: Concrete} end
> 
> ```

This is a really interesting option I hadn’t considered. It would certainly lock the possible types down to two.

One reason I think it’s useful to include `true` and `false` specifically as type parameters is because these values might be useful in methods using these types. The concrete example I’ll give is `CliffordNumbers.Z2CliffordNumber{P,Q,T,L}`, which has two aliases:

```julia
const EvenCliffordNumber{Q,T<:BaseNumber,L} = Z2CliffordNumber{false,Q,T,L}
const OddCliffordNumber{Q,T<:BaseNumber,L} = Z2CliffordNumber{true,Q,T,L}

```

Usually, we deal with even or odd grade elements of Clifford algebras (and not mixed grade elements). The type parameter `P` encodes the parity of the grade, which allows a decent amount of simplification when writing algorithms.
