Weekly update on development process (Oct 30, 2023)

Fairpool
4 min readOct 30, 2023

Fairpool is a DEX for personal tokens. Create your own token & receive royalties from trading volume. Increase volume by offering dividends / selling content for your token. Subscribe to our Telegram / Twitter to get notified about updates.

Weekly progress

  • ⚒ Worked on our decision-making system.

Writing reusable functions

On one hand, it’s better to split the code into multiple functions, so that the same code could be reused in multiple places. However, there should be a natural limit to it, because it’s hard to follow the code that is split into too many functions.

We propose that every function should have a single “data pipeline”. To explain this concept, we need to introduce a couple of definitions.

First, we classify the function inputs into “primary” & “secondary”. The primary input is the one being operated on, while the secondary inputs are configuration parameters for the operation. For example (in TypeScript):

```

const getRows = (query: string) => (db: Database) => {

const connection = await getConnection(db)

return executeQuery(query)(connection)

}

```

Here, the `db` is the primary input, while `query` is a secondary input.

Notes:

  • The primary input must always be the last input — this allows to combine multiple functions into a single pipeline (more on that later).
  • A function may have only a single primary input.
  • A function may have multiple secondary inputs.

Notice that we can rewrite this function in the following way:

```

const getRows = (query: string) => pipe(getConnection, executeQuery(query))

```

Here, the primary input is implicit. This allows building specializations of the functions, like this:

```

const getUsers = pipe(getRows(“SELECT * FROM users”), parseUsers)

```

In the following text, we’ll refer to pipelined functions simply as “pipelines”.

Every pipeline has a “length” — the number of functions in the pipeline. For example:

  • `getRows` has a length of 2 (calls `getConnection`, then `executeQuery(query)`)
  • `getUsers` has a length of 2 (calls `getRows`, then `parseUsers`)

Notice that we don’t know the length of `getConnection` (and maybe it’s not even a pipeline).

Notice that we may define the `length` for different depths. For example:

  • `getUsers` has a length of 2 at depth 0 (calls `getRows`, then `parseUsers`)
  • `getUsers` has a length of 3 at depth 1 (calls `getConnection`, then `executeQuery(query)`, then `UsersSchema.parse`)

In general, it is better to define multiple pipelines with small length at depth 0 — this leads to more reusable code.

Forks

Sometimes we need to execute different code depending on data. For example, we may want to allow only admins to get all users. This creates a fork in the pipeline.

```

interface WithUser {

user: User

}

interface WithDatabase {

db: Database

}

interface AuthContext extends WithUser, WithDatabase {}

type GetUsersFromAuthContext = (context: AuthContext) => Promise<Result<Error, User[]>>

const getUsersFromAuthContext: GetUsersFromAuthContext = cond(({user}) => isAdmin(user), ({ db }) => getUsers(db), () => failure(NotAdmin))

```

Notice that `getUsersFromAuthContext` requires the current `user` in addition to `db`. This is why the type signature is more complicated.

Notice that we can’t provide `getUsers` directly — since it requires a `db` input, we must get the `db` from `context` via an intermediate function.

Plucks

The use of wrapper functions is quite common, we can abstract them away:

```

const getUsersOf = withDatabase(getUsersFromDatabase)

const isAdminOf = withUser(isAdminUser)

const getUsers = cond(isAdminOf, getUsersOf, () => failure(NotAdmin))

```

Guard

The use of `cond(…, …, () => failure(NotAdmin))` is quite common, we can abstract it away, too:

```

const getUsers = guard(isAdminOf, getUsersOf, NotAdmin)

```

Comparison

```

// old-school direct code style

const getUsersOld = ({user, db}: AuthContext) => {

if (isAdmin(user)) {

return getUsers(db)

} else {

return failure(NotAdmin)

}

}

// new-school functional composition style

const getUsersNew = guard(isAdminOf, getUsersOf, NotAdmin)

```

Essence

Functional composition allows restricting control flow to just one control structure at the time.

This allows writing the code in a more succinct way:

  • No primary arguments
  • No braces.
  • No `return`, `if`, `switch` statements.

We think it distills the core logic into a local language that is very easy to understand.

Next week’s focus

  1. ⚒ Develop our decision-making system.

About Fairpool

Fairpool is a DEX for personal tokens. Create your own token & receive royalties from trading volume. Increase volume by offering dividends / selling content for your token. If you want to get notified about updates to our products, please follow our Telegram & Twitter.

Any questions? Reach out to us:

Website: fairpool.io

Telegram: @FairpoolDEX

Twitter: @FairpoolDEX

Anchor Podcasts: @FairpoolDEX

Medium: fairpool-dex.medium.com

$FAIR token: Uniswap

--

--

Fairpool

Fairpool is a DEX for personal tokens. Create your token & receive royalties from trading volume. Increase volume by offering dividends / selling content.