Introducción a C++
Gerson Garrido Manislla
Universidad Nacional de Ingeniería
Escuela de Ciencia de la Computación
Acerca de mí
-
Senior UI Developer at PureCars, a digital marketing and automation company in the automotive space
-
Moonlights as a consultant with his brother where they work with small businesses to create modern dashboard applications.
-
Love ultimate frisbee and watching NFL
Joel Kanzelmeyer
C++
C++
- Creado por Bjarne S. en 1983
- Multiparadigma, Orientado a objetos*
- Es un lenguaje potente
Empezemos!
/* Programa que nos muestra en consola Hola mundo!*/
// Esto también es un comentario
#include <iostream>
int main()
{
std::cout << "Hello World!";
return 0;
}
Vamos a explicar linea a linea
/* Programa que nos muestra en consola Hola mundo!*/
// Esto también es un comentario
Estos son comentarios es decir que no afectan el comportamiento del programa. Los programadores usan los comentarios para documentar el código.
#include <iostream>
Esta linea es una directivas leídas e interpretadas el preprocesador. Son líneas especiales interpretadas antes de que empiece la compilación del programa mismo. En este caso, la directiva #include <iostream>, permite realizar operaciones de entrada y salida estándar, como escribir la salida de este programa.
int main ()
La función llamada main() es una función especial en todos los programas C ++. Es la función llamada cuando se ejecuta el programa. La ejecución de todos los programas de C ++ comienza con la función principal.
continuación..
{
...
}
std::cout << "Hello World!";
La llave abierta "{" indica el comienzo de la definición de la función principal, y la llave de cierre "}" , indica su final. Todas las funciones utilizan llaves para indicar el comienzo y el final de sus definiciones.
std::cout << "Hello World!";
Esta línea es una declaración de C ++. Una declaración es una expresión que realmente puede producir algún efecto.
Esta declaración tiene tres partes: Primero, std :: cout, que signfica standard character output (salida de caracteres estándar). En segundo lugar, el operador de inserción (<<), que indica que lo que sigue se inserta en std :: cout. Finalmente, una oración entre comillas ("¡Hola mundo!"), Es el contenido insertado en la salida estándar.
Observe que la sentencia termina con un punto y coma (;). Este carácter marca el final de la declaración. Todas las instrucciones de C ++ deben finalizar con un carácter punto y coma.
Fácil de entender
Ahora vamos a ejecutarlo
Easy to reason about
(RIP Gene Wilder)
Good separation of concerns
Your business logic is kept out of your views
Developer adoption
A community of great developers support Redux
Middleware
Enhance base functionality with custom middleware
React Boilerplate
The struggle is real
JS Tool Fatigue
React Boilerplate to the rescue!
- Most stable/production-ready solution I've found
- Follows good practices for large React apps
- Server-side rendering
- Code splitting (lazy load code when needed)
- Hot module reloading (excellent for debugging)
- ES6 support and CSS modules/PostCSS
React Boilerplate to the rescue!
Best Practices
Redux at Scale
Best Practice #1
Smart containers and dumb components
Redux at Scale
Dumb Components
(Presentational)
Dumb Component Example
const DumbComponent = (props) => {
const { text, onChange } = props;
return (
<div>
<p>Your text: {text}</p>
<input value={text} onChange={onChange} />
</div>
);
};
Smart Containers
(Stateful)
Smart Container Example
//...
import { updateText } from '../actions';
//...
class SmartContainer extends React.Component {
render() {
const { text, updateText } = this.props;
return (
<DumbComponent text={text} onChange={updateText} />
);
}
}
const mapStateToProps = (state) => ({
text: state.text
});
const mapDispatchToProps = (dispatch) => ({
updateText: (evt) => dispatch(updateText(evt.target.text))
});
export default connect(mapStateToProps, mapDispatchToProps)(SmartContainer);
Benefits
- Better separation of concerns
- Better reusability
- A library of UI components
Best Practice #2
Immutability
Redux at Scale
Equality checking in Javascript
1 === 1
'string' === 'string'
true === true
const obj1 = { prop: ’someValue’ };
const obj2 = { prop: ’someValue’ };
console.log(obj1 === obj2); // false
Why is this a problem?
Fine-tuning React Performance
shouldComponentUpdate
class NewsFeed extends React.Component {
shouldComponentUpdate(nextProps) {
return !_.isEqual(this.props.articles, nextProps.articles) ||
!_.isEqual(this.props.user, nextProps.user);
}
render() {
const { user, articles } = this.props;
return (
<div>
<p>Hello, {user.firstName}! Here is your news for the day:</p>
<div>
{
articles.map((article) => (
<Article article=[article} />
))
}
</div>
</div>
);
}
}
Immutable.js
shouldComponentUpdate with Immutable.js
shouldComponentUpdate(nextProps) {
return this.props.articles !== nextProps.articles ||
this.props.user !== nextProps.user;
}
shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
react-addons-shallow-compare
Benefits
- Improves performance
- Simplifies shouldComponentUpdate
- Easier to reason about
Best Practice #3
Normalized state tree
Redux at Scale
API returns deeply nested objects
// API response
[
{
"id": 1,
"title": "Why React is Awesome",
"author": {
"id": 1,
"name": "Joel Kanzelmeyer"
}
},
{
"id": 2,
"title": "Why Redux is Great",
"author": {
"id": 1,
"name": "Joel Kanzelmeyer"
}
}
]
// Redux state
{
articles: [{
id: 1,
title: 'Why React is Awesome',
author: {
id: 1,
name: 'Joel Kanzelmeyer'
}
}, {
id: 2,
title: 'Why Redux is Great',
author: {
id: 1,
name: 'Joel Kanzelmeyer'
}
}]
}
// Redux state
{
articles: [{
id: 1,
title: 'Why React is Awesome',
author: {
id: 1,
name: 'Joel Kanzelmeyer'
}
}, {
id: 2,
title: 'Why Redux is Great',
author: {
id: 1,
name: 'Joel Cancelmeyer'
}
}]
}
normalizr
Turns this...
// API response
[
{
"id": 1,
"title": "Why React is Awesome",
"author": {
"id": 1,
"name": "Joel Kanzelmeyer"
}
},
{
"id": 2,
"title": "Why Redux is Great",
"author": {
"id": 1,
"name": "Joel Kanzelmeyer"
}
}
]
// Normalized result
{
entities: {
authors: {
1: {
id: 1,
name: 'Joel Kanzelmeyer'
}
},
articles: {
1: {
id: 1,
name: 'Why React is Awesome'
},
2: {
id: 2,
name: 'Why Redux is Great'
}
}
},
result: {
authors: [1],
articles: [1, 2]
}
}
into this.
Example with normalizr
// Redux state
{
articles: [{
id: 1,
title: 'Why React is Awesome',
author: {
id: 1,
name: 'Joel Kanzelmeyer'
}
}, {
id: 2,
title: 'Why Redux is Great',
author: {
id: 1,
name: 'Joel Kanzelmeyer'
}
}]
}
// Redux state with normalizr
{
authors: {
byId: {
1: {
id: 1,
name: 'Joel Kanzelmeyer'
}
},
all: [1]
},
articles: {
byId: {
1: {
id: 1,
title: 'Why React is Awesome',
author: 1
},
2: {
id: 2,
title: 'Why Redux is Great',
author: 1
}
},
all: [1, 2]
}
}
redux-normalizr-middleware
Action/reducer example
import { Schema, arrayOf } from 'normalizr';
// define your schemas
const article = new Schema('articles');
const author = new Schema('authors');
// define nesting rules
article.define({
author: author
});
export const getArticlesSuccess = (response) => ({
type: 'GET_ARTICLES_SUCCESS',
payload: response.body,
meta: {
// provide middleware with normalizr schema
schema: arrayOf(article)
}
});
// API response
[
{
"id": 1,
"title": "Why React is Awesome",
"author": {
"id": 1,
"name": "Joel Kanzelmeyer"
}
},
{
"id": 2,
"title": "Why Redux is Great",
"author": {
"id": 1,
"name": "Joel Kanzelmeyer"
}
}
]
// define initial state for reducers
const initialState = Immutable.Map({
byId: Immutable.Map(),
all: Immutable.Set()
});
// define default reducer that will merge in normalized state
const defaultReducer = (schemaName, state, action) => {
if (action.entities && action.entities[schemaName]) {
return state
.mergeIn(['byId'], action.entities[schemaName])
.mergeIn(['all'], action.result[schemaName]);
}
return state;
}
// authors reducer
const authors = (state = initialState, action) => {
switch (action.type) {
case 'UPDATE_AUTHOR':
return state.mergeIn(['byId', action.id], action.author);
default:
return defaultReducer('authors', state, action);
}
};
// articles reducer
const articles = (state = initialState, action) => {
return defaultReducer('articles', state, action);
};
// root reducer
const reducer = combineReducers({
authors,
articles
});
Benefits
- Normalizes deeply nested objects
- Prevents duplicate state
- Normalization can happen in middleware
Problems You'll Encounter
Redux at Scale
(and solutions to them)
Problem #1
Asynchronous/chained actions
(actions with side-effects)
Redux at Scale
Example
// API service
const getBooks = () =>
fetch('/api/books')
.then(handleErrors)
.then(parseJSON);
// View
vm.loadBooks = () => {
vm.booksLoading = true;
booksApi.getBooks()
.then((books) => {
vm.booksLoading = false;
vm.books = books;
})
.catch((err) => {
vm.booksLoading = false;
vm.error = err;
});
};
MVC
// Actions
const getBooks = () => {
return fetch('/api/books')
.then(handleErrors)
.then(parseJSON)
.then((books) => ({
type: 'GET_BOOKS',
payload: books
});
};
onComponentWillMount() {
this.props.getBooks();
// Uncaught Error: Actions must be plain objects.
// Use custom middleware for async actions.
}
React/Redux
What We Really Need
// Actions
const getBooks = () => ({
type: 'GET_BOOKS'
});
const getBooksSuccess = (books) => ({
type: 'GET_BOOKS_SUCCESS',
payload: books
});
const getBooksFailure = (error) => ({
type: 'GET_BOOKS_FAILURE',
payload: error
});
// Reducer
const booksReducer = (state, action) => {
switch (action.type) {
case 'GET_BOOKS':
return {
...state,
loading: true,
error: null
};
case 'GET_BOOKS_SUCCESS':
return {
...state,
loading: false,
books: action.payload
};
case 'GET_BOOKS_FAILURE':
return {
...state,
loading: false,
error: action.payload
};
default:
return state;
}
};
redux-thunk
Solution #1
Thunked Action
function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
dispatch(increment());
};
}
Example with redux-thunk
// Actions
// create a thunked action
const getBooks = () => {
return (dispatch, getState) => {
// check to see if books have already been fetched
if (!getState().books) {
// if not, dispatch the GET_BOOKS action
dispatch({
type: 'GET_BOOKS'
});
// then you make your http request
booksApi.getBooks()
.then((books) => {
// dispatch success action
dispatch(getBooksSuccess(books));
})
.catch((error) => {
// dispatch failure action
dispatch(getBooksFailure(error));
});
}
};
};
const getBooksSuccess = (books) => ({
type: 'GET_BOOKS_SUCCESS',
payload: books
});
const getBooksFailure = (error) => ({
type: 'GET_BOOKS_FAILURE',
payload: error
});
// in your component
onComponentWillMount() {
this.props.getBooks();
/**
dispatches GET_BOOKS and
then when response comes back
it will dispatch either
GET_BOOKS_SUCCESS or
GET_BOOKS_FAILURE
**/
}
render() {
const {
isLoading,
error,
books
} = this.props;
if (isLoading) {
return (
<div>loading...</div>
);
}
if (error) {
return (
<div>error: {error}</div>
);
}
return (
<BooksList books={books} />
);
}
redux-thunk
- Handle side-effects within your action creators
- Able to unit test, must mock redux store and http requests
redux-saga
Solution #2
(My preference, for what it's worth)
redux-saga
class UserComponent extends React.Component {
...
onSomeButtonClicked() {
const { userId, dispatch } = this.props
dispatch({type: 'USER_FETCH_REQUESTED', payload: {userId}})
}
...
}
import { takeEvery } from 'redux-saga'
import { call, put } from 'redux-saga/effects'
import Api from '...'
// worker Saga: will be fired on USER_FETCH_REQUESTED actions
function* fetchUser(action) {
try {
const user = yield call(Api.fetchUser, action.payload.userId);
yield put({type: 'USER_FETCH_SUCCEEDED', user: user});
} catch (e) {
yield put({type: 'USER_FETCH_FAILED', message: e.message});
}
}
/*
Starts fetchUser on each dispatched 'USER_FETCH_REQUESTED' action.
Allows concurrent fetches of user.
*/
function* mySaga() {
yield* takeEvery('USER_FETCH_REQUESTED', fetchUser);
}
export default mySaga;
Why I prefer redux-saga over redux-thunk
Easier to reason about
import { takeEvery } from 'redux-saga'
import { call, put, select } from 'redux-saga/effects'
import { getBooksSuccess, getBooksFailure } from './actions';
const selectBooks = (state) => state.books;
function* fetchBooks(action) {
// check to see if books exist in state already
const books = yield select(selectBooks);
if (!books) {
try {
// call booksApi.getBooks to get books from API
const _books = yield call(booksApi.getBooks);
// dispatch GET_BOOKS_SUCCESS
yield put(getBooksSuccess(_books));
} catch (e) {
// if request fails, dispatch GET_BOOKS_FAILURE
yield put(getBooksFailure(e.message));
}
}
}
// "listener" function that will listen for GET_BOOKS actions
function* booksSaga() {
yield* takeEvery('GET_BOOKS', fetchBooks);
}
export default booksSaga;
// Actions
// create a thunked action
const getBooks = () => ({
type: 'GET_BOOKS'
});
const getBooksSuccess = (books) => ({
type: 'GET_BOOKS_SUCCESS',
payload: books
});
const getBooksFailure = (error) => ({
type: 'GET_BOOKS_FAILURE',
payload: error
});
Why I prefer redux-saga over redux-thunk
Testing is simpler
import test from 'tape';
import { put, call } from 'redux-saga/effects'
import { delay } from 'redux-saga'
import { incrementAsync } from './sagas'
test('incrementAsync Saga test', (assert) => {
const gen = incrementAsync()
assert.deepEqual(
gen.next().value,
call(delay, 1000),
'incrementAsync Saga must call delay(1000)'
)
assert.deepEqual(
gen.next().value,
put({type: 'INCREMENT'}),
'incrementAsync Saga must dispatch an INCREMENT action'
)
assert.deepEqual(
gen.next(),
{ done: true, value: undefined },
'incrementAsync Saga must be done'
)
assert.end()
});
import { put, call } from 'redux-saga/effects'
import { delay } from 'redux-saga'
export function* incrementAsync() {
// use the call Effect
yield call(delay, 1000)
yield put({ type: 'INCREMENT' })
}
Why I prefer redux-saga over redux-thunk
Highly de-coupled
- Your actions no longer contain business logic
- Business logic can be encapsulated in sagas
- Add new logic to existing actions by creating sagas
redux-saga
- Keeps your actions pure
- Handle side-effects in a "separate thread" with sagas
- Able to unit test without mocking surrounding environment
Problem #2
Selecting state, especially derived state, is painful and non-performant
Redux at Scale
Example
// SomeComponent.js
...
const mapStateToProps = (state) => {
const books = state.books; // Immutable map
const filterText = state.filterText;
return {
filteredBooks: books.filter((book) => {
return book.name.includes(filterText);
})
};
};
...
export default connect(mapStateToProps)(SomeComponent);
Why is this a problem?
const mapStateToProps = (state) => {
const books = state.books; // Immutable map
const filterText = state.filterText;
return {
filteredBooks: books.filter((book) => {
return book.name.includes(filterText);
})
};
};
- mapStateToProps is called on every state change
- Shallowly compares and re-renders only when changed
- Filtering, sorting, mapping, etc always returns new instance
reselect
Solution
reselect
// selectors.js
import { createSelector } from 'reselect';
const booksSelector = (state) => state.books;
const filterTextSelector = (state) => state.filterText;
export const filteredBooksSelector = createSelector(
booksSelector,
filterTextSelector,
(books, filterText) =>
books.filter((book) => {
return book.name.includes(filterText);
})
);
// SomeComponent.js
import { filteredBooksSelector } from './selectors.js';
...
const mapStateToProps = (state) => {
return {
filteredBooks: filteredBooksSelector(state)
};
};
...
export default connect(mapStateToProps)(SomeComponent);
reselect
- Better performance via memoized selectors
- Selectors are now composable
- Added benefit of encapsulating state selection logic
Problem #3
Too much boilerplate code and too reliant on string constants
Redux at Scale
export const ADD_TODO = 'ADD_TODO';
export const REMOVE_TODO = 'REMOVE_TODO';
export const EDIT_TODO = 'EDIT_TODO';
export const COMPLETE_TODO = 'COMPLETE_TODO';
export const COMPLETE_ALL = 'COMPLETE_ALL';
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED';
import * as types from './constants';
const addTodo = (text) => ({
type: types.ADD_TODO,
payload: { text }
});
const removeTodo = (id) => ({
type: types.REMOVE_TODO,
payload: { id }
});
const editTodo = (id, text) => ({
type: types.EDIT_TODO,
payload: { id, text }
});
const completeTodo = (id) => ({
type: types.COMPLETE_TODO,
payload: { id }
});
const completeAll = () => ({
type: types.COMPLETE_ALL
});
const clearCompleted = () => ({
type: types.CLEAR_COMPLETED
});
import {
ADD_TODO, DELETE_TODO, EDIT_TODO, COMPLETE_TODO,
COMPLETE_ALL, CLEAR_COMPLETED
} from './constants';
const initialState = [
{
text: 'Use Redux',
completed: false,
id: 0
}
]
export default function todos(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return [
{
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
completed: false,
text: action.text
},
...state
]
case DELETE_TODO:
return state.filter(todo =>
todo.id !== action.id
)
case EDIT_TODO:
return state.map(todo =>
todo.id === action.id ?
{ ...todo, text: action.text } :
todo
)
case COMPLETE_TODO:
return state.map(todo =>
todo.id === action.id ?
{ ...todo, completed: !todo.completed } :
todo
)
case COMPLETE_ALL:
const areAllMarked = state.every(todo => todo.completed)
return state.map(todo => ({
...todo,
completed: !areAllMarked
}))
case CLEAR_COMPLETED:
return state.filter(todo => todo.completed === false)
default:
return state
}
}
Constants
Actions
Reducer
Example
redux-act
Solution
createAction
Simpler, more consistent way to create redux action creators
const addTodo = createAction('Add todo');
addTodo('content');
// return {
// type: '[1] Add todo',
// payload: 'content'
// }
const editTodo = createAction(
'Edit todo',
(id, content) => ({id, content})
);
editTodo(42, 'the answer');
// return {
// type: '[2] Edit todo',
// payload: {
// id: 42,
// content: 'the answer'
// }
// }
const serializeTodo = createAction('SERIALIZE_TODO');
serializeTodo(1);
// return { type: 'SERIALIZE_TODO', payload: 1 }
createReducer
Simpler, more consistent way to create redux reducers
const increment = createAction();
const add = createAction();
// First pattern
const reducerMap = createReducer({
[increment]: (state) => state + 1,
[add]: (state, payload) => state + payload
}, 0);
// Second pattern
const reducerFactory = createReducer(function (on, off) {
on(increment, (state) => state + 1);
on(add, (state, payload) => state + payload);
// 'off' remove support for a specific action
}, 0);
import { createAction } from 'redux-act';
const addTodo = createAction('ADD_TODO');
const removeTodo = createAction('REMOVE_TODO');
const editTodo = createAction('EDIT_TODO');
const completeTodo = createAction('COMPLETE_TODO');
const completeAll = createAction('COMPLETE_ALL');
const clearCompleted = createAction('CLEAR_COMPLETED');
import {
addTodo, deleteTodo, editTodo, completeTodo,
completeAll, clearCompleted
} from './actions';
import { createReducer } from 'redux-act';
const initialState = [
{
text: 'Use Redux',
completed: false,
id: 0
}
]
export default createReducer({
[addTodo]: (state, { text }) => [
{
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
completed: false,
text
},
...state
],
[deleteTodo]: (state, { id }) => state.filter(todo => todo.id !== id),
[editTodo]: (state, { id, text }) => state.map(todo =>
todo.id === id ?
{ ...todo, text } :
todo
),
[completeTodo]: (state, { id }) => state.map(todo =>
todo.id === id ?
{ ...todo, completed: !todo.completed } :
todo
),
[completeAll]: (state) => {
const areAllMarked = state.every(todo => todo.completed)
return state.map(todo => ({
...todo,
completed: !areAllMarked
}))
},
[clearCompleted]: (state) => state.filter(todo => todo.completed === false)
}, initialState)
Actions
Reducer
export const ADD_TODO = 'ADD_TODO';
export const REMOVE_TODO = 'REMOVE_TODO';
export const EDIT_TODO = 'EDIT_TODO';
export const COMPLETE_TODO = 'COMPLETE_TODO';
export const COMPLETE_ALL = 'COMPLETE_ALL';
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED';
import * as types from './constants';
const addTodo = (text) => ({
type: types.ADD_TODO,
payload: { text }
});
const removeTodo = (id) => ({
type: types.REMOVE_TODO,
payload: { id }
});
const editTodo = (id, text) => ({
type: types.EDIT_TODO,
payload: { id, text }
});
const completeTodo = (id) => ({
type: types.COMPLETE_TODO,
payload: { id }
});
const completeAll = () => ({
type: types.COMPLETE_ALL
});
const clearCompleted = () => ({
type: types.CLEAR_COMPLETED
});
import {
ADD_TODO, DELETE_TODO, EDIT_TODO, COMPLETE_TODO,
COMPLETE_ALL, CLEAR_COMPLETED
} from './constants';
const initialState = [
{
text: 'Use Redux',
completed: false,
id: 0
}
]
export default function todos(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return [
{
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
completed: false,
text: action.text
},
...state
]
case DELETE_TODO:
return state.filter(todo =>
todo.id !== action.id
)
case EDIT_TODO:
return state.map(todo =>
todo.id === action.id ?
{ ...todo, text: action.text } :
todo
)
case COMPLETE_TODO:
return state.map(todo =>
todo.id === action.id ?
{ ...todo, completed: !todo.completed } :
todo
)
case COMPLETE_ALL:
const areAllMarked = state.every(todo => todo.completed)
return state.map(todo => ({
...todo,
completed: !areAllMarked
}))
case CLEAR_COMPLETED:
return state.filter(todo => todo.completed === false)
default:
return state
}
}
Constants
Actions
Reducer
redux-act
- Reduce boilerplate
- Use actions themselves as references rather than constants
- Flux Standard Action compliant
Honorable Mentions
redux-actions
Conclusion
- Utilize "smart containers" and "dumb components"
- Use Immutable data to help performance
- A normalized state tree helps avoid duplication
- Conquer side-effects with redux-thunk or redux-saga
- Create performant, composable selectors with reselect
- Use redux-act to reduce boilerplate and simplify
Links
- ReactJS - facebook.github.io/react
- Redux - redux.js.org
- react-boilerplate - www.reactboilerplate.com
- Immutable.js - facebook.github.io/immutable-js
- normalizr - github.com/paularmstrong/normalizr
- redux-thunk - github.com/gaearon/redux-thunk
- redux-saga - github.com/yelouafi/redux-saga
- reselect - github.com/reactjs/reselect
- redux-act - github.com/pauldijou/redux-act
- redux-actions - github.com/acdlite/redux-actions
Joel Kanzelmeyer
@kanzelm3
Questions?
Copy of Taming Large React Applications w/ Redux
By gerson231294
Copy of Taming Large React Applications w/ Redux
In this talk, Joel will introduce concepts that make large React applications more scalable and maintainable. You will learn the benefits of Redux a predictable, single-way data flow model as he walks through sample code and best practices like top down approach. You’ll walk away with what you need to know to architect a React application with all the patterns that help it scale well with your team. We will cover bootstrapping a modern React application with react-boilerplate (http://reactboilerplate.com) and using libraries like redux-saga and reselect to improve code re-use, removing coupling, and reducing complexity.
- 587