From d4d16b5b762805e50346824578a324fadc7ff6d8 Mon Sep 17 00:00:00 2001 From: msaug Date: Wed, 22 Feb 2023 16:54:42 +0100 Subject: [PATCH] feat: traits exercises --- exercises/traits/README.md | 11 +++++ exercises/traits/traits1.cairo | 31 ++++++++++++++ exercises/traits/traits2.cairo | 44 ++++++++++++++++++++ exercises/traits/traits3.cairo | 76 ++++++++++++++++++++++++++++++++++ info.toml | 38 ++++++++++++++++- 5 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 exercises/traits/README.md create mode 100644 exercises/traits/traits1.cairo create mode 100644 exercises/traits/traits2.cairo create mode 100644 exercises/traits/traits3.cairo diff --git a/exercises/traits/README.md b/exercises/traits/README.md new file mode 100644 index 000000000..3c8bdbccf --- /dev/null +++ b/exercises/traits/README.md @@ -0,0 +1,11 @@ +# Traits + +A trait is a collection of methods. + +Data types can implement traits. To do so, the methods making up the trait are defined for the data type. For example, the `u256` data type implements the `Into` trait. This allows a user to write `1.into()` to convert a felt into a u256. + +In this way, traits are somewhat similar to Java interfaces and C++ abstract classes. + +Because traits indicate shared behavior between data types, they are useful when writing generics. + +- [Traits & Impls](https://link.medium.com/IQGqboBerxb#83b5) diff --git a/exercises/traits/traits1.cairo b/exercises/traits/traits1.cairo new file mode 100644 index 000000000..48ccbb56f --- /dev/null +++ b/exercises/traits/traits1.cairo @@ -0,0 +1,31 @@ +// traits1.cairo +// Time to implement some traits! + +// Your task is to implement the trait +// `AnimalTrait` for the type `Animal` +// +// Execute `starklings hint traits1` or use the `hint` watch subcommand for a hint. + +// Fill in the impl block to make the code work. + +#[derive(Copy, Drop)] +struct Animal { + noise: felt +} + +trait AnimalTrait { + fn new(noise: felt) -> T; + fn make_noise(self: T) -> felt; +} + +impl AnimalImpl of AnimalTrait:: {// TODO: implement the trait AnimalTrait for Animal +} + +#[test] +fn test_traits1() { + // TODO make the test pass by creating two instances of Animal + // and calling make_noise on them + + assert(cat.make_noise() == 'meow', 'Wrong noise'); + assert(cow.make_noise() == 'moo', 'Wrong noise'); +} diff --git a/exercises/traits/traits2.cairo b/exercises/traits/traits2.cairo new file mode 100644 index 000000000..88646d235 --- /dev/null +++ b/exercises/traits/traits2.cairo @@ -0,0 +1,44 @@ +// traits2.cairo + +// The previous exercise did not make the distinction +// between different types of animals, but this one does. +// The trait `AnimalTrait` has two functions: +// `new` and `make_noise`. +// `new` should return a new instance of the type +// implementing the trait. +// `make_noise` should return the noise the animal makes. +// The types `Cat` and `Cow` are already defined for you. +// You need to implement the trait `AnimalTrait` for them. + +// No hints for this one! +// Execute `starklings hint traits2` or use the `hint` watch subcommand for a hint. + +#[derive(Copy, Drop)] +struct Cat { + noise: felt, +} + +#[derive(Copy, Drop)] +struct Cow { + noise: felt, +} + +trait AnimalTrait { + fn new() -> T; + fn make_noise(self: T) -> felt; +} + +impl CatImpl of AnimalTrait:: { + // TODO: implement the trait Animal for the type Cat +} + +// TODO: implement the trait Animal for the type Cow + +#[test] +fn test_traits2() { + let kitty: Cat = AnimalTrait::new(); + assert(kitty.make_noise() == 'meow', 'Wrong noise'); + + let cow: Cow = AnimalTrait::new(); + assert(cow.make_noise() == 'moo', 'Wrong noise'); +} diff --git a/exercises/traits/traits3.cairo b/exercises/traits/traits3.cairo new file mode 100644 index 000000000..930eb0152 --- /dev/null +++ b/exercises/traits/traits3.cairo @@ -0,0 +1,76 @@ +// traits3.cairo +// +// The previous exercise showed how to implement a trait for multiple types. +// This exercise shows how you can implement multiple traits for a single type. +// This is useful when you have types that share some common functionality, but +// also have some unique functionality. + +// Execute `starklings hint traits3` or use the `hint` watch subcommand for a hint. + +#[derive(Copy, Drop)] +struct Fish { + noise: felt, + distance: u32, +} + +#[derive(Copy, Drop)] +struct Dog { + noise: felt, + distance: u32, +} + +trait AnimalTrait { + fn new() -> T; + fn make_noise(self: T) -> felt; + fn get_distance(self: T) -> u32; +} + +trait FishTrait { + fn swim(ref self: Fish) -> (); +} + +trait DogTrait { + fn walk(ref self: Dog) -> (); +} + +impl AnimalFishImpl of AnimalTrait:: { + fn new() -> Fish { + Fish { noise: 'blub', distance: 0_u32 } + } + fn make_noise(self: Fish) -> felt { + self.noise + } + fn get_distance(self: Fish) -> u32 { + self.distance + } +} + +impl AnimalDogImpl of AnimalTrait:: { + fn new() -> Dog { + Dog { noise: 'woof', distance: 0_u32 } + } + fn make_noise(self: Dog) -> felt { + self.noise + } + fn get_distance(self: Dog) -> u32 { + self.distance + } +} + +// TODO: implement FishTrait for the type Fish + +// TODO: implement DogTrait for the type Dog + +#[test] +fn test_traits3() { + // Don't modify this test! + let mut salmon: Fish = AnimalTrait::new(); + salmon.swim(); + assert(salmon.make_noise() == 'blub', 'Wrong noise'); + assert(salmon.get_distance() == 1_u32, 'Wrong distance'); + + let mut dog: Dog = AnimalTrait::new(); + dog.walk(); + assert(dog.make_noise() == 'woof', 'Wrong noise'); + assert(dog.get_distance() == 1_u32, 'Wrong distance'); +} diff --git a/info.toml b/info.toml index 0c01469ad..2bcabc81b 100644 --- a/info.toml +++ b/info.toml @@ -286,4 +286,40 @@ For get_fees: This method takes an additional argument, is there a field in the Looking at the test functions will also help you understand more about the syntax. This section will help you understanding more about impls and traits: https://link.medium.com/c8TqX7R3qxb#83b5. -""" \ No newline at end of file +""" + + +# TRAITS + +[[exercises]] +name = "traits1" +path = "exercises/traits/traits1.cairo" +mode = "test" +hint = """ +If you want to implement a trait for a type, you have to implement all the methods in the trait. +Based on the signature of the method, you can easily implement it. + +In the test, you need to instantiate two objects of type `Animal`. +You can call the method of a trait by using the MyTrait::foo() syntax. +How would you instantiate the two objects with AnimalTrait? +Maybe you need to specify the type of the object? +""" + +[[exercises]] +name = "traits2" +path = "exercises/traits/traits2.cairo" +mode = "test" +hint = """ No hints for this one! It is very similar to the previous exercise.""" + +[[exercises]] +name = "traits3" +path = "exercises/traits/traits3.cairo" +mode = "test" +hint = """ +You can implement multiple traits for a type. +When a trait is destined to be implemented by a single type, you don't need to use generics. +If you're having trouble updating the distance value in the `Fish` and `Dog` impls, remember that you need to first +1. Destructure the object into mutable variables +2. Update the distance variable +3. Reconstruct `self` with the updated variables (`self = MyStruct { ... }`) +"""