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

[book] Adds the Pattern: Newtype page #112

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
- [Pattern: Capability](./programmability/capability.md)
- [Epoch and Time](./programmability/epoch-and-time.md)
- [Collections](./programmability/collections.md)
- [Pattern: Newtype](./programmability/newtype-pattern.md)
- [Dynamic Fields](./programmability/dynamic-fields.md)
- [Dynamic Object Fields](./programmability/dynamic-object-fields.md)
- [Dynamic Collections](./programmability/dynamic-collections.md)
Expand Down
5 changes: 2 additions & 3 deletions book/src/programmability/collections.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,5 @@ if the two `VecSet` instances contain the same elements.

## Next Steps

In the next section we will cover [Dynamic Fields](./dynamic-fields.md) - an important primitive
that allows for [Dynamic Collections](./dynamic-collections.md) - a way to store large collections
of data in a more flexible, yet more expensive way.
In the next section we will cover the [Newtype Pattern](./newtype-pattern.md) - a design pattern
often used with collection types to extend or restrict their behavior.
55 changes: 55 additions & 0 deletions book/src/programmability/newtype-pattern.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Pattern: Newtype

Sometimes there's a need to create a new type that behaves similarly to another type, but with some
modifications or restrictions. For example, you might want to create a
[collection type](./collections.md) which behaves like a `vector` but doesn't allow modifying the
elements after they've been inserted. Newtype pattern is a good way to achieve this.

## Definition

The newtype pattern is a design pattern where you create a new type that wraps an existing type. The
new type is a distinct type from the original type, but it may be converted to and from the original
type.

Often it is implemented as a positional struct with a single field.

```move
{{#include ../../../packages/samples/sources/programmability/newtype-pattern.move:main}}
```

## Common Practices

For cases when the goal is extending the behavior of an existing type, it is fairly common to
provide accessors to the wrapped type. This allows the user to still access the underlying type
directly if needed. For example, in the following code, we provide `inner()` and `inner_mut()`
methods to the `Stack` type.

```move
{{#include ../../../packages/samples/sources/programmability/newtype-pattern.move:common}}
```

## Advantages

The newtype pattern has several benefits:

- Allows defining custom functions for an existing type.
- Constrains function signatures to the newtype, thereby making the code more robust.
- Often increases the readability of the code by providing a more descriptive type name.

## Disadvantages

The newtype pattern is a very powerful pattern in two scenarios: when you want to limit the behavior
of an existing type and provide a custom interface to the same data structure, and when you want to
extend the behavior of an existing type. However, it does have some limitations:

- It can be verbose to implement, especially if you want to expose all the methods of the wrapped
type.
- The implementation can be quite sparse, as it often just forwards the calls to the wrapped type.

## Next Steps

Newtype is a very useful pattern which goes hand in hand with the collection types, which we have
demonstrated in the previous section. In the next section we will cover
[Dynamic Fields](./dynamic-fields.md) - an important primitive that allows for
[Dynamic Collections](./dynamic-collections.md) - a way to store large collections of data in a more
flexible, yet more expensive way.
2 changes: 1 addition & 1 deletion packages/samples/sources/move-basics/struct.move
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#[allow(unused_variable, unused_field)]
module book::struct_syntax;

use std::string::{Self, String};
use std::string::String;

// ANCHOR: def
/// A struct representing an artist.
Expand Down
35 changes: 35 additions & 0 deletions packages/samples/sources/programmability/newtype-pattern.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

// ANCHOR: main
module book::newtype_pattern;

/// Very simple stack implementation using the newtype pattern. Does not allow
/// accessing the elements unless they are popped.
public struct Stack<T>(vector<T>) has copy, store, drop;

/// Push an element to the stack.
public fun push_back<T>(v: &mut Stack<T>, el: T) {
v.0.push_back(el);
}

/// Pop an element from the stack. Unlike `vector`, this function won't
/// fail if the stack is empty and will return `None` instead.
public fun pop_back<T>(v: &mut Stack<T>): Option<T> {
if (v.0.length() == 0) option::none()
else option::some(v.0.pop_back())
}

/// Get the size of the stack.
public fun size<T>(v: &Stack<T>): u64 {
v.0.length()
}
// ANCHOR_END: main

// ANCHOR: common
/// Allows reading the contents of the `Stack`.
public fun inner<T>(v: &Stack<T>): &vector<T> { &v.0 }

/// Allows mutable access to the contents of the `Stack`.
public fun inner_mut<T>(v: &mut Stack<T>): &mut vector<T> { &mut v.0 }
// ANCHOR_END: common
Loading