Appendix III: Fission Style Guide

We focus on storytelling in our code

Type Signatures

Type signatures are required

Single-Line

Right
Wrong
Right
id :: a -> a
id x = x
Wrong
id
:: a
-> a
id x = x

Multi-Line

Right
Wrong
Right
rawList ::
( MonadRIO cfg m
, Has IPFS.BinPath cfg
, Has IPFS.Timeout cfg
, HasProcessContext cfg
, HasLogFunc cfg
)
=> m (ExitCode, Lazy.ByteString, Lazy.ByteString)
rawList = IPFSProc.run' ["bootstrap", "list"]
Wrong
rawList :: MonadRIO cfg m
=> Has IPFS.BinPath cfg
=> Has IPFS.Timeout cfg
=> HasProcessContext cfg
=> HasLogFunc cfg
=> m (ExitCode, Lazy.ByteString, Lazy.ByteString)
rawList = IPFSProc.run' ["bootstrap", "list"]

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
Right
app
|> condDebug
|> CORS.middleware
|> runner settings
|> liftIO
Wrong
app
|> condDebug
|> CORS.middleware
|> runner settings
|> liftIO

As much as possible, pipe in one direction

Right
Wrong
Right
Hku.Password <| encodeUtf8 <| Hku.password <| Hku.api <| manifest
Wrong
Hku.Password <| encodeUtf8 (manifest |> Hku.api |> Hku.password)

Break long pipelines into multiple lines where possible

Right
Wrong
Right
app
|> condDebug
|> CORS.middleware
|> runner settings
|> liftIO
Wrong
app |> condDebug |> CORS.middleware |> runner settings |> liftIO

Do not mix pipes with the bind operator

Right
Wrong
Right
app <- Web.app
app
|> condDebug
|> CORS.middleware
|> runner settings
|> liftIO
Wrong
Web.app
>>= condDebug
|> CORS.middleware
|> runner settings
|> liftIO

Avoid the Point-Free Style

Refrain from defining functions in point-free style:

Right
Wrong
Right
docs :: Web.Host -> Swagger
docs host' =
host'
|> app (Proxy @Web.API)
|> dns
|> ipfs
|> heroku
|> ping
|> user
Wrong
docs :: Web.Host -> Swagger
docs = app (Proxy @Web.API)
. dns
. ipfs
. heroku
. ping
. user

However, they often do read well in a pipeline:

Right
Acceptable
Right
alphaNum :: MonadIO m => Natural -> m Text
alphaNum len =
len
|> bsRandomLength
|> fmap (decodeUtf8Lenient . BS.filter isAsciiAlphaNum)
Acceptable
alphaNum :: MonadIO m => Natural -> m Text
alphaNum len =
len
|> bsRandomLength
|> fmap (\str ->
str
|> BS.filter isAsciiAlphaNum
|> decodeUtf8Lenient)

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
Right
fromHandler :: MonadIO m => Handler a -> m a
fromHandler handler =
liftIO <| runHandler handler >>= \case
Right inner -> pure inner
Left servantErr -> throwM servantErr
-- In use...
server appHost = Web.Swagger.server fromHandler appHost
:<|> IPFS.server
:<|> const Heroku.server
:<|> User.server
:<|> pure Ping.pong
:<|> DNS.server
Wrong
fromHandler :: Handler a -> IO a
fromHandler handler =
runHandler handler >>= \case
Right inner -> pure inner
Left servantErr -> throwM servantErr
-- In use...
server appHost = Web.Swagger.server (liftIO . fromHandler) appHost
:<|> IPFS.server
:<|> const Heroku.server
:<|> User.server
:<|> pure Ping.pong
:<|> DNS.server