====== Coding conventions ====== * Derived from * [[https://wiki.haskell.org/Programming_guidelines|Haskell - Programming guidelines]] * [[https://github.com/tibbe/haskell-style-guide/blob/master/haskell-style.md|Haskell Style Guide]] * Cross-link * [[codesnippets:haddockexamples|Haddock examples]] ===== General rules ===== ==== Line length ==== For source code, the line length is up to 80 characters, at the maximum. * Example: ------------------------------------------------------------------------------ |---... ... instance Formatting a => Formatting (Ds.Tree a) where format t = format' (Indent []) t where format' :: Formatting a => Indent -> (Ds.Tree a) -> String format' ind (Ds.Node s tl) = (format ind) ++ (format s) ++ "\n" ++ (prettyFormatTL' (pushIndent ind Continuing) tl) prettyFormatTL' :: Formatting a => Indent -> [Ds.Tree a] -> String prettyFormatTL' _ ([]) = "" prettyFormatTL' ind (tn:[]) = (format' (finishIndent ind) tn) prettyFormatTL' ind (tn:rtl) = (format' ind tn) ++ (prettyFormatTL' ind rtl) For comments code, the line length is up to 160 characters, at the maximum. * Example: ------------------------------------------------------------------------------ |---... ... chSetInline :: [Char] -> Char -> Integer -> [Char] chSetInline [] chToSet nCol -- there is no character at all | nCol > 0 = ' ' : (chSetInline ([]) chToSet (nCol-1)) -- -> extend with blank, recursively | nCol == 0 = chToSet : [] -- -> add character | otherwise = [] -- -> leave as is (empty) chSetInline lch0@(ch:lch) chToSet nCol -- there is at least one character | nCol > 0 = ch : (chSetInline lch chToSet (nCol-1)) -- -> go to next character, recursively | nCol == 0 = chToSet : lch -- -> set character here | otherwise = lch0 -- -> leave as is ==== Indentation ==== * four spaces * no tabs ==== Binding names in pattern and functions ==== * lower camel case * if the result has a dedicated type or is restricted to type class * then the first word (prefix) __may__ suggest a type or type class * n for numbers * see also * [[codesnippets:codingconventions#standard_prefixes|Standard prefixes]] * [[codesnippets:codingconventions#custom_prefixes|Custom prefixes]] * continues with capital speaking names * e.g. Size, Replicate * Abbreviated names if to long * if available one of the suggested below * e.g. Pos for Position, Long for Longitude, In, Out * Combined as usually written * e.g. CursorPosition * If heavy abbreviation is needed without vowels * e.g. Cmp for Composition * otherwise * the first word is a uncapitalized speaking name or a lower camel case of composed speaking names * speaking names * noun (preferable to emphasise functional programming) * e.g. count/Count, matrix/Matrix, cursor/Cursor, length/Length * verbs * e.g. take/Take, replicate/Peplicate * dependent on better readability abreviations within a name may be also written in camel case e.g. Sql for SQL, Ascii for ASCII * examples: * takeEnd * niLength * length ^ Abbreviation ^ Meaning ^ | Char | Character | | In | Input | | Out | Output | | Pos | Position | Example ''outFromCmpOut'' with function name using custom prefix ''out'' to indicate the resulting custom type: outFromCmpOut :: (ComposableOutput usrSym) -> (Output usrSym) Example ''symTree'' as matched pattern name using custom prefix ''sym'' to indicate the custom type: outFromCmpOut (CmpOut (CmpValid symTree _ _) _) = Valid symTree Example ''lErr'' as matched name using standard prefix ''l'' plus ''err'' to indicate the custom list type combined with custom prefix ''err'': outFromCmpOut (CmpOut (CmpInvalid lerr) _) = Invalid lerr ==== Standard prefixes ==== * Prefixes for __may be__ used types, type classes, and pattern ^ Type or type class ^ Prefix ^ Examples ^ | ''a'' | x | ''xMax'' for a maximum value of an arbitrary type | | ''Num =>'' | n | ''nRowPos'' for the line number of a row | | ''Int'' | n or nj | ''njExp'' for the exponent in a power operation | | ''Integer'' | n or ni | ''niLength'' to provide the length of a list | | ''Double'' | n or nd | ''ndDegreesCentigrade'' to indicate a temperature | | ''Float'' | n or nf | ''nfVolt'' to indicate a voltage | | ''Bool'' | is, do, does, are, has | ''isHead'', ''doAllOutFileExist'', ''areAllOpen'', ''hasBeenUpdated'' | | ''['''']'' | ''l'' | ''lerr'' to match with list of elements of a data type that is defined with prefix ''err'' | | ''(,)'' | ''tpl'', ''tpl2'' | ''tpl2'' just a tuple with two elements | | ''(,,<3>)'' | ''tpl'', ''tpl3'' | ''tpl'' just a tuple | | ''(:lr'') | //none//, and ''lr'' | ''(ch:lrch)'' for a list of Char | | ''l@(:lr'') | ''l'', //none//, and ''lr'' | ''lch@(ch:lrch)'' for a list of Char | | ''Char'' | ''ch'' | chRead for a charater to read | | ''String'', ''[Char]'' | ''s'', ''lch'' | ''sStream'' record name to get a string from stream | | ''Maybe '' | ''m'' | ''mch'' for a binding of type ''Maybe Char'' | | ''Either '' | ''e'' | ''en'' or ''eni'' for a binding of type ''Either String Integer'' | | ''IO '' | ''io'' | ''io'' for a function result type ''IO ()'' or ''ios'' for a function result type ''IO String'' | | ''Word8'' | ''w8'' | ''w8FromChar'' converts char to a Word8 value | | ''Word16'' | ''w16'' | | | ''Word32'' | ''w32'' | | | ''Word64'' | ''w64'' | | | ''*->*'' | ''f'' | ''fConvert'' to name a function as parameter or ''fioWrite'' for a function that writes to ''IO ()'' | * Prefixes __may be__ used for commonly used modules ^ Module ^ Prefix ^ Import directive ^ | Control.Exception.Base | Ex | ''import qualified Control.Exception.Base as Ex'' | | Data.Bits | Bts | ''import qualified Data.Bits as Bts'' | | Data.ByteString | BS | ''import qualified Data.ByteString as BS'' | | Data.Char | Chr | ''import qualified Data.Char as Chr'' | | Data.List | Lst | ''import qualified Data.List as Lst'' | | Data.Time | Tm | ''import qualified Data.Time as Tm'' | | Data.Word | Wrd | ''import qualified Data.Word as Wrd'' | | Debug.Trace | Dbg | ''import qualified Debug.Trace as Dbg'' | | Numeric | Nm | ''import qualified Numeric as Nm'' | | System.Directory | Dir | ''import qualified System.Directory as Dir'' | | System.IO | SIo | ''import qualified System.IO as SIo'' | ==== Custom prefixes ==== * Prefixes for custom types, type classes, and pattern * custom prefixes are to be declared for each data type and type class * as in the example below * first bullet: ''* prefix: '' * prefix in lower camel case * -- EOLMode -- | ...EOL (end of line) mode. {-| * prefix: eol ... -} data EOLMode = ... * example, in record syntax: * reolStream :: EOLMode, * -- Code -- | ... {-| * prefix: cd ... -} class Code cd where ... * example, in a type constraint * fx :: Code cd => cd -> Char ... * Prefixes for custom modules * example: {-| Description : supports transformation from multiple system stream inputs like files and stdin to multiple system output stream like files, stdout and stderr. Copyright : (c) Jörg K.-H. W. Brüggmann, 2021-2024 License : proprietary, to be dual licensed Maintainer : info@joerg-brueggmann.de Stability : experimental Portability : POSIX ...provides support for transformation from multiple system stream inputs like files and stdin to multiple system output stream like files, stdout and stderr. * suggested prefix: 'Mio' * import qualified MultiOctetStreamIO as Mio ===== Outline of a module ===== * [[codesnippets:codingconventions#module_description|Module description]] * [[codesnippets:codingconventions#ghc_extensions_and_options|GHC extensions and options]] * [[codesnippets:codingconventions#module_declaration_and_export_list|Module declaration and export list]] * keyword: module * [[codesnippets:codingconventions#sections|Sections]] * [[codesnippets:codingconventions#class_declaration|Class declaration]] * keyword: class * [[codesnippets:codingconventions#data_type_declaration|Data type declaration]] * keywords: data, type, newtype * [[codesnippets:codingconventions#instance_declaration|Instance declaration]] * keyword: instance * [[codesnippets:codingconventions#function_declaration|Function declaration]] ==== Module description ==== * Haddock comment * contains * description (''Description'') * in way that it becomes a complete sentence when started with module name * like in the code below it translates to ''HPowerLib.ExportTest makes it easier to export functional tests that are not exported themselves.'' * copyright according to the example below * license * All rights reserved * GPLv3+, see also file 'LICENSE' and 'README.md' * maintainer (''Maintainer''), always an email address * stability (''Stability''), possible values: * unstable * experimental * provisional * stable * frozen * portability (''Portability''), possible values: * portable * non-portable () * bullets * most important properties * expandable example * may be with output of example * ideally using doc test * example: -- >>> fact 5 -- 120 * Example: {-| Description : makes it easier to export functional tests that are not exported themselves. Copyright : (c) Jörg K.-H. W. Brüggmann, 2021-2024 License : All rights reserved Maintainer : info@joerg-brueggmann.de Stability : experimental Portability : POSIX * makes it easier to export functional tests that are not exported themselves. * suggested prefix: __/Exp/__ * See also module HPowerLib.ImportTests, which supports import of the tests into the test framework Tasty. ==== __Example code to export tests__ @ ... module HPowerLib.Parser ( ... , ... , testGroup_parseComposable ) where ... @ -} ==== GHC extensions and options ==== * after module documentation * just listed one after the other * only if needed * Example: {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE StandaloneDeriving #-} ==== Module declaration and export list ==== * with export list * each exported item classified according to sections * Example: module HPowerLib.BitSeqContext ( -- * data types and its instances Context( .. ) , Position( .. ) , LineNumber , ColumnNumber , BitPosition -- * functions regarding 'Context' , initFromOctets , initFromBits , startPositionMemory , position , isEOS -- * functions to test at context position -- ** class 'TestableEncoding' to test encodings , TestableEncoding( .. ) -- * functions to test number of available bits , testAvailableBits -- * functions to test particular sequences , testCode , testBits ) where ===== Sections ===== * in order to get better overview * module's code is sectioned by comments, as shown below * two blank lines away from the text above * also to see whether the code is 80 character wide * sections may separate * type class by type class * with its functions, if they can be associated * type by type * with its instances, if they are in the module, and can be associated * with its functions, if they can be associated * example: -------------------------------------------------------------------------------- -- data types and its instances data Context = Context { rBits :: Bits.Sequence, rPositionMemory :: ( Position, Maybe Char ) } deriving ( Eq, Show ) ... -------------------------------------------------------------------------------- -- functions regarding 'Context' initFromOctets :: Oct.Octets -> Context initFromOctets octets = Context ( Bits.Sequence Bits.NoSubOctet octets Bits.NoSubOctet ) startPositionMemory ... -------------------------------------------------------------------------------- -- functions to test at context position -------------------------------------------------------------------------------- -- class 'TestableEncoding' to test encodings class Cd.Code code => TestableEncoding code where decodeTest :: Context -> ( Maybe code, Context ) instance TestableEncoding Cd.CharUtf8 where decodeTest :: Context -> (Maybe Cd.CharUtf8, Context) decodeTest context0@( Context _ pos0 ) = ... instance TestableEncoding Cd.CharIso1 where decodeTest context0 = ... instance TestableEncoding Cd.CharWin1 where decodeTest context0 = ... nextOctet :: Context -> ( Maybe Oct.Octet, Context ) nextOctet context = ... -------------------------------------------------------------------------------- -- functions to test number of available bits -------------------------------------------------------------------------------- -- function 'testAvailableBits' testAvailableBits :: Context -> Integer -> ( Maybe Bits.Sequence, Context ) testAvailableBits context0@( Context bits0 ( pos0, _ ) ) numberOfBits = ... -------------------------------------------------------------------------------- -- functions to test particular sequences -------------------------------------------------------------------------------- -- function 'testCode' testCode :: Cd.Code cd => Context -> cd -> ( Bool, Context ) testCode context code = ( contextBits == charBits, nextContext ) ... nextCode :: Cd.Code cd => Context -> cd -> ( Bits.Sequence, Bits.Sequence, Context ) nextCode ( Context bits pos ) code = ... nextPositionMemoryFromChar :: ( Position, Maybe Char ) -> Char -> Integer -> ( Position, Maybe Char ) nextPositionMemoryFromChar ( BitPosition bitPosition0, _ ) _ nBits = ( BitPosition ( bitPosition0 + nBits ), Nothing ) nextPositionMemoryFromChar ( CharAndBitPosition lineNumber0 columnNumber0 bitPosition0, mch0 ) ch nBits = ... -------------------------------------------------------------------------------- -- function 'testBits' testBits :: Context -> Bits.Sequence -> ( Bool, Context ) testBits context bitsToTest = ... ===== Class declaration ===== * example: -- HUnit -- | ...capability to compare the expected value with the actual one, -- | and to display the difference if there is one. {-| -} class ( Show cmp ) => HUnit cmp where -- equals {- | ...compares the expected value with the actual one. * result 'True' if both values are equal, otherwise 'False' -} equals :: cmp -- ^ actual value -> cmp -- ^ expected value -> Bool -- ^ 'True' when actual value and expected value is considered equal, otherwise 'False' ===== Instance declaration ===== * example: instance Format Prs.CharCode where display ( Prs.Utf8Code code ) = show ( Cd.chFromCode code ) ++ "(UTF-8)" display ( Prs.Iso1Code code ) = show ( Cd.chFromCode code ) ++ "(iso 8859-1)" display ( Prs.Win1Code code ) = show ( Cd.chFromCode code ) ++ "(Windows-1252)" ===== Data type declaration ===== * it starts with the name in a single line comment, completed with ''...'' * continues with multi line haddock comment * short description at the very top * blank line * bullets of properties, if neccessary ==== Sum type ==== * example: -- EOLMode -- | ...EOL (end of line) mode. {-| * prefix: eol * to determine row * to control generation of new lines -} data EOLMode = -- | for Windows, a new line is "\r\n", and "\x0d\x0A", respectively WindowsEOL -- | for Unix, a new line is "\n", and "\x0A", respectively | UnixEOL -- for macOS, anew line is "\r", and "\x0d", respectively | MacEOL deriving Show ==== Product type ==== * example: -- Geoposition -- | ...geocoordinates in degrees. {-| * prefix: gps -} data Geoposition = Geoposition -- | Latitude Double -- | Longitude Double deriving Show ==== Mixed type ==== * example: -- | parser output that supports composition of all syntax elements including selections, sequences, repetitions, and so on data ComposableOutput = -- | previous input was valid or not yet parsed CmpValid -- | remaining input InputStream -- | symbol tree SymTree -- | lErrHintCmp [ErrorTree] -- | previous input was invalid | CmpInvalid -- | remaining input InputStream -- | error tree ErrorTree deriving Show ==== Record syntax ==== * indentation, line, comments and bracketing as in the example below * first line ''data ='' * second line '' {'' * haddock compliant comment, explaining the row below * record * bindings start with ''r'' to avoid shadowing when names are also used in pattern matching * the rest of the binding is as explained [[codesnippets:codingconventions#binding_names_in_pattern_and_functions|here]] * -- Stream -- | ...string as input stream. {- | * prefix: st * that starts at column 1, row 1 * going from left to right, and for each new line from top to bottom -} data Stream = Stream { -- | controls the recognition of line breaks, which is important to calculate text positions ('Pos') reolStream :: EOLMode, -- | keeps the rest of the stream if used by 'strFwdToNextChar' rsStream :: String, -- | keeps the current position of the stream rposStream :: Pos } deriving Show === In sums of algebraic types: "dangerous" === NOTE: Usage of record syntax in sums of algebraic types is "dangerous". They can lead to runtime errors. Don't use them! See the example below - went wrong. * example: someFunc :: IO () someFunc = print crash data Unsafe = UnsafeA { rniA1 :: Int, rniA2 :: Int } | UnsafeB { rniB1 :: Int, rniB2 :: Int } unsafe = UnsafeB 23 34 crash :: Int crash = rniA1 unsafe * output: XYZ-exe: No match in record selector rniA1 Rather use the safe alternative below. * example: someFunc :: IO () someFunc = print cannotCrash data Safe = SafeA Int Int | SafeB Int Int safe = SafeB 23 34 cannotCrash :: Int cannotCrash = first safe where first :: Safe -> Int first (SafeA ni1 _) = ni1 first (SafeB ni1 _) = ni1 * output: 23 ===== Function declaration ===== * function name and description should focus on what it provides, not what it does * e.g. "'Stream' that is initialised" instead of "initialises a 'Stream'" * function name in single line comment, as idicated below * Haddock comment * starts with what the function is doing * does not necessarily have to be a complete sentence * goes right into it * refers to the resulting type (in single quotes) * later bullets, after blank line, may indicate properties * all rules for [[codesnippets:codingconventions#binding_names_in_pattern_and_functions|Binding names]] apply also to function name * -- stInitalStream {-| ...'Stream' that is initialised. * from end of line mode ('EOLMode'), and content of the stream ('String') * resets posStream (:: Pos) to initial position -} stInitalStream :: EOLMode -- ^ end of line mode -> String -- ^ content of the stream -> Stream -- ^ the initialised stream stInitalStream eolMode s = Stream eolMode s initPos ===== ✎ ===== ~~DISCUSSION~~