Trait is a feature that allows you to reduce code duplication.

Why do we need trait?¶

Suppose we have two type of structure, which is People and Fruit ,

1 2 3 4 5 6 7 def People : name String : age Number def Fruit : name String : size Number

Then, suppose we have a list of People and a list of Fruit , and we wish to find out the maximum of each list.

For the People list, the maximum means the People with the largest age .

For the Fruit list, the maximum means the Fruit with the largest size .

To achieve that, we created two functions:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // Function for searching the max People in a People list def ( this List { People }). max -> People let mutable result = this . ( 0 ) for i in 1 .to ( this .length - 1 ) if this . ( i ) : age > result : age result = this . ( i ) return result // Function for searching the max Fruit in a Fruit list def ( this List { Fruit }). max -> Fruit let mutable result = this . ( 0 ) for i in 1 .to ( this .length - 1 ) if this . ( i ) : size > result : size result = this . ( i ) return result

If you've notice carefully enough, the two functions only differs by one line, which is line 5 and line 14 (as highlighted in the code snippet above).

This indicates code duplication, which is bad and dangerous.

In general, there are two ways for solving the code duplicaiton problem:

Using lambdas (a.k.a first-class functions) Using trait

Using lambda¶

To understand why trait is necessary, let us first look at how lambda can solve the problem, as shown below.

1 2 3 4 5 6 7 // Declare the max function which takes a lambda/function def ( this List { T }). maxBy ( comparer Function { Tuple { T , T } to Boolean }) -> T let mutable result = this . ( 0 ) for i in 1 .to ( this .length - 1 ) if comparer .invoke (( this . ( i ), result )) result = this . ( i ) return result

To use the function above,

1 2 // suppose `peoples` is a variable that is declared let maxPeople = peoples .maxB y ( _ : age > __ : age )

So, what's the problem with using lambdas?

Only people that understand lambdas understand the hackish looking code Code maintainability is reduced (due to 1.) Reusing the same function requires more typing (implying minor code duplication)

Note Despite the disadvantages described above, there are one major benefit of using lambdas, which is flexibility. So, in a situation where flexibility is more favorable, you should use lambdas instead of traits.

Using trait¶

In general, we need 4 steps to use trait:

Define a trait with some name Usually, the name should be an adjective which ends with the -able suffix.

For example, Comparable, Eatable, Reversible etc. Define a body-less function which uses the trait defined in step 1. Define some non-body-less function which uses the trait defined in step 1. Implement the required function needed by the traits for the data type we want

The following code demonstrates how to use trait in Pineapple.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // First, define the trait // Note that it is always in this format: // def T is <TRAIT_NAME> def T is Comparable // Second, define a body-less function which uses the Comparable trait // To achieve this, we need to use the `if` keyword // This is like saying // we need to define a function for T called `.isMoreThan` // if T is Comparable def ( this T ). isMoreThan ( that T ) -> Boolean if T is Comparable // Thirdly, define the .max function which uses the Comparable trait // We will use the `where` keyword here def ( this List { T }). max -> T where T is Comparable let mutable result = this . ( 0 ) for i in 1 .to ( this .length - 1 ) if this . ( i ). isMoreThan ( result ) // we can use the .isMoreThan function, because T is Comparable result = this . ( i ) return result

So, now we only left the last step. But, before that let's see what will happen if we missed out the last step:

1 2 3 let peoples = List { People } let eldest = peoples .ma x // Error: `People` has not implemented the `Comparable` trait

We will get an error as described in line 3, because the People type have not implemented the function we defined in step 2. And here's how we can implement it.

1 2 3 def ( this People ). isMoreThan ( that People ) -> Boolean // if People is Comparable <-- This line is not needed! return this : age > that : age

And we are done.

So, if you ever need to use the max function on your custom data type, you just need to implement the isMoreThan function for your data type.

No more copy and paste!

Differences with other languages¶

In fact, Pineapple's trait is a featured inpsired by Java/C#'s Interfaces, Haskell's Type Classes and Scala's Trait.

However, there are one major differences that makes Pineapple's trait stands out:

Using trait in Pineapple will not break existing code.

Suppose we have a data type called Animal and a function named isMoreThan .

1 2 3 4 5 6 7 8 9 // Language: Java public class Animal { public String name ; public int weight ; public boolean isHeavierThan ( Animal other ) { return this . weight > other . weight ; } }

1 2 3 4 5 6 7 8 -- Language: Haskell data Animal = Animal { name :: String , weight :: Int } isHeavierThan :: Animal -> Animal -> Bool isHeavierThan x y = ( weight x ) > ( weight y )

1 2 3 4 5 6 7 // Language: Pineapple def Animal : name String : weight Integer def ( this Animal ). isHeavierThan ( that Animal ) -> Boolean return this : weight > that : weight

Imagine that during development, we suddenly realize we need a Comparable interface/typeclasses/trait for the Animal type. So, we coded it:

1 2 3 4 // Java public interface Comparable < T > { public boolean isHeavierThan ( T other ); }

1 2 3 -- Haskell class Comparable a where isHeavierThan :: a -> a -> Bool

1 2 3 4 5 // Pineapple def T is Comparable def ( this T ). isHeavierThan ( that T ) -> Boolean if T is Comparable

However, to implement Comparable for Animal type, we need to modify the previous code as such (modification are those highlighted lines):

1 2 3 4 5 6 7 8 9 // Language: Java public class Animal implements Comparable < Animal > { public String name ; public int weight ; public boolean isHeavierThan ( Animal other ) { return this . weight > other . weight ; } }

1 2 3 4 5 6 7 8 -- Language: Haskell data Animal = Animal { name :: String , weight :: Int } instance Comparable Animal where isHeavierThan x y = ( weight x ) > ( weight y )

But, no modification is needed in the Pineapple's code!

1 2 3 4 5 6 7 // Language: Pineapple def Animal : name String : weight Integer def ( this Animal ). isHeavierThan ( that Animal ) -> Boolean return this : weight > that : weight

One advantages of such feature is that Pineapple allows an easier incremental design approach, where you do not need to think of the future too much.

Other features¶

Trait extension¶

You can extend a trait by using the extends keyword. For example,

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // Define a trait named Equatable def T is Equatable def ( this T ) == ( that T ) -> Boolean if T is Equatable def ( this T ) != ( that T ) -> Boolean where T is Equatable return ( this == that ). not def T is Comparable extends T is Equatable def ( this T ) > ( that T ) -> Boolean if T is Comparable def ( this T ) < ( that T ) -> Boolean where T is Comparable return (( this > that ). not ). and (( this == that ). not )

Using trait in data structures¶

Another application of trait is for creating generic data structure. For example, a BinaryTree:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def BinaryTree { T } where T is Comparable : current T : left BinaryTree { T } ? : right BinaryTree { T } ? def ( this BinaryTree { T } ? ). insert ( element T ) -> BinaryTree { T } where T is Comparable if this == #nil this = BinaryTree { T } : current = element : left = #nil : right = #nil elif element >= this : current this : right = this : right .insert ( element ) elif element < this : current this : left = this : left .insert ( element ) return this

Multiple type parameters trait¶

To define a trait with more than one type parameter, you need to use the following format:

1 def T1 is < TRAIT_ NAME > T2

For example,