willcrichton / tyrade

A pure functional language for type-level programming in Rust
323 stars 13 forks source link

Alternative construct of type operating traits #4

Open jerry73204 opened 4 years ago

jerry73204 commented 4 years ago

I noticed tyrade treats the first argument specially when translating function arguments.

impl<...> ComputeIdent<Arg2, Arg3, ...> for Arg1 {
    type Output = ...;
}

The design does not distinguish functions and methods. For example, users would add L name prefixes on list operating traits for distinction. It results in C-style naming.

impl<L: List, V> LAppend<V> for L {...}
impl<L: List, V> LPrepend<V> for L {...}

I have an alternative approach that enables us to write methods in impl {} block. We can instead move the first argument to the generic list. The additional benefic is that we can define constant-valued functions.

impl<...> Function<Arg1, Arg2, ...> for () {
    type Ouput = ...;
}

impl ConstFn for () {
    type Output = U3; // from typenum
}

impl<...> Calling<Arg1, Arg2, ...> for ()
where
    (): Function<Arg1, ...> // trait bound
 {
    type Output = <() as Function<Arg1, ...>>::Output;
}

While for methods, the self is regarded as operands.

impl List {
    fn Append(self, V: Type) -> List {
    }
}

// translates to

impl<S: LIst, V> Append<V> for S
{
    type Output = ...;
}

We can make it further. The Compute* traits are generalized into maps like that is done in my type-freak (code). It enables the function passing arguments. For example, List::map.

pub trait Map<Inputs> {
    type Output;
}

// type operating trait from user
trait Compute<Arg1, ...> {}
impl Compute<Arg1, ...> for () { type Output = ...; }

// derived
struct ComputeMap;
impl<...> Map<(Arg1, ...)> for ComputeMap {
    type Output = <() as Compute<Arg1, ...>>::Output;
}

// now we have "callbacks"
trait Calling<T, F: Map> {}
impl<T, F: Map> Calling<T, F> for () {
    type Output = <F as Map<T1, T2, T3, ...>>::Output;  // T1, T2, T3 arguments are derived from T
} 
willcrichton commented 4 years ago

These are great observations. I like the idea of allowing direct impl blocks.

I've been thinking about how to implement higher-order operators like map. This isn't the same, but I think related to implementing polymorphic operators. For example, TIf. Right now I just hackily monomorphize the trait for a set of known types. But ideally you could do something like

tyrade! {
  #[derive(TIf, TMap)]
  enum TList {
    Nil,
    Cons(Type, TList)
  }

  fn AddOne(N: Num) -> Num { N + 1 }
  fn LAddOne(L: List) -> List { TMap(L, AddOne) }
}
jerry73204 commented 4 years ago

@willcrichton You can look at how it is done here.

The crucial part is that whenever we pass something callable to generic places of a type operator, the callable thing must be a type. The Func trait delegates the type-level computation to a type.

Apart from that, using types that implement Func maybe an alternative building blocks than using trait + impl blocks. I tried it once and got stuck in the issue that the compiler failed to expand recursive traits. You can see the old compiler issue rust-lang/rust#64917.