Appendix III: Fission Style Guide
We focus on storytelling in our code

Type Signatures

Type signatures are required

Single-Line

Right
Wrong
1
id :: a -> a
2
id x = x
Copied!
1
id
2
:: a
3
-> a
4
id x = x
Copied!

Multi-Line

Right
Wrong
1
rawList ::
2
( MonadRIO cfg m
3
, Has IPFS.BinPath cfg
4
, Has IPFS.Timeout cfg
5
, HasProcessContext cfg
6
, HasLogFunc cfg
7
)
8
=> m (ExitCode, Lazy.ByteString, Lazy.ByteString)
9
rawList = IPFSProc.run' ["bootstrap", "list"]
Copied!
1
rawList :: MonadRIO cfg m
2
=> Has IPFS.BinPath cfg
3
=> Has IPFS.Timeout cfg
4
=> HasProcessContext cfg
5
=> HasLogFunc cfg
6
=> m (ExitCode, Lazy.ByteString, Lazy.ByteString)
7
rawList = IPFSProc.run' ["bootstrap", "list"]
Copied!
Why is it like this? A major reason is that the syntax highlighter in VS Code breaks with the :: on the next line. We've also reverted to using tuple-style constraints, because in practive they making distinguishing between the constraints and argument types.

Pipes

Indent multi-line pipelines by 2 from the first item. This makes it consistent in do-notation and normal notations.
Right
Wrong
1
app
2
|> condDebug
3
|> CORS.middleware
4
|> runner settings
5
|> liftIO
Copied!
1
app
2
|> condDebug
3
|> CORS.middleware
4
|> runner settings
5
|> liftIO
Copied!
As much as possible, pipe in one direction
Right
Wrong
1
Hku.Password <| encodeUtf8 <| Hku.password <| Hku.api <| manifest
Copied!
1
Hku.Password <| encodeUtf8 (manifest |> Hku.api |> Hku.password)
Copied!
Break long pipelines into multiple lines where possible
Right
Wrong
1
app
2
|> condDebug
3
|> CORS.middleware
4
|> runner settings
5
|> liftIO
6
Copied!
1
app |> condDebug |> CORS.middleware |> runner settings |> liftIO
Copied!
Do not mix pipes with the bind operator
Right
Wrong
1
app <- Web.app
2
3
app
4
|> condDebug
5
|> CORS.middleware
6
|> runner settings
7
|> liftIO
Copied!
1
Web.app
2
>>= condDebug
3
|> CORS.middleware
4
|> runner settings
5
|> liftIO
Copied!

Avoid the Point-Free Style

Refrain from defining functions in point-free style:
Right
Wrong
1
docs :: Web.Host -> Swagger
2
docs host' =
3
host'
4
|> app (Proxy @Web.API)
5
|> dns
6
|> ipfs
7
|> heroku
8
|> ping
9
|> user
Copied!
1
docs :: Web.Host -> Swagger
2
docs = app (Proxy @Web.API)
3
. dns
4
. ipfs
5
. heroku
6
. ping
7
. user
8
Copied!
However, they often do read well in a pipeline:
Right
Acceptable
1
alphaNum :: MonadIO m => Natural -> m Text
2
alphaNum len =
3
len
4
|> bsRandomLength
5
|> fmap (decodeUtf8Lenient . BS.filter isAsciiAlphaNum)
Copied!
1
alphaNum :: MonadIO m => Natural -> m Text
2
alphaNum len =
3
len
4
|> bsRandomLength
5
|> fmap (\str ->
6
str
7
|> BS.filter isAsciiAlphaNum
8
|> decodeUtf8Lenient)
Copied!

MonadIO

Whenever possible, generalize IO functions to MonadIO. This prevents you from needing to generalize the function at the call site, while still able to use it in IO contexts.
Right
Wrong
1
fromHandler :: MonadIO m => Handler a -> m a
2
fromHandler handler =
3
liftIO <| runHandler handler >>= \case
4
Right inner -> pure inner
5
Left servantErr -> throwM servantErr
6
7
-- In use...
8
9
server appHost = Web.Swagger.server fromHandler appHost
10
:<|> IPFS.server
11
:<|> const Heroku.server
12
:<|> User.server
13
:<|> pure Ping.pong
14
:<|> DNS.server
Copied!
1
fromHandler :: Handler a -> IO a
2
fromHandler handler =
3
runHandler handler >>= \case
4
Right inner -> pure inner
5
Left servantErr -> throwM servantErr
6
7
-- In use...
8
9
server appHost = Web.Swagger.server (liftIO . fromHandler) appHost
10
:<|> IPFS.server
11
:<|> const Heroku.server
12
:<|> User.server
13
:<|> pure Ping.pong
14
:<|> DNS.server
Copied!
Last modified 1yr ago