Created
November 22, 2020 15:04
-
-
Save tfausak/eebeecf592c8f7ef8bbb576d85f4d616 to your computer and use it in GitHub Desktop.
2020 State of Haskell Survey
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# language OverloadedStrings #-} | |
module Main ( main ) where | |
import qualified Control.Monad as Monad | |
import qualified Data.Aeson as Aeson | |
import qualified Data.Aeson.Encode.Pretty as Aeson | |
import qualified Data.Aeson.Types as Aeson | |
import qualified Data.Bifunctor as Bifunctor | |
import qualified Data.ByteString.Lazy as LazyByteString | |
import qualified Data.Csv as Csv | |
import qualified Data.HashMap.Strict as HashMap | |
import qualified Data.List as List | |
import qualified Data.List.NonEmpty as NonEmpty | |
import qualified Data.Map as Map | |
import qualified Data.Maybe as Maybe | |
import qualified Data.Ord as Ord | |
import qualified Data.Text as Text | |
import qualified Data.Time as Time | |
import qualified Data.Vector as Vector | |
import qualified Lucid | |
import qualified System.Directory as Directory | |
import qualified System.FilePath as FilePath | |
import qualified Text.Read as Read | |
main :: IO () | |
main = do | |
putStrLn "Reading responses ..." | |
let directory = "s3" :: FilePath | |
files <- Directory.listDirectory directory | |
responses <- Monad.forM files $ \ file -> do | |
let path = FilePath.combine directory file | |
contents <- LazyByteString.readFile path | |
either fail pure $ Aeson.eitherDecode contents | |
putStrLn "Generating JSON ..." | |
LazyByteString.writeFile "2020-state-of-haskell-survey-results.json" $ | |
Aeson.encodePretty responses | |
putStrLn "Generating CSV ..." | |
LazyByteString.writeFile "2020-state-of-haskell-survey-results.csv" $ | |
Csv.encodeDefaultOrderedByName responses | |
putStrLn "Generating language extension CSV ..." | |
LazyByteString.writeFile "2020-haskell-language-extensions.csv" | |
. Csv.encodeDefaultOrderedByName | |
. fmap Extension | |
. Map.toAscList | |
. fmap (Bifunctor.bimap length length . List.partition id) | |
. Map.fromListWith (<>) | |
. fmap (\ (k, v) -> (k, [v])) | |
$ concatMap (Map.toList . responseS2Q5) responses | |
putStrLn "Generating HTML ..." | |
LazyByteString.writeFile "2020-state-of-haskell-survey-results.html" | |
. LazyByteString.concatMap (\ x -> LazyByteString.pack $ if x == 0x3c then [0x0a, x] else [x]) | |
. Lucid.renderBS | |
. Lucid.doctypehtml_ $ do | |
Lucid.head_ $ do | |
Lucid.meta_ [Lucid.charset_ "utf-8"] | |
Lucid.title_ "2020 State of Haskell Survey Results" | |
Lucid.body_ $ do | |
Lucid.h1_ "2020 State of Haskell Survey Results" | |
Lucid.style_ $ Text.unlines | |
[ ".row { position: relative; }" | |
, ".bar { height: 100%; left: 0; max-width: 100%; position: absolute; top: 0; }" | |
, ".b1 { background-color: #9e9ac8; }" -- purple | |
, ".b2 { background-color: #cbc9e2; }" -- light purple | |
, ".b3 { background-color: #67a9cf; }" -- blue | |
, ".b4 { background-color: #ef8a62; }" -- red | |
, ".percent, .count, .choice { display: inline-block; position: relative; }" | |
, ".percent, .count { text-align: right; width: 3em; }" | |
, ".choice { padding-left: 1em; }" | |
] | |
Lucid.ol_ | |
. Monad.forM_ sections | |
$ \ section -> Lucid.li_ $ do | |
Lucid.a_ [Lucid.href_ $ "#" <> sectionName section] | |
. Lucid.toHtml | |
$ sectionTitle section | |
Lucid.ol_ | |
. Monad.forM_ (sectionQuestions section) | |
$ \ question -> Lucid.li_ | |
. Lucid.a_ [Lucid.href_ $ "#" <> questionName section question] | |
. Lucid.toHtml | |
$ questionPrompt question | |
Monad.forM_ sections $ \ section -> do | |
Lucid.h2_ [Lucid.id_ $ sectionName section] | |
. Lucid.toHtml | |
$ sectionTitle section | |
Monad.forM_ (sectionQuestions section) $ \ question -> do | |
Lucid.h3_ [Lucid.id_ $ questionName section question] | |
. Lucid.toHtml | |
$ questionPrompt question | |
case questionExpect question of | |
ExpectSingle xs -> do | |
Lucid.p_ "Optional. Single select." | |
makeSingleChart xs (fetch section question) responses | |
ExpectMulti other xs -> do | |
let allowedOther = other == OtherAllow | |
Lucid.p_ $ do | |
"Optional. Multi select. " | |
if allowedOther | |
then "Allowed 'other'." | |
else "Did not allow 'other'." | |
let threshold = Time.UTCTime (Time.fromGregorian 2020 11 2) (Time.timeOfDayToTime $ Time.TimeOfDay 12 28 9) | |
makeMultiChart | |
(xs <> if allowedOther then ["Other"] else []) | |
(\ r -> | |
( responseFinish r > threshold | |
, fmap (\ x -> if elem x xs then x else "Other") $ fetch section question r | |
)) | |
responses | |
ExpectEmail -> do | |
Lucid.p_ "Providing an email address was optional this year." | |
makeSingleChart | |
["Provided", "Not provided"] | |
(\ r -> [if null $ responseS0Q0 r then "Not provided" else "Provided"]) | |
responses | |
ExpectLikert -> do | |
Lucid.p_ "Optional. Single select." | |
makeSingleChart | |
["Strongly disagree", "Disagree", "Neutral", "Agree", " Strongly agree"] | |
(fetch section question) | |
responses | |
ExpectFree -> Lucid.p_ "Optional. Free response answers were collected but not analyzed." | |
ExpectExtension xs -> do | |
Lucid.p_ "Optional. Multi select. Did not allow 'other'." | |
makeExtensionChart xs responses | |
putStrLn "Done." | |
newtype Extension | |
= Extension (Text.Text, (Int, Int)) | |
deriving (Eq, Show) | |
instance Csv.DefaultOrdered Extension where | |
headerOrder = const $ Vector.fromList | |
[ "Name" | |
, "Pro" | |
, "Con" | |
] | |
instance Csv.ToNamedRecord Extension where | |
toNamedRecord (Extension (name, (pro, con))) = Csv.namedRecord | |
[ Csv.namedField "Name" name | |
, Csv.namedField "Pro" pro | |
, Csv.namedField "Con" con | |
] | |
fetch :: Section -> Question -> Response -> [Text.Text] | |
fetch section question = case (sectionIndex section, questionIndex question) of | |
-- (0, 0) -> responseS0Q0 -- email | |
(0, 1) -> responseS0Q1 | |
(0, 2) -> responseS0Q2 | |
(0, 3) -> responseS0Q3 | |
(0, 4) -> responseS0Q4 | |
(0, 5) -> responseS0Q5 | |
(0, 6) -> responseS0Q6 | |
(0, 7) -> responseS0Q7 | |
(0, 8) -> responseS0Q8 | |
(0, 9) -> responseS0Q9 | |
(0, 10) -> responseS0Q10 | |
(0, 11) -> responseS0Q11 | |
(0, 12) -> responseS0Q12 | |
(1, 0) -> responseS1Q0 | |
(1, 1) -> responseS1Q1 | |
(1, 2) -> responseS1Q2 | |
(1, 3) -> responseS1Q3 | |
(2, 0) -> responseS2Q0 | |
(2, 1) -> responseS2Q1 | |
(2, 2) -> responseS2Q2 | |
(2, 3) -> responseS2Q3 | |
(2, 4) -> responseS2Q4 | |
-- (2, 5) -> responseS2Q5 -- extensions | |
(2, 6) -> responseS2Q6 | |
(3, 0) -> responseS3Q0 | |
(3, 1) -> responseS3Q1 | |
(3, 2) -> responseS3Q2 | |
(3, 3) -> responseS3Q3 | |
(3, 4) -> responseS3Q4 | |
(3, 5) -> responseS3Q5 | |
(4, 0) -> responseS4Q0 | |
(4, 1) -> responseS4Q1 | |
(5, 0) -> responseS5Q0 | |
(5, 1) -> responseS5Q1 | |
(6, 0) -> responseS6Q0 | |
(6, 1) -> responseS6Q1 | |
(6, 2) -> responseS6Q2 | |
(6, 3) -> responseS6Q3 | |
(6, 4) -> responseS6Q4 | |
(6, 5) -> responseS6Q5 | |
(6, 6) -> responseS6Q6 | |
(6, 7) -> responseS6Q7 | |
(6, 8) -> responseS6Q8 | |
(6, 9) -> responseS6Q9 | |
(6, 10) -> responseS6Q10 | |
(6, 11) -> responseS6Q11 | |
(6, 12) -> responseS6Q12 | |
(6, 13) -> responseS6Q13 | |
(6, 14) -> responseS6Q14 | |
(6, 15) -> responseS6Q15 | |
(6, 16) -> responseS6Q16 | |
(6, 17) -> responseS6Q17 | |
(6, 18) -> responseS6Q18 | |
(6, 19) -> responseS6Q19 | |
(6, 20) -> responseS6Q20 | |
(6, 21) -> responseS6Q21 | |
(6, 22) -> responseS6Q22 | |
(6, 23) -> responseS6Q23 | |
(7, 0) -> responseS7Q0 | |
(7, 1) -> responseS7Q1 | |
(7, 2) -> responseS7Q2 | |
(7, 3) -> responseS7Q3 | |
(7, 4) -> responseS7Q4 | |
(7, 5) -> responseS7Q5 | |
(7, 6) -> responseS7Q6 | |
(7, 7) -> responseS7Q7 | |
(7, 8) -> responseS7Q8 | |
(7, 9) -> responseS7Q9 | |
(7, 10) -> responseS7Q10 | |
(7, 11) -> responseS7Q11 | |
(8, 0) -> responseS8Q0 | |
(8, 1) -> responseS8Q1 | |
t -> error $ show t | |
makeExtensionChart | |
:: [Text.Text] | |
-> [Response] | |
-> Lucid.Html () | |
makeExtensionChart extensions responses = let | |
xs = filter (not . null) $ fmap responseS2Q5 responses | |
total = length xs | |
ys = Map.fromListWith (<>) $ concatMap (fmap (\ (k, v) -> (k, [v])) . Map.toList) xs | |
ts = fmap | |
(\ ext -> let | |
zs = Map.findWithDefault [] ext ys | |
ps = filter id zs -- votes "for" this extension | |
cs = filter not zs -- votes "against" this extension | |
cp = length ps -- count of pros | |
cc = length cs -- count of cons | |
pp = showText (round (100 * fromIntegral cp / fromIntegral total :: Double) :: Int) <> "%" -- percent of pros | |
pc = showText (round (100 * fromIntegral cc / fromIntegral total :: Double) :: Int) <> "%" -- percent of cons | |
in (ext, ((pp, pc), (cp, cc)))) | |
extensions | |
in Lucid.div_ [Lucid.class_ "answer"] | |
. Monad.forM_ (List.sortOn (\ (_, (_, (cp, cc))) -> (Ord.Down cp, cc)) ts) $ \ (ext, ((pp, pc), (cp, cc))) -> | |
Lucid.div_ [Lucid.class_ "row"] $ do | |
Lucid.div_ [Lucid.class_ "bar b3", Lucid.style_ $ "width: " <> pp] mempty | |
Lucid.div_ [Lucid.class_ "bar b4", Lucid.style_ $ "left: " <> pp <> "; width: " <> pc] mempty | |
Lucid.div_ [Lucid.class_ "percent"] $ Lucid.toHtml $ "+" <> pp | |
Lucid.div_ [Lucid.class_ "percent"] . Lucid.toHtml $ "-" <> pc | |
Lucid.div_ [Lucid.class_ "count"] . Lucid.toHtml $ "+" <> show cp | |
Lucid.div_ [Lucid.class_ "count"] . Lucid.toHtml $ "-" <> show cc | |
Lucid.div_ [Lucid.class_ "choice"] $ Lucid.toHtml ext | |
makeMultiChart | |
:: [Text.Text] | |
-> (Response -> (Bool, [Text.Text])) | |
-> [Response] | |
-> Lucid.Html () | |
makeMultiChart choices f responses = let | |
xs = filter (not . null . snd) $ fmap f responses | |
total = length xs | |
in Lucid.div_ [Lucid.class_ "answer"] | |
. Monad.forM_ choices $ \ choice -> Lucid.div_ [Lucid.class_ "row"] $ do | |
let | |
goods = filter (== choice) . concat . fmap snd $ filter fst xs | |
bads = filter (== choice) . concat . fmap snd $ filter (not . fst) xs | |
countGoods = length goods | |
countBads = length bads | |
percentGood = showText (round (100 * fromIntegral countGoods / fromIntegral total :: Double) :: Int) <> "%" | |
percentBad = showText (round (100 * fromIntegral countBads / fromIntegral total :: Double) :: Int) <> "%" | |
Lucid.div_ [Lucid.class_ "bar b1", Lucid.style_ $ "width: " <> percentGood] mempty | |
Lucid.div_ [Lucid.class_ "bar b2", Lucid.style_ $ "left: " <> percentGood <> "; width: " <> percentBad] mempty | |
Lucid.div_ [Lucid.class_ "percent"] $ Lucid.toHtml percentGood | |
Lucid.div_ [Lucid.class_ "percent"] . Lucid.toHtml $ "+" <> percentBad | |
Lucid.div_ [Lucid.class_ "count"] . Lucid.toHtml $ show countGoods | |
Lucid.div_ [Lucid.class_ "count"] . Lucid.toHtml $ "+" <> show countBads | |
Lucid.div_ [Lucid.class_ "choice"] $ Lucid.toHtml choice | |
makeSingleChart | |
:: [Text.Text] | |
-> (Response -> [Text.Text]) | |
-> [Response] | |
-> Lucid.Html () | |
makeSingleChart choices f responses = let | |
xs = filter (not . null) $ fmap f responses | |
total = length xs | |
in Lucid.div_ [Lucid.class_ "answer"] | |
. Monad.forM_ choices $ \ choice -> Lucid.div_ [Lucid.class_ "row"] $ do | |
let | |
ys = filter (== choice) $ concat xs | |
count = length ys | |
percent = showText (round (100 * fromIntegral count / fromIntegral total :: Double) :: Int) <> "%" | |
Lucid.div_ [Lucid.class_ "bar b1", Lucid.style_ $ "width: " <> percent] mempty | |
Lucid.div_ [Lucid.class_ "percent"] $ Lucid.toHtml percent | |
Lucid.div_ [Lucid.class_ "count"] . Lucid.toHtml $ show count | |
Lucid.div_ [Lucid.class_ "choice"] $ Lucid.toHtml choice | |
sectionName :: Section -> Text.Text | |
sectionName section = "s" <> showText (sectionIndex section) | |
questionName :: Section -> Question -> Text.Text | |
questionName section question = sectionName section | |
<> "q" <> showText (questionIndex question) | |
showText :: Show a => a -> Text.Text | |
showText = Text.pack . show | |
data Response = Response | |
{ responseStart :: Time.UTCTime | |
, responseFinish :: Time.UTCTime | |
, responseS0Q0 :: [Text.Text] | |
, responseS0Q1 :: [Text.Text] | |
, responseS0Q2 :: [Text.Text] | |
, responseS0Q3 :: [Text.Text] | |
, responseS0Q4 :: [Text.Text] | |
, responseS0Q5 :: [Text.Text] | |
, responseS0Q6 :: [Text.Text] | |
, responseS0Q7 :: [Text.Text] | |
, responseS0Q8 :: [Text.Text] | |
, responseS0Q9 :: [Text.Text] | |
, responseS0Q10 :: [Text.Text] | |
, responseS0Q11 :: [Text.Text] | |
, responseS0Q12 :: [Text.Text] | |
, responseS1Q0 :: [Text.Text] | |
, responseS1Q1 :: [Text.Text] | |
, responseS1Q2 :: [Text.Text] | |
, responseS1Q3 :: [Text.Text] | |
, responseS2Q0 :: [Text.Text] | |
, responseS2Q1 :: [Text.Text] | |
, responseS2Q2 :: [Text.Text] | |
, responseS2Q3 :: [Text.Text] | |
, responseS2Q4 :: [Text.Text] | |
, responseS2Q5 :: Map.Map Text.Text Bool | |
, responseS2Q6 :: [Text.Text] | |
, responseS3Q0 :: [Text.Text] | |
, responseS3Q1 :: [Text.Text] | |
, responseS3Q2 :: [Text.Text] | |
, responseS3Q3 :: [Text.Text] | |
, responseS3Q4 :: [Text.Text] | |
, responseS3Q5 :: [Text.Text] | |
, responseS4Q0 :: [Text.Text] | |
, responseS4Q1 :: [Text.Text] | |
, responseS5Q0 :: [Text.Text] | |
, responseS5Q1 :: [Text.Text] | |
, responseS6Q0 :: [Text.Text] | |
, responseS6Q1 :: [Text.Text] | |
, responseS6Q2 :: [Text.Text] | |
, responseS6Q3 :: [Text.Text] | |
, responseS6Q4 :: [Text.Text] | |
, responseS6Q5 :: [Text.Text] | |
, responseS6Q6 :: [Text.Text] | |
, responseS6Q7 :: [Text.Text] | |
, responseS6Q8 :: [Text.Text] | |
, responseS6Q9 :: [Text.Text] | |
, responseS6Q10 :: [Text.Text] | |
, responseS6Q11 :: [Text.Text] | |
, responseS6Q12 :: [Text.Text] | |
, responseS6Q13 :: [Text.Text] | |
, responseS6Q14 :: [Text.Text] | |
, responseS6Q15 :: [Text.Text] | |
, responseS6Q16 :: [Text.Text] | |
, responseS6Q17 :: [Text.Text] | |
, responseS6Q18 :: [Text.Text] | |
, responseS6Q19 :: [Text.Text] | |
, responseS6Q20 :: [Text.Text] | |
, responseS6Q21 :: [Text.Text] | |
, responseS6Q22 :: [Text.Text] | |
, responseS6Q23 :: [Text.Text] | |
, responseS7Q0 :: [Text.Text] | |
, responseS7Q1 :: [Text.Text] | |
, responseS7Q2 :: [Text.Text] | |
, responseS7Q3 :: [Text.Text] | |
, responseS7Q4 :: [Text.Text] | |
, responseS7Q5 :: [Text.Text] | |
, responseS7Q6 :: [Text.Text] | |
, responseS7Q7 :: [Text.Text] | |
, responseS7Q8 :: [Text.Text] | |
, responseS7Q9 :: [Text.Text] | |
, responseS7Q10 :: [Text.Text] | |
, responseS7Q11 :: [Text.Text] | |
, responseS8Q0 :: [Text.Text] | |
, responseS8Q1 :: [Text.Text] | |
} deriving (Eq, Show) | |
instance Aeson.FromJSON Response where | |
parseJSON = Aeson.withObject "Response" $ \ object -> let | |
req = fmap first . required object | |
opt = fmap (maybe [] unwrapList) . optional object | |
ext = pure . flip getExtensions object | |
in Response | |
<$> req "started-at" | |
<*> req "finished-at" | |
<*> opt "section-0-question-0" | |
<*> opt "section-0-question-1" | |
<*> opt "section-0-question-2" | |
<*> opt "section-0-question-3" | |
<*> opt "section-0-question-4" | |
<*> opt "section-0-question-5" | |
<*> opt "section-0-question-6" | |
<*> opt "section-0-question-7" | |
<*> opt "section-0-question-8" | |
<*> opt "section-0-question-9" | |
<*> opt "section-0-question-10" | |
<*> opt "section-0-question-11" | |
<*> opt "section-0-question-12" | |
<*> opt "section-1-question-0" | |
<*> opt "section-1-question-1" | |
<*> opt "section-1-question-2" | |
<*> opt "section-1-question-3" | |
<*> opt "section-2-question-0" | |
<*> opt "section-2-question-1" | |
<*> opt "section-2-question-2" | |
<*> opt "section-2-question-3" | |
<*> opt "section-2-question-4" | |
<*> ext "section-2-question-5" | |
<*> opt "section-2-question-6" | |
<*> opt "section-3-question-0" | |
<*> opt "section-3-question-1" | |
<*> opt "section-3-question-2" | |
<*> opt "section-3-question-3" | |
<*> opt "section-3-question-4" | |
<*> opt "section-3-question-5" | |
<*> opt "section-4-question-0" | |
<*> opt "section-4-question-1" | |
<*> opt "section-5-question-0" | |
<*> opt "section-5-question-1" | |
<*> opt "section-6-question-0" | |
<*> opt "section-6-question-1" | |
<*> opt "section-6-question-2" | |
<*> opt "section-6-question-3" | |
<*> opt "section-6-question-4" | |
<*> opt "section-6-question-5" | |
<*> opt "section-6-question-6" | |
<*> opt "section-6-question-7" | |
<*> opt "section-6-question-8" | |
<*> opt "section-6-question-9" | |
<*> opt "section-6-question-10" | |
<*> opt "section-6-question-11" | |
<*> opt "section-6-question-12" | |
<*> opt "section-6-question-13" | |
<*> opt "section-6-question-14" | |
<*> opt "section-6-question-15" | |
<*> opt "section-6-question-16" | |
<*> opt "section-6-question-17" | |
<*> opt "section-6-question-18" | |
<*> opt "section-6-question-19" | |
<*> opt "section-6-question-20" | |
<*> opt "section-6-question-21" | |
<*> opt "section-6-question-22" | |
<*> opt "section-6-question-23" | |
<*> opt "section-7-question-0" | |
<*> opt "section-7-question-1" | |
<*> opt "section-7-question-2" | |
<*> opt "section-7-question-3" | |
<*> opt "section-7-question-4" | |
<*> opt "section-7-question-5" | |
<*> opt "section-7-question-6" | |
<*> opt "section-7-question-7" | |
<*> opt "section-7-question-8" | |
<*> opt "section-7-question-9" | |
<*> opt "section-7-question-10" | |
<*> opt "section-7-question-11" | |
<*> opt "section-8-question-0" | |
<*> opt "section-8-question-1" | |
instance Aeson.ToJSON Response where | |
toJSON response = let | |
pair k f = k Aeson..= f response | |
in Aeson.object | |
[ pair "startedAt" responseStart | |
, pair "finishedAt" responseFinish | |
-- , pair "s0q0" responseS0Q0 -- email | |
, pair "s0q1" responseS0Q1 | |
, pair "s0q2" responseS0Q2 | |
, pair "s0q3" responseS0Q3 | |
, pair "s0q4" responseS0Q4 | |
, pair "s0q5" responseS0Q5 | |
, pair "s0q6" responseS0Q6 | |
, pair "s0q7" responseS0Q7 | |
, pair "s0q8" responseS0Q8 | |
, pair "s0q9" responseS0Q9 | |
, pair "s0q10" responseS0Q10 | |
, pair "s0q11" responseS0Q11 | |
, pair "s0q12" responseS0Q12 | |
, pair "s1q0" responseS1Q0 | |
, pair "s1q1" responseS1Q1 | |
, pair "s1q2" responseS1Q2 | |
, pair "s1q3" responseS1Q3 | |
, pair "s2q0" responseS2Q0 | |
, pair "s2q1" responseS2Q1 | |
, pair "s2q2" responseS2Q2 | |
, pair "s2q3" responseS2Q3 | |
, pair "s2q4" responseS2Q4 | |
, pair "s2q5" responseS2Q5 | |
, pair "s2q6" responseS2Q6 | |
, pair "s3q0" responseS3Q0 | |
, pair "s3q1" responseS3Q1 | |
, pair "s3q2" responseS3Q2 | |
, pair "s3q3" responseS3Q3 | |
, pair "s3q4" responseS3Q4 | |
, pair "s3q5" responseS3Q5 | |
, pair "s4q0" responseS4Q0 | |
, pair "s4q1" responseS4Q1 | |
, pair "s5q0" responseS5Q0 | |
, pair "s5q1" responseS5Q1 | |
, pair "s6q0" responseS6Q0 | |
, pair "s6q1" responseS6Q1 | |
, pair "s6q2" responseS6Q2 | |
, pair "s6q3" responseS6Q3 | |
, pair "s6q4" responseS6Q4 | |
, pair "s6q5" responseS6Q5 | |
, pair "s6q6" responseS6Q6 | |
, pair "s6q7" responseS6Q7 | |
, pair "s6q8" responseS6Q8 | |
, pair "s6q9" responseS6Q9 | |
, pair "s6q10" responseS6Q10 | |
, pair "s6q11" responseS6Q11 | |
, pair "s6q12" responseS6Q12 | |
, pair "s6q13" responseS6Q13 | |
, pair "s6q14" responseS6Q14 | |
, pair "s6q15" responseS6Q15 | |
, pair "s6q16" responseS6Q16 | |
, pair "s6q17" responseS6Q17 | |
, pair "s6q18" responseS6Q18 | |
, pair "s6q19" responseS6Q19 | |
, pair "s6q20" responseS6Q20 | |
, pair "s6q21" responseS6Q21 | |
, pair "s6q22" responseS6Q22 | |
, pair "s6q23" responseS6Q23 | |
, pair "s7q0" responseS7Q0 | |
, pair "s7q1" responseS7Q1 | |
, pair "s7q2" responseS7Q2 | |
, pair "s7q3" responseS7Q3 | |
, pair "s7q4" responseS7Q4 | |
, pair "s7q5" responseS7Q5 | |
, pair "s7q6" responseS7Q6 | |
, pair "s7q7" responseS7Q7 | |
, pair "s7q8" responseS7Q8 | |
, pair "s7q9" responseS7Q9 | |
, pair "s7q10" responseS7Q10 | |
, pair "s7q11" responseS7Q11 | |
, pair "s8q0" responseS8Q0 | |
, pair "s8q1" responseS8Q1 | |
] | |
instance Csv.DefaultOrdered Response where | |
headerOrder = const $ Vector.fromList | |
[ "startedAt" | |
, "finishedAt" | |
-- , "s0q0" -- email | |
, "s0q1" | |
, "s0q2" | |
, "s0q3" | |
, "s0q4" | |
, "s0q5" | |
, "s0q6" | |
, "s0q7" | |
, "s0q8" | |
, "s0q9" | |
, "s0q10" | |
, "s0q11" | |
, "s0q12" | |
, "s1q0" | |
, "s1q1" | |
, "s1q2" | |
, "s1q3" | |
, "s2q0" | |
, "s2q1" | |
, "s2q2" | |
, "s2q3" | |
, "s2q4" | |
, "s2q5" | |
, "s2q6" | |
, "s3q0" | |
, "s3q1" | |
, "s3q2" | |
, "s3q3" | |
, "s3q4" | |
, "s3q5" | |
, "s4q0" | |
, "s4q1" | |
, "s5q0" | |
, "s5q1" | |
, "s6q0" | |
, "s6q1" | |
, "s6q2" | |
, "s6q3" | |
, "s6q4" | |
, "s6q5" | |
, "s6q6" | |
, "s6q7" | |
, "s6q8" | |
, "s6q9" | |
, "s6q10" | |
, "s6q11" | |
, "s6q12" | |
, "s6q13" | |
, "s6q14" | |
, "s6q15" | |
, "s6q16" | |
, "s6q17" | |
, "s6q18" | |
, "s6q19" | |
, "s6q20" | |
, "s6q21" | |
, "s6q22" | |
, "s6q23" | |
, "s7q0" | |
, "s7q1" | |
, "s7q2" | |
, "s7q3" | |
, "s7q4" | |
, "s7q5" | |
, "s7q6" | |
, "s7q7" | |
, "s7q8" | |
, "s7q9" | |
, "s7q10" | |
, "s7q11" | |
, "s8q0" | |
, "s8q1" | |
] | |
instance Csv.ToNamedRecord Response where | |
toNamedRecord response = let | |
field k f = Csv.namedField k $ f response | |
format = Time.formatTime Time.defaultTimeLocale "%Y-%m-%dT%H:%M:%S" | |
commas = Text.intercalate "," | |
in Csv.namedRecord | |
[ field "startedAt" $ format . responseStart | |
, field "finishedAt" $ format . responseFinish | |
-- , field "s0q0" responseS0Q0 -- email | |
, field "s0q1" $ commas . responseS0Q1 | |
, field "s0q2" $ commas . responseS0Q2 | |
, field "s0q3" $ commas . responseS0Q3 | |
, field "s0q4" $ commas . responseS0Q4 | |
, field "s0q5" $ commas . responseS0Q5 | |
, field "s0q6" $ commas . responseS0Q6 | |
, field "s0q7" $ commas . responseS0Q7 | |
, field "s0q8" $ commas . responseS0Q8 | |
, field "s0q9" $ commas . responseS0Q9 | |
, field "s0q10" $ commas . responseS0Q10 | |
, field "s0q11" $ commas . responseS0Q11 | |
, field "s0q12" $ commas . responseS0Q12 | |
, field "s1q0" $ commas . responseS1Q0 | |
, field "s1q1" $ commas . responseS1Q1 | |
, field "s1q2" $ commas . responseS1Q2 | |
, field "s1q3" $ commas . responseS1Q3 | |
, field "s2q0" $ commas . responseS2Q0 | |
, field "s2q1" $ commas . responseS2Q1 | |
, field "s2q2" $ commas . responseS2Q2 | |
, field "s2q3" $ commas . responseS2Q3 | |
, field "s2q4" $ commas . responseS2Q4 | |
, field "s2q5" $ commas . fmap (\ (k, v) -> (if v then "+" else "-") <> k) . Map.toAscList . responseS2Q5 | |
, field "s2q6" $ commas . responseS2Q6 | |
, field "s3q0" $ commas . responseS3Q0 | |
, field "s3q1" $ commas . responseS3Q1 | |
, field "s3q2" $ commas . responseS3Q2 | |
, field "s3q3" $ commas . responseS3Q3 | |
, field "s3q4" $ commas . responseS3Q4 | |
, field "s3q5" $ commas . responseS3Q5 | |
, field "s4q0" $ commas . responseS4Q0 | |
, field "s4q1" $ commas . responseS4Q1 | |
, field "s5q0" $ commas . responseS5Q0 | |
, field "s5q1" $ commas . responseS5Q1 | |
, field "s6q0" $ commas . responseS6Q0 | |
, field "s6q1" $ commas . responseS6Q1 | |
, field "s6q2" $ commas . responseS6Q2 | |
, field "s6q3" $ commas . responseS6Q3 | |
, field "s6q4" $ commas . responseS6Q4 | |
, field "s6q5" $ commas . responseS6Q5 | |
, field "s6q6" $ commas . responseS6Q6 | |
, field "s6q7" $ commas . responseS6Q7 | |
, field "s6q8" $ commas . responseS6Q8 | |
, field "s6q9" $ commas . responseS6Q9 | |
, field "s6q10" $ commas . responseS6Q10 | |
, field "s6q11" $ commas . responseS6Q11 | |
, field "s6q12" $ commas . responseS6Q12 | |
, field "s6q13" $ commas . responseS6Q13 | |
, field "s6q14" $ commas . responseS6Q14 | |
, field "s6q15" $ commas . responseS6Q15 | |
, field "s6q16" $ commas . responseS6Q16 | |
, field "s6q17" $ commas . responseS6Q17 | |
, field "s6q18" $ commas . responseS6Q18 | |
, field "s6q19" $ commas . responseS6Q19 | |
, field "s6q20" $ commas . responseS6Q20 | |
, field "s6q21" $ commas . responseS6Q21 | |
, field "s6q22" $ commas . responseS6Q22 | |
, field "s6q23" $ commas . responseS6Q23 | |
, field "s7q0" $ commas . responseS7Q0 | |
, field "s7q1" $ commas . responseS7Q1 | |
, field "s7q2" $ commas . responseS7Q2 | |
, field "s7q3" $ commas . responseS7Q3 | |
, field "s7q4" $ commas . responseS7Q4 | |
, field "s7q5" $ commas . responseS7Q5 | |
, field "s7q6" $ commas . responseS7Q6 | |
, field "s7q7" $ commas . responseS7Q7 | |
, field "s7q8" $ commas . responseS7Q8 | |
, field "s7q9" $ commas . responseS7Q9 | |
, field "s7q10" $ commas . responseS7Q10 | |
, field "s7q11" $ commas . responseS7Q11 | |
, field "s8q0" $ commas . responseS8Q0 | |
, field "s8q1" $ commas . responseS8Q1 | |
] | |
required :: Aeson.FromJSON a => Aeson.Object -> Text.Text -> Aeson.Parser a | |
required object key = object Aeson..: key | |
optional :: Aeson.FromJSON a => Aeson.Object -> Text.Text -> Aeson.Parser (Maybe a) | |
optional object key = object Aeson..:? key | |
-- The extension answers look like this: | |
-- | |
-- > { "section-2-question-5-choice-0-value": "OverloadedStrings" | |
-- > , "section-2-question-5-choice-0": "yes" | |
-- > , ... } | |
-- | |
-- This function is responsible for crunching that into this: | |
-- | |
-- > { "OverloadedStrings": true | |
-- > , ... } | |
getExtensions :: Text.Text -> Aeson.Object -> Map.Map Text.Text Bool | |
getExtensions prefix | |
= Map.fromList | |
. Maybe.mapMaybe (\ xs -> let | |
(maybeExtension, maybeWanted) = foldr | |
(\ e (ml, mr) -> case e of | |
Left l -> (Just l, mr) | |
Right r -> (ml, Just r)) | |
(Nothing, Nothing) | |
xs | |
in (,) <$> maybeExtension <*> maybeWanted) | |
. Map.elems | |
. Map.fromListWith (<>) | |
. (\ xs -> xs :: [(Int, [Either Text.Text Bool])]) | |
. Maybe.mapMaybe (\ (key, value) -> do | |
Monad.guard . not $ Text.isSuffixOf "-time" key | |
noPrefix <- Text.stripPrefix (prefix <> "-choice-") key | |
case Text.stripSuffix "-value" noPrefix of | |
Nothing -> do | |
index <- Read.readMaybe $ Text.unpack noPrefix | |
case Aeson.fromJSON value of | |
Aeson.Error _ -> Nothing | |
Aeson.Success xs -> pure (index, fmap (Right . isYes) $ unwrapList xs) | |
Just noSuffix -> do | |
index <- Read.readMaybe $ Text.unpack noSuffix | |
case Aeson.fromJSON value of | |
Aeson.Error _ -> Nothing | |
Aeson.Success xs -> pure (index, fmap Left $ unwrapList xs)) | |
. HashMap.toList | |
isYes :: Text.Text -> Bool | |
isYes = (==) "yes" | |
data List a | |
= One a | |
| Many (NonEmpty.NonEmpty a) | |
deriving (Eq, Show) | |
instance Aeson.FromJSON a => Aeson.FromJSON (List a) where | |
parseJSON value = case value of | |
Aeson.Array _ -> fmap Many $ Aeson.parseJSON value | |
_ -> fmap One $ Aeson.parseJSON value | |
unwrapList :: List a -> [a] | |
unwrapList list = case list of | |
One x -> [x] | |
Many xs -> NonEmpty.toList xs | |
first :: List a -> a | |
first list = case list of | |
One x -> x | |
Many xs -> NonEmpty.head xs | |
newtype Number | |
= Number Double | |
deriving (Eq, Show) | |
instance Aeson.FromJSON Number where | |
parseJSON = Aeson.withText "Number" $ | |
either fail (pure . Number) . Read.readEither . Text.unpack | |
data Section = Section | |
{ sectionIndex :: Int | |
, sectionTitle :: Text.Text | |
, sectionQuestions :: [Question] | |
} deriving (Eq, Show) | |
data Question = Question | |
{ questionIndex :: Int | |
, questionPrompt :: Text.Text | |
, questionExpect :: Expect | |
} deriving (Eq, Show) | |
data Expect | |
= ExpectEmail | |
| ExpectSingle [Text.Text] | |
| ExpectLikert | |
| ExpectMulti Other [Text.Text] | |
| ExpectExtension [Text.Text] | |
| ExpectFree | |
deriving (Eq, Show) | |
data Other | |
= OtherAllow | |
| OtherReject | |
deriving (Eq, Show) | |
sections :: [Section] | |
sections = | |
[ Section 0 "Haskell usage" | |
[ Question 0 "What is your email address?" ExpectEmail | |
, Question 1 "Do you use Haskell?" $ ExpectSingle | |
[ "Yes" | |
, "No, but I used to" | |
, "No, I never have" | |
] | |
, Question 2 "If you stopped using Haskell, how long did you use it before you stopped?" $ ExpectSingle | |
[ "Less than 1 day" | |
, "1 day to 1 week" | |
, "1 week to 1 month" | |
, "1 month to 1 year" | |
, "More than 1 year" | |
, "n/a" | |
] | |
, Question 3 "If you do not use Haskell, why not?" $ ExpectMulti OtherAllow | |
[ "Haskell does not support the platforms I need" | |
, "Haskell is too hard to learn" | |
, "Haskell lacks critical features" | |
, "Haskell lacks critical libraries" | |
, "Haskell lacks critical tools" | |
, "Haskell's documentation is not good enough" | |
, "Haskell's performance is not good enough" | |
, "My company doesn't use Haskell" | |
] | |
, Question 4 "How long have you been using Haskell?" $ ExpectSingle | |
[ "Less than 1 day" | |
, "1 day to 1 week" | |
, "1 week to 1 month" | |
, "1 month to 1 year" | |
, "1 year to 2 years" | |
, "2 years to 3 years" | |
, "3 years to 4 years" | |
, "4 years to 5 years" | |
, "5 years to 6 years" | |
, "6 years to 7 years" | |
, "7 years to 8 years" | |
, "8 years to 9 years" | |
, "9 years to 10 years" | |
, "10 years to 15 years" | |
, "15 years to 20 years" | |
, "More than 20 years" | |
] | |
, Question 5 "How frequently do you use Haskell?" $ ExpectSingle | |
[ "Daily" | |
, "Weekly" | |
, "Monthly" | |
, "Yearly" | |
, "Rarely" | |
] | |
, Question 6 "How would you rate your proficiency in Haskell?" $ ExpectSingle | |
[ "Beginner" | |
, "Intermediate" | |
, "Advanced" | |
, "Expert" | |
, "Master" | |
] | |
, Question 7 "Where do you use Haskell?" $ ExpectMulti OtherReject | |
[ "Academia" | |
, "Home" | |
, "Industry" | |
, "School" | |
] | |
, Question 8 "Do you use Haskell at work?" $ ExpectSingle | |
[ "Yes, most of the time" | |
, "Yes, some of the time" | |
, "No, but my company does" | |
, "No, but I'd like to" | |
, "No, and I don't want to" | |
] | |
, Question 9 "If you do not use Haskell at work, why not?" $ ExpectMulti OtherAllow | |
[ "Haskell does not support the platforms I need" | |
, "Haskell is too hard to learn" | |
, "Haskell lacks critical features" | |
, "Haskell lacks critical libraries" | |
, "Haskell lacks critical tools" | |
, "Haskell's documentation is not good enough" | |
, "Haskell's performance is not good enough" | |
, "It's too hard to hire Haskell developers" | |
, "My company doesn't use Haskell" | |
] | |
, Question 10 "Which programming languages other than Haskell are you fluent in?" $ ExpectMulti OtherAllow | |
[ "Assembly" | |
, "C" | |
, "C#" | |
, "C++" | |
, "Clojure" | |
, "Elm" | |
, "Erlang" | |
, "F#" | |
, "Go" | |
, "Groovy" | |
, "Hack" | |
, "Java" | |
, "JavaScript" | |
, "Julia" | |
, "Kotlin" | |
, "Lua" | |
, "Matlab" | |
, "Objective-C" | |
, "Ocaml" | |
, "Perl" | |
, "PHP" | |
, "PureScript" | |
, "Python" | |
, "R" | |
, "Ruby" | |
, "Rust" | |
, "Scala" | |
, "Shell" | |
, "Swift" | |
, "TypeScript" | |
, "VBA" | |
, "VB.NET" | |
] | |
, Question 11 "Which types of software do you develop with Haskell?" $ ExpectMulti OtherAllow | |
[ "Agents or daemons" | |
, "API services (returning non-HTML)" | |
, "Automation or scripts" | |
, "Command-line programs (CLI)" | |
, "Data processing" | |
, "Desktop programs (GUI)" | |
, "Libraries or frameworks" | |
, "Web services (returning HTML)" | |
] | |
, Question 12 "Which industries do you use Haskell in?" $ ExpectMulti OtherAllow | |
[ "Academia" | |
, "Banking or finance" | |
, "Commerce or retail" | |
, "Cryptocurrency" | |
, "Education" | |
, "Embedded" | |
, "Gaming" | |
, "Government" | |
, "Healthcare or medical" | |
, "Mobile" | |
, "Science" | |
, "Web" | |
] | |
] | |
, Section 1 "Projects" | |
[ Question 0 "How many Haskell projects do you contribute to?" $ ExpectSingle | |
[ "0" | |
, "1" | |
, "2" | |
, "3" | |
, "4" | |
, "5" | |
, "6 to 10" | |
, "11 to 20" | |
, "More than 20" | |
] | |
, Question 1 "What is the total size of all the Haskell projects you contribute to?" $ ExpectSingle | |
[ "Less than 1,000 lines of code" | |
, "Between 1,000 and 9,999 lines of code" | |
, "Between 10,000 and 99,999 lines of code" | |
, "More than 100,000 lines of code" | |
] | |
, Question 2 "Which platforms do you develop Haskell on?" $ ExpectMulti OtherAllow | |
[ "BSD" | |
, "Linux" | |
, "MacOS" | |
, "Windows" | |
] | |
, Question 3 "Which platforms do you target?" $ ExpectMulti OtherAllow | |
[ "Android" | |
, "BSD" | |
, "iOS" | |
, "Linux" | |
, "MacOS" | |
, "Windows" | |
] | |
] | |
, Section 2 "Compilers" | |
[ Question 0 "Which Haskell compilers do you use?" $ ExpectMulti OtherAllow | |
[ "GHC" | |
, "GHCJS" | |
] | |
, Question 1 "Which installation methods do you use for your Haskell compiler?" $ ExpectMulti OtherAllow | |
[ "Source" | |
, "Official binaries" | |
, "Minimal installer" | |
, "Haskell Platform" | |
, "Operating system package" | |
, "Stack" | |
, "Nix" | |
, "ghcup" | |
] | |
, Question 2 "Has upgrading your Haskell compiler broken your code in the last year?" $ ExpectSingle | |
[ "Yes" | |
, "No" | |
] | |
, Question 3 "How has upgrading your Haskell compiler broken your code in the last year?" $ ExpectMulti OtherAllow | |
[ "Compiler bugs" | |
, "Expected changes, such as the MonadFail proposal" | |
, "Incompatible dependencies" | |
, "New warnings" | |
, "Unexpected changes" | |
] | |
, Question 4 "Which versions of GHC do you use?" $ ExpectMulti OtherAllow | |
[ "> 8.10" | |
, "8.10.x" | |
, "8.8.x" | |
, "8.6.x" | |
, "8.4.x" | |
, "8.2.x" | |
, "8.0.x" | |
, "< 8.0" | |
] | |
, Question 5 "Which language extensions would you like to be enabled by default?" $ ExpectExtension | |
[ "AllowAmbiguousTypes" | |
, "ApplicativeDo" | |
, "Arrows" | |
, "BangPatterns" | |
, "BinaryLiterals" | |
, "BlockArguments" | |
, "CApiFFI" | |
, "ConstrainedClassMethods" | |
, "ConstraintKinds" | |
, "Cpp" | |
, "DataKinds" | |
, "DatatypeContexts" | |
, "DefaultSignatures" | |
, "DeriveAnyClass" | |
, "DeriveDataTypeable" | |
, "DeriveFoldable" | |
, "DeriveFunctor" | |
, "DeriveGeneric" | |
, "DeriveLift" | |
, "DeriveTraversable" | |
, "DerivingStrategies" | |
, "DerivingVia" | |
, "DisambiguateRecordFields" | |
, "DuplicateRecordFields" | |
, "EmptyCase" | |
, "ExistentialQuantification" | |
, "ExplicitForAll" | |
, "ExplicitNamespaces" | |
, "ExtendedDefaultRules" | |
, "FlexibleContexts" | |
, "FlexibleInstances" | |
, "ForeignFunctionInterface" | |
, "FunctionalDependencies" | |
, "GADTs" | |
, "GADTSyntax" | |
, "GeneralizedNewtypeDeriving" | |
, "HexFloatLiterals" | |
, "ImplicitParams" | |
, "ImportQualifiedPost" | |
, "ImpredicativeTypes" | |
, "IncoherentInstances" | |
, "InstanceSigs" | |
, "InterruptibleFFI" | |
, "KindSignatures" | |
, "LambdaCase" | |
, "LiberalTypeSynonyms" | |
, "MagicHash" | |
, "MonadComprehensions" | |
, "MonoLocalBinds" | |
, "MultiParamTypeClasses" | |
, "MultiWayIf" | |
, "NamedFieldPuns" | |
, "NamedWildCards" | |
, "NegativeLiterals" | |
, "NoEmptyDataDecls" | |
, "NoImplicitPrelude" | |
, "NoMonadFailDesugaring" | |
, "NoMonomorphismRestriction" | |
, "NoPatternGuards" | |
, "NoStarIsType" | |
, "NoTraditionalRecordSyntax" | |
, "NPlusKPatterns" | |
, "NullaryTypeClasses" | |
, "NumDecimals" | |
, "NumericUnderscores" | |
, "OverlappingInstances" | |
, "OverloadedLabels" | |
, "OverloadedLists" | |
, "OverloadedStrings" | |
, "PackageImports" | |
, "ParallelListComp" | |
, "PartialTypeSignatures" | |
, "PatternSynonyms" | |
, "PolyKinds" | |
, "PostfixOperators" | |
, "QuantifiedConstraints" | |
, "QuasiQuotes" | |
, "Rank2Types" | |
, "RankNTypes" | |
, "RebindableSyntax" | |
, "RecordWildCards" | |
, "RecursiveDo" | |
, "RoleAnnotations" | |
, "ScopedTypeVariables" | |
, "StandaloneDeriving" | |
, "StandaloneKindSignatures" | |
, "StaticPointers" | |
, "Strict" | |
, "StrictData" | |
, "TemplateHaskell" | |
, "TemplateHaskellQuotes" | |
, "TransformListComp" | |
, "Trustworthy" | |
, "TupleSections" | |
, "TypeApplications" | |
, "TypeFamilies" | |
, "TypeFamilyDependencies" | |
, "TypeInType" | |
, "TypeOperators" | |
, "TypeSynonymInstances" | |
, "UnboxedSums" | |
, "UnboxedTuples" | |
, "UndecidableInstances" | |
, "UndecidableSuperClasses" | |
, "UnicodeSyntax" | |
, "UnliftedNewtypes" | |
, "Unsafe" | |
, "ViewPatterns" | |
] | |
, Question 6 "How important do you feel it would be to have a new version of the Haskell language standard?" $ ExpectSingle | |
[ "Extremely important" | |
, "Very important" | |
, "Moderately important" | |
, "Slightly important" | |
, "Not at all important" | |
] | |
] | |
, Section 3 "Tooling" | |
[ Question 0 "Which build tools do you use for Haskell?" $ ExpectMulti OtherAllow | |
[ "Bazel" | |
, "Cabal" | |
, "ghc-pkg" | |
, "Mafia" | |
, "Make" | |
, "Nix" | |
, "Shake" | |
, "Stack" | |
] | |
, Question 1 "Which editors do you use for Haskell?" $ ExpectMulti OtherAllow | |
[ "Atom" | |
, "Emacs family" | |
, "IntelliJ IDEA" | |
, "Notepad++" | |
, "Sublime Text" | |
, "Vi family" | |
, "Visual Studio Code" | |
] | |
, Question 2 "Which version control systems do you use for Haskell?" $ ExpectMulti OtherAllow | |
[ "Darcs" | |
, "Git" | |
, "Mercurial" | |
, "Pijul" | |
, "Subversion" | |
] | |
, Question 3 "Where do you get Haskell packages from?" $ ExpectMulti OtherAllow | |
[ "Hackage" | |
, "Nix" | |
, "Source" | |
, "Stackage" | |
] | |
, Question 4 "Which tools do you use to test Haskell code?" $ ExpectMulti OtherAllow | |
[ "Haskell Test Framework" | |
, "Hedgehog" | |
, "Hspec" | |
, "HUnit" | |
, "QuickCheck" | |
, "SmallCheck" | |
, "Tasty" | |
] | |
, Question 5 "Which tools do you use to benchmark Haskell code?" $ ExpectMulti OtherAllow | |
[ "Bench" | |
, "Criterion" | |
, "Gauge" | |
] | |
] | |
, Section 4 "Infrastructure" | |
[ Question 0 "Which tools do you use to deploy Haskell applications?" $ ExpectMulti OtherAllow | |
[ "Docker images" | |
, "Dynamic binaries" | |
, "Nix expressions" | |
, "Static binaries" | |
] | |
, Question 1 "Where do you deploy Haskell applications?" $ ExpectMulti OtherAllow | |
[ "Amazon Web Services" | |
, "Digital Ocean" | |
, "Google Cloud" | |
, "Heroku" | |
, "Microsoft Azure" | |
, "Self or company owned servers" | |
] | |
] | |
, Section 5 "Community" | |
[ Question 0 "Where do you interact with the Haskell community?" $ ExpectMulti OtherAllow | |
[ "Conferences (academic)" | |
, "Conferences (commercial)" | |
, "Discord" | |
, "Discourse" | |
, "GitHub" | |
, "Gitter" | |
, "IRC" | |
, "Lobsters" | |
, "Mailing lists" | |
, "Mastodon" | |
, "Matrix/Riot" | |
, "Meetups" | |
, "Reddit" | |
, "Slack" | |
, "Stack Overflow" | |
, "Telegram" | |
, "Twitter" | |
] | |
, Question 1 "Which of the following Haskell topics would you like to see more written about?" $ ExpectMulti OtherAllow | |
[ "Algorithm implementations" | |
, "Application architectures" | |
, "Beginner fundamentals" | |
, "Best practices" | |
, "Case studies" | |
, "Comparisons to other languages" | |
, "Debugging how-tos" | |
, "Design patterns" | |
, "Game development" | |
, "GUIs" | |
, "Library walkthroughs" | |
, "Machine learning" | |
, "Mobile development" | |
, "Performance analysis" | |
, "Production infrastructure" | |
, "Project maintenance" | |
, "Project setup" | |
, "Tooling choices" | |
, "Web development" | |
] | |
] | |
, Section 6 "Feelings" | |
[ Question 0 "I feel welcome in the Haskell community." ExpectLikert | |
, Question 1 "I am satisfied with Haskell as a language." ExpectLikert | |
, Question 2 "I am satisfied with Haskell's compilers, such as GHC." ExpectLikert | |
, Question 3 "I am satisfied with Haskell's build tools, such as Cabal." ExpectLikert | |
, Question 4 "I am satisfied with Haskell's package repositories, such as Hackage." ExpectLikert | |
, Question 5 "I can find Haskell libraries for the things that I need." ExpectLikert | |
, Question 6 "I think Haskell libraries are high quality." ExpectLikert | |
, Question 7 "I have a good understanding of Haskell best practices." ExpectLikert | |
, Question 8 "I think Haskell libraries are well documented." ExpectLikert | |
, Question 9 "I can easily compare competing Haskell libraries to select the best one." ExpectLikert | |
, Question 10 "I think that Haskell libraries are easy to use." ExpectLikert | |
, Question 11 "I think that Haskell libraries provide a stable API." ExpectLikert | |
, Question 12 "I think that Haskell libraries work well together." ExpectLikert | |
, Question 13 "I think that software written in Haskell is easy to maintain." ExpectLikert | |
, Question 14 "Once my Haskell program compiles, it generally does what I intended." ExpectLikert | |
, Question 15 "I think that Haskell libraries perform well." ExpectLikert | |
, Question 16 "Haskell's performance meets my needs." ExpectLikert | |
, Question 17 "I can easily reason about the performance of my Haskell code." ExpectLikert | |
, Question 18 "I would recommend using Haskell to others." ExpectLikert | |
, Question 19 "I would prefer to use Haskell for my next new project." ExpectLikert | |
, Question 20 "Haskell is working well for my team." ExpectLikert | |
, Question 21 "Haskell is critical to my company's success." ExpectLikert | |
, Question 22 "As a candidate, I can easily find Haskell jobs." ExpectLikert | |
, Question 23 "As a hiring manager, I can easily find qualified Haskell candidates." ExpectLikert | |
] | |
, Section 7 "Demographics" | |
[ Question 0 "Which country do you live in?" $ ExpectSingle | |
[ "Afghanistan" | |
, "Akrotiri" | |
, "Albania" | |
, "Algeria" | |
, "American Samoa" | |
, "Andorra" | |
, "Angola" | |
, "Anguilla" | |
, "Antarctica" | |
, "Antigua and Barbuda" | |
, "Argentina" | |
, "Armenia" | |
, "Aruba" | |
, "Ashmore and Cartier Islands" | |
, "Australia" | |
, "Austria" | |
, "Azerbaijan" | |
, "The Bahamas" | |
, "Bahrain" | |
, "Bangladesh" | |
, "Barbados" | |
, "Bassas da India" | |
, "Belarus" | |
, "Belgium" | |
, "Belize" | |
, "Benin" | |
, "Bermuda" | |
, "Bhutan" | |
, "Bolivia" | |
, "Bosnia and Herzegovina" | |
, "Botswana" | |
, "Bouvet Island" | |
, "Brazil" | |
, "British Indian Ocean Territory" | |
, "British Virgin Islands" | |
, "Brunei" | |
, "Bulgaria" | |
, "Burkina Faso" | |
, "Burma" | |
, "Burundi" | |
, "Cambodia" | |
, "Cameroon" | |
, "Canada" | |
, "Cape Verde" | |
, "Cayman Islands" | |
, "Central African Republic" | |
, "Chad" | |
, "Chile" | |
, "China" | |
, "Christmas Island" | |
, "Clipperton Island" | |
, "Cocos (Keeling) Islands" | |
, "Colombia" | |
, "Comoros" | |
, "Democratic Republic of the Congo" | |
, "Republic of the Congo" | |
, "Cook Islands" | |
, "Coral Sea Islands" | |
, "Costa Rica" | |
, "Cote d'Ivoire" | |
, "Croatia" | |
, "Cuba" | |
, "Cyprus" | |
, "Czech Republic" | |
, "Denmark" | |
, "Dhekelia" | |
, "Djibouti" | |
, "Dominica" | |
, "Dominican Republic" | |
, "Ecuador" | |
, "Egypt" | |
, "El Salvador" | |
, "Equatorial Guinea" | |
, "Eritrea" | |
, "Estonia" | |
, "Ethiopia" | |
, "Europa Island" | |
, "Falkland Islands (Islas Malvinas)" | |
, "Faroe Islands" | |
, "Fiji" | |
, "Finland" | |
, "France" | |
, "French Guiana" | |
, "French Polynesia" | |
, "French Southern and Antarctic Lands" | |
, "Gabon" | |
, "The Gambia" | |
, "Gaza Strip" | |
, "Georgia" | |
, "Germany" | |
, "Ghana" | |
, "Gibraltar" | |
, "Glorioso Islands" | |
, "Greece" | |
, "Greenland" | |
, "Grenada" | |
, "Guadeloupe" | |
, "Guam" | |
, "Guatemala" | |
, "Guernsey" | |
, "Guinea" | |
, "Guinea-Bissau" | |
, "Guyana" | |
, "Haiti" | |
, "Heard Island and McDonald Islands" | |
, "Holy See (Vatican City)" | |
, "Honduras" | |
, "Hong Kong" | |
, "Hungary" | |
, "Iceland" | |
, "India" | |
, "Indonesia" | |
, "Iran" | |
, "Iraq" | |
, "Ireland" | |
, "Isle of Man" | |
, "Israel" | |
, "Italy" | |
, "Jamaica" | |
, "Jan Mayen" | |
, "Japan" | |
, "Jersey" | |
, "Jordan" | |
, "Juan de Nova Island" | |
, "Kazakhstan" | |
, "Kenya" | |
, "Kiribati" | |
, "North Korea" | |
, "South Korea" | |
, "Kuwait" | |
, "Kyrgyzstan" | |
, "Laos" | |
, "Latvia" | |
, "Lebanon" | |
, "Lesotho" | |
, "Liberia" | |
, "Libya" | |
, "Liechtenstein" | |
, "Lithuania" | |
, "Luxembourg" | |
, "Macau" | |
, "Macedonia" | |
, "Madagascar" | |
, "Malawi" | |
, "Malaysia" | |
, "Maldives" | |
, "Mali" | |
, "Malta" | |
, "Marshall Islands" | |
, "Martinique" | |
, "Mauritania" | |
, "Mauritius" | |
, "Mayotte" | |
, "Mexico" | |
, "Federated States of Micronesia" | |
, "Moldova" | |
, "Monaco" | |
, "Mongolia" | |
, "Montserrat" | |
, "Morocco" | |
, "Mozambique" | |
, "Namibia" | |
, "Nauru" | |
, "Navassa Island" | |
, "Nepal" | |
, "Netherlands" | |
, "Netherlands Antilles" | |
, "New Caledonia" | |
, "New Zealand" | |
, "Nicaragua" | |
, "Niger" | |
, "Nigeria" | |
, "Niue" | |
, "Norfolk Island" | |
, "Northern Mariana Islands" | |
, "Norway" | |
, "Oman" | |
, "Pakistan" | |
, "Palau" | |
, "Palestine" | |
, "Panama" | |
, "Papua New Guinea" | |
, "Paracel Islands" | |
, "Paraguay" | |
, "Peru" | |
, "Philippines" | |
, "Pitcairn Islands" | |
, "Poland" | |
, "Portugal" | |
, "Puerto Rico" | |
, "Qatar" | |
, "Reunion" | |
, "Romania" | |
, "Russia" | |
, "Rwanda" | |
, "Saint Helena" | |
, "Saint Kitts and Nevis" | |
, "Saint Lucia" | |
, "Saint Pierre and Miquelon" | |
, "Saint Vincent and the Grenadines" | |
, "Samoa" | |
, "San Marino" | |
, "Sao Tome and Principe" | |
, "Saudi Arabia" | |
, "Senegal" | |
, "Serbia and Montenegro" | |
, "Seychelles" | |
, "Sierra Leone" | |
, "Singapore" | |
, "Slovakia" | |
, "Slovenia" | |
, "Solomon Islands" | |
, "Somalia" | |
, "South Africa" | |
, "South Georgia and the South Sandwich Islands" | |
, "Spain" | |
, "Spratly Islands" | |
, "Sri Lanka" | |
, "Sudan" | |
, "Suriname" | |
, "Svalbard" | |
, "Swaziland" | |
, "Sweden" | |
, "Switzerland" | |
, "Syria" | |
, "Taiwan" | |
, "Tajikistan" | |
, "Tanzania" | |
, "Thailand" | |
, "Timor-Leste" | |
, "Togo" | |
, "Tokelau" | |
, "Tonga" | |
, "Trinidad and Tobago" | |
, "Tromelin Island" | |
, "Tunisia" | |
, "Turkey" | |
, "Turkmenistan" | |
, "Turks and Caicos Islands" | |
, "Tuvalu" | |
, "Uganda" | |
, "Ukraine" | |
, "United Arab Emirates" | |
, "United Kingdom" | |
, "United States" | |
, "Uruguay" | |
, "Uzbekistan" | |
, "Vanuatu" | |
, "Venezuela" | |
, "Vietnam" | |
, "Virgin Islands" | |
, "Wake Island" | |
, "Wallis and Futuna" | |
, "West Bank" | |
, "Western Sahara" | |
, "Yemen" | |
, "Zambia" | |
, "Zimbabwe" | |
] | |
, Question 1 "How old are you?" $ ExpectSingle | |
[ "Under 18 years old" | |
, "18 to 24 years old" | |
, "25 to 34 years old" | |
, "35 to 44 years old" | |
, "45 to 54 years old" | |
, "55 to 64 years old" | |
, "Over 65 years old" | |
] | |
, Question 2 "What is your gender?" $ ExpectSingle | |
[ "Male" | |
, "Female" | |
, "Non-binary" | |
] | |
, Question 3 "Do you identify as transgender?" $ ExpectSingle | |
[ "Yes" | |
, "No" | |
] | |
, Question 4 "Are you a student?" $ ExpectSingle | |
[ "Yes, full time" | |
, "Yes, part time" | |
, "No" | |
] | |
, Question 5 "What is the highest level of education you have completed?" $ ExpectSingle | |
[ "Less than high school diploma" | |
, "High school diploma" | |
, "Some college" | |
, "Associate degree" | |
, "Bachelor's degree" | |
, "Master's degree" | |
, "Professional degree" | |
, "Doctoral degree" | |
] | |
, Question 6 "What is your employment status?" $ ExpectSingle | |
[ "Employed full time" | |
, "Employed part time" | |
, "Self employed" | |
, "Not employed, but looking for work" | |
, "Not employed, and not looking for work" | |
, "Retired" | |
] | |
, Question 7 "How large is the company you work for?" $ ExpectSingle | |
[ "Fewer than 10 employees" | |
, "10 to 99 employees" | |
, "100 to 999 employees" | |
, "More than 1,000 employees" | |
, "n/a" | |
] | |
, Question 8 "How many years have you been coding?" $ ExpectSingle | |
[ "0 to 4 years" | |
, "5 to 9 years" | |
, "10 to 14 years" | |
, "15 to 19 years" | |
, "20 to 24 years" | |
, "25 to 29 years" | |
, "30 or more years" | |
] | |
, Question 9 "How many years have you been coding professionally?" $ ExpectSingle | |
[ "0 to 4 years" | |
, "5 to 9 years" | |
, "10 to 14 years" | |
, "15 to 19 years" | |
, "20 to 24 years" | |
, "25 to 29 years" | |
, "30 or more years" | |
] | |
, Question 10 "Do you code as a hobby?" $ ExpectSingle | |
[ "Yes" | |
, "No" | |
] | |
, Question 11 "Have you contributed to any open source projects?" $ ExpectSingle | |
[ "Yes" | |
, "No" | |
] | |
] | |
, Section 8 "Meta" | |
[ Question 0 "Did you take any previous surveys?" $ ExpectMulti OtherReject | |
[ "2019" | |
, "2018" | |
, "2017" | |
] | |
, Question 1 "How did you hear about this survey?" $ ExpectSingle | |
[ "Discord" | |
, "Discourse" | |
, "GitHub" | |
, "Haskell Weekly" | |
, "Gitter" | |
, "In person" | |
, "IRC" | |
, "Lobsters" | |
, "Mailing lists" | |
, "Mastodon" | |
, "Matrix/Riot" | |
, "Reddit" | |
, "Slack" | |
, "Telegram" | |
, "Twitter" | |
, "Other" | |
] | |
] | |
, Section 9 "Free response" | |
[ Question 0 "If you wanted to convince someone to use Haskell, what would you say?" ExpectFree | |
, Question 1 "If you could change one thing about Haskell, what would it be?" ExpectFree | |
] | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment