Appendix III: Fission Style Guide

We focus on storytelling in our code

Type Signatures

Type signatures are required

Single-Line

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

Multi-Line

Right
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"]
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
app
|> condDebug
|> CORS.middleware
|> runner settings
|> liftIO
app
|> condDebug
|> CORS.middleware
|> runner settings
|> liftIO
As much as possible, pipe in one direction
Right
Wrong
Hku.Password <| encodeUtf8 <| Hku.password <| Hku.api <| manifest
Hku.Password <| encodeUtf8 (manifest |> Hku.api |> Hku.password)
Break long pipelines into multiple lines where possible
Right
Wrong
app
|> condDebug
|> CORS.middleware
|> runner settings
|> liftIO
app |> condDebug |> CORS.middleware |> runner settings |> liftIO
Do not mix pipes with the bind operator
Right
Wrong
app <- Web.app
app
|> condDebug
|> CORS.middleware
|> runner settings
|> liftIO
Web.app
>>= condDebug
|> CORS.middleware
|> runner settings
|> liftIO

Avoid the Point-Free Style

Refrain from defining functions in point-free style:
Right
Wrong
docs :: Web.Host -> Swagger
docs host' =
host'
|> app (Proxy @Web.API)
|> dns
|> ipfs
|> heroku
|> ping
|> user
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
alphaNum :: MonadIO m => Natural -> m Text
alphaNum len =
len
|> bsRandomLength
|> fmap (decodeUtf8Lenient . BS.filter isAsciiAlphaNum)
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
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
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