Argument datatype dependant of previous arguments?

While working with a dynamically typed lang, I came across this:


<span style="color:#323232;">hash(password, algorithm, algorithmOptions)
</span>

Where algorithm is a constant signaling which hashing algorithm to use, and algorithmOptions is a dict whose keys depend on algorithm.

So I thought, can we dictate that if a previous parameter has this value, then this parameter has to have this other value?

E.g.


<span style="color:#323232;">enum HashAlgo {
</span><span style="color:#323232;">    Bcrypt,
</span><span style="color:#323232;">    Argon2,
</span><span style="color:#323232;">}
</span><span style="color:#323232;">
</span><span style="color:#323232;">type BcryptOptions = {
</span><span style="color:#323232;">    Int optionA,
</span><span style="color:#323232;">    Int optionB,
</span><span style="color:#323232;">}
</span><span style="color:#323232;">
</span><span style="color:#323232;">type Argon2Options = {
</span><span style="color:#323232;">    String optionC,
</span><span style="color:#323232;">    String optionD,
</span><span style="color:#323232;">}
</span><span style="color:#323232;">
</span><span style="color:#323232;">
</span><span style="color:#323232;">// Here I make this type "depend" on an argument of type HashAlgo
</span><span style="color:#323232;">type HashOptions = [HashAlgo] => {
</span><span style="color:#323232;">    HashAlgo::Bcrypt => BcryptOptions,
</span><span style="color:#323232;">    HashAlgo::Argon2 => Argon2Options,
</span><span style="color:#323232;">}
</span><span style="color:#323232;">
</span><span style="color:#323232;">fun hash(
</span><span style="color:#323232;">    String password,
</span><span style="color:#323232;">    HashAlgo algorithm,
</span><span style="color:#323232;">    // Here I use HashOptions, passing the prev. argument
</span><span style="color:#323232;">    HashOptions[algorithm] options,
</span><span style="color:#323232;">)
</span>

This way the compiler can ensure the correct dict is used, based on the value of algorithm

Does something like this exist? I now realize that it would be impossible to type check in compile time based on a runtime value, but if it was allowed only for constants? What do you think?

JustTesting,

This would be trivial in python with something like


<span style="color:#323232;">from typing import overload
</span><span style="color:#323232;">from enum import Enum
</span><span style="color:#323232;">
</span><span style="color:#323232;">@overload
</span><span style="color:#323232;">def hash(password: str, algorithm: BCryptAlgorithm, options: BCryptOptions):
</span><span style="color:#323232;">    ...
</span><span style="color:#323232;">
</span><span style="color:#323232;">@overload
</span><span style="color:#323232;">def hash(password: str, algorithm: Argon2Algorithm, options: Argon2Options):
</span><span style="color:#323232;">    ...
</span><span style="color:#323232;">
</span><span style="color:#323232;">def hash(password: str, algorithm, options):
</span><span style="color:#323232;">    [...implementation...]
</span>

Of course it’s python, so at runtime it wouldn’t matter, but the static type checker would complain if you called hash with BCryptAlgorithm and Argon2Options. You could also have it return different types based on the arguments and then in call sites it’d know which type will be returned based on the type of the arguments. And only the last function has am implementation, the @overload ones are just type signatures.

It’s documented here.

armchair_progamer,

Multiple ways you can do this. Most of these should also extend to multiple arguments, and although the constant is promoted to type level, you can pass it around nested functions as a type parameter.

With generics

In Java (personally I think this approach is best way to implement your specific example; also Kotlin, C#, and some others are similar):


<span style="font-weight:bold;color:#a71d5d;">interface </span><span style="color:#0086b3;">HashAlgo</span><span style="font-weight:bold;color:#a71d5d;">&</span><span style="color:#323232;">lt;</span><span style="color:#0086b3;">Options</span><span style="font-weight:bold;color:#a71d5d;">> </span><span style="color:#323232;">{
</span><span style="color:#323232;">    </span><span style="color:#0086b3;">String </span><span style="color:#323232;">hash(</span><span style="color:#0086b3;">String</span><span style="color:#323232;"> password, </span><span style="color:#0086b3;">Options</span><span style="color:#323232;"> options);
</span><span style="color:#323232;">}
</span><span style="color:#323232;">
</span><span style="font-weight:bold;color:#a71d5d;">class </span><span style="color:#0086b3;">Bcrypt </span><span style="font-weight:bold;color:#a71d5d;">implements </span><span style="color:#0086b3;">HashAlgo</span><span style="color:#323232;">&</span><span style="color:#0086b3;">lt</span><span style="color:#323232;">;</span><span style="color:#0086b3;">BcryptOptions</span><span style="color:#323232;">> { ... }
</span><span style="font-weight:bold;color:#a71d5d;">class </span><span style="color:#0086b3;">Argon2 </span><span style="font-weight:bold;color:#a71d5d;">implements </span><span style="color:#0086b3;">HashAlgo</span><span style="color:#323232;">&</span><span style="color:#0086b3;">lt</span><span style="color:#323232;">;</span><span style="color:#0086b3;">Argon2Options</span><span style="color:#323232;">> { ... }
</span><span style="color:#323232;">record </span><span style="color:#0086b3;">BcryptOptions </span><span style="color:#323232;">{ ... }
</span><span style="color:#323232;">record </span><span style="color:#0086b3;">Argon2Options </span><span style="color:#323232;">{ ... }
</span>

In Haskell without GADTs (also Rust is similar):


<span style="font-weight:bold;color:#a71d5d;">class </span><span style="color:#0086b3;">HashAlgo </span><span style="color:#323232;">opts </span><span style="font-weight:bold;color:#a71d5d;">where
</span><span style="color:#323232;">  </span><span style="font-weight:bold;color:#795da3;">hash </span><span style="font-weight:bold;color:#a71d5d;">:: String -> </span><span style="color:#323232;">opts </span><span style="font-weight:bold;color:#a71d5d;">-> String
</span><span style="color:#323232;">
</span><span style="font-weight:bold;color:#a71d5d;">data </span><span style="color:#0086b3;">BcryptOptions </span><span style="font-weight:bold;color:#a71d5d;">= </span><span style="color:#0086b3;">BcryptOptions</span><span style="color:#323232;"> { </span><span style="font-weight:bold;color:#a71d5d;">...</span><span style="color:#323232;"> }
</span><span style="font-weight:bold;color:#a71d5d;">data </span><span style="color:#0086b3;">Argon2Options </span><span style="font-weight:bold;color:#a71d5d;">= </span><span style="color:#0086b3;">Argon2Options</span><span style="color:#323232;"> { </span><span style="font-weight:bold;color:#a71d5d;">...</span><span style="color:#323232;"> }
</span><span style="color:#323232;">
</span><span style="font-weight:bold;color:#a71d5d;">instance HashAlgo BcryptOptions where
</span><span style="color:#323232;">  hash password </span><span style="color:#0086b3;">BcryptOptions</span><span style="color:#323232;"> { </span><span style="font-weight:bold;color:#a71d5d;">..</span><span style="color:#323232;"> } </span><span style="font-weight:bold;color:#a71d5d;">= ...
</span><span style="color:#323232;">
</span><span style="font-weight:bold;color:#a71d5d;">instance HashAlgo Argon2Options where
</span><span style="color:#323232;">  hash password </span><span style="color:#0086b3;">Argon2Options</span><span style="color:#323232;"> { </span><span style="font-weight:bold;color:#a71d5d;">..</span><span style="color:#323232;"> } </span><span style="font-weight:bold;color:#a71d5d;">= ...
</span>

In C (with _Generic):


<span style="font-weight:bold;color:#a71d5d;">typedef struct </span><span style="color:#323232;">{ </span><span style="font-weight:bold;color:#a71d5d;">... </span><span style="color:#323232;">} bcrypt_options;
</span><span style="font-weight:bold;color:#a71d5d;">typedef struct </span><span style="color:#323232;">{ </span><span style="font-weight:bold;color:#a71d5d;">... </span><span style="color:#323232;">} argon2_options;
</span><span style="color:#323232;">
</span><span style="font-weight:bold;color:#a71d5d;">char* </span><span style="font-weight:bold;color:#795da3;">hash_bcrypt</span><span style="color:#323232;">(</span><span style="font-weight:bold;color:#a71d5d;">const char* </span><span style="color:#323232;">password, bcrypt_options options) { </span><span style="font-weight:bold;color:#a71d5d;">... </span><span style="color:#323232;">}
</span><span style="font-weight:bold;color:#a71d5d;">char* </span><span style="font-weight:bold;color:#795da3;">hash_argon2</span><span style="color:#323232;">(</span><span style="font-weight:bold;color:#a71d5d;">const char* </span><span style="color:#323232;">password, argon2_options options) { </span><span style="font-weight:bold;color:#a71d5d;">... </span><span style="color:#323232;">}
</span><span style="color:#323232;">
</span><span style="font-weight:bold;color:#a71d5d;">#define </span><span style="font-weight:bold;color:#795da3;">hash</span><span style="color:#323232;">(password, options) _Generic((options), bcrypt_options</span><span style="font-weight:bold;color:#a71d5d;">:</span><span style="color:#323232;"> hash_bcrypt, argon2_options</span><span style="font-weight:bold;color:#a71d5d;">:</span><span style="color:#323232;"> hash_argon2)(password, options)
</span>

In TypeScript, inverting which type is parameterized (see this StackOverflow question for another TypeScript approach):


<span style="color:#323232;">type HashAlgo = 'bcrypt' | 'argon2'
</span><span style="color:#323232;">type HashOptions&lt;H> = H extends 'bcrypt' ? BcryptOptions : H extends 'argon2' ? ArgonOptions : never
</span><span style="color:#323232;">
</span><span style="color:#323232;">function hash&lt;H>(password: string, algorithm: H, options: HashOptions&lt;H>): string { ... }
</span>

With constant generics or full dependent types

This way is a bit more straightforward but also way more complicated for the compiler, and most languages don’t have these features or they’re very experimental. Dependent types are useful when your constant is non-trivial to compute and you can’t even compute it fully, like vectors with their length as a type parameter and . In that case generics aren’t enough. Constant generics aren’t full dependent types but let you do things like the vector-sum example.

In Haskell with GADTs AKA Generic Algebraic Data types (also works in Idris, Agda, and other Haskell-likes; you can simulate in Rust using GATs AKA Generic Associated Types, but it’s much uglier):


<span style="font-weight:bold;color:#a71d5d;">data </span><span style="color:#0086b3;">BcryptOptions </span><span style="font-weight:bold;color:#a71d5d;">= </span><span style="color:#0086b3;">BcryptOptions</span><span style="color:#323232;"> { </span><span style="font-weight:bold;color:#a71d5d;">...</span><span style="color:#323232;"> }
</span><span style="font-weight:bold;color:#a71d5d;">data </span><span style="color:#0086b3;">Argon2Options </span><span style="font-weight:bold;color:#a71d5d;">= </span><span style="color:#0086b3;">Argon2Options</span><span style="color:#323232;"> { </span><span style="font-weight:bold;color:#a71d5d;">...</span><span style="color:#323232;"> }
</span><span style="font-weight:bold;color:#a71d5d;">data </span><span style="color:#0086b3;">Algorithm</span><span style="color:#323232;"> opts </span><span style="font-weight:bold;color:#a71d5d;">where
</span><span style="color:#323232;">    </span><span style="color:#0086b3;">Bcrypt </span><span style="font-weight:bold;color:#a71d5d;">:: </span><span style="color:#0086b3;">Algorithm BcryptOptions
</span><span style="color:#323232;">    </span><span style="color:#0086b3;">Argon2 </span><span style="font-weight:bold;color:#a71d5d;">:: </span><span style="color:#0086b3;">Algorithm Argon2Options
</span><span style="color:#323232;">
</span><span style="font-weight:bold;color:#795da3;">hash </span><span style="font-weight:bold;color:#a71d5d;">:: String -> Algorithm </span><span style="color:#323232;">opts </span><span style="font-weight:bold;color:#a71d5d;">-> </span><span style="color:#323232;">opts </span><span style="font-weight:bold;color:#a71d5d;">-> String
</span><span style="color:#323232;">hash password algo opts </span><span style="font-weight:bold;color:#a71d5d;">=
</span><span style="color:#323232;">    </span><span style="font-weight:bold;color:#a71d5d;">case</span><span style="color:#323232;"> algo </span><span style="font-weight:bold;color:#a71d5d;">of
</span><span style="color:#323232;">    </span><span style="font-weight:bold;color:#a71d5d;">| </span><span style="color:#0086b3;">Bcrypt </span><span style="font-weight:bold;color:#a71d5d;">-> ... </span><span style="font-style:italic;color:#969896;">-- opts is inferred to be BcryptOptions here
</span><span style="color:#323232;">    </span><span style="font-weight:bold;color:#a71d5d;">| </span><span style="color:#0086b3;">Argon2 </span><span style="font-weight:bold;color:#a71d5d;">-> ... </span><span style="font-style:italic;color:#969896;">-- opts is inferred to be Argon2Options here
</span>

In Coq (also flipping the parameterized types again):


<span style="color:#323232;">Inductive algorithm : Set := bcrypt | argon2.
</span><span style="color:#323232;">Inductive algorithm_options (A: algorithm) : Set := bcrypt_options : ... -> algorithm_options bcrypt | argon2_options : ... -> algorithm_options argon2.
</span><span style="color:#323232;">
</span><span style="color:#323232;">Fixpoint hash (password : string) (algo : algorithm) (opts : algorithm_options also) : string := ... .
</span>
Crazazy,

I think what you are referring to is “dependent types” They usually occur in more advanced functional programming languages like Idris or Agda but Zig also has them in a way

fkn,

This is possible with c++ templates.

  • All
  • Subscribed
  • Moderated
  • Favorites
  • programming_languages@programming.dev
  • GTA5RPClips
  • DreamBathrooms
  • thenastyranch
  • magazineikmin
  • tacticalgear
  • cubers
  • Youngstown
  • mdbf
  • slotface
  • rosin
  • osvaldo12
  • ngwrru68w68
  • kavyap
  • InstantRegret
  • JUstTest
  • everett
  • Durango
  • cisconetworking
  • khanakhh
  • ethstaker
  • tester
  • anitta
  • Leos
  • normalnudes
  • modclub
  • megavids
  • provamag3
  • lostlight
  • All magazines