Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative construct of type operating traits #4

Open
jerry73204 opened this issue Jul 26, 2020 · 2 comments
Open

Alternative construct of type operating traits #4

jerry73204 opened this issue Jul 26, 2020 · 2 comments

Comments

@jerry73204
Copy link

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
Copy link
Owner

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
Copy link
Author

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

  • The Func trait makes the implemented type callable.
  • The map, fold, scan, filter demo on typed list.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants