GHCi is the interactive shell for the GHC compiler. GHCi is where we will spend most of our time in every day development.
| Command | Shortcut | Action |
|---|---|---|
:reload |
:r |
Code reload |
:type |
:t |
Type inspection |
:kind |
:k |
Kind inspection |
:info |
:i |
Information |
:print |
:p |
Print the expression |
:edit |
:e |
Load file in system editor |
:load |
:l |
Set the active Main module in the REPL |
:add |
:ad |
Load a file into the REPL namespace |
:browse |
:bro |
Browse all available symbols in the REPL namespace |
Cool Prompts
:set prompt "λ: "
:set prompt "ΠΣ: "Shell Commands
:def make (\_ -> return ":! make")
:def spiritanimal (\_ -> return ":! cowsay -f tux \"More types!\" ")Inline Hoogle
:def hoogle \s -> return $ ":! hoogle --count=15 \"" ++ s ++ "\""λ: :hoogle (a -> b) -> f a -> f b
Data.Traversable fmapDefault :: Traversable t => (a -> b) -> t a -> t b
Prelude fmap :: Functor f => (a -> b) -> f a -> f bProfiling
λ: :set +s
λ: foldr (+) 0 [1..25]
325
it :: Prelude.Integer
(0.02 secs, 4900952 bytes)The Prelude isn't great, but GHC is wonderful in that it allows us to replace it with your company's industrial Prelude's (protolude, classy-prelude, Sentenai.Prelude, etc).
$ ghci -package classy-prelude -XNoImplicitPrelude$ stack ghci --package protolude --ghc-options -XNoImplicitPreludeSeveral commands can be added to your .ghci config to lookup module information.
:def browser \ u -> return $ ":! chromium-browser " ++ u
:def package \ m -> return $ ":! ghc-pkg --simple-output find-module " ++ m
:{
:def doc \ m -> return $ if m == ""
then ":browser $(ghc --print-libdir)/../../share/doc/ghc/html/index.html"
else ":browser $(ghc-pkg --simple-output field $(ghc-pkg --simple-output find-module " ++ m ++ ") haddock-html)/$(echo " ++ m ++ " | tr . -).html"
:}λ> :package Control.Monad.Reader
mtl-2.2.1
λ> :package Type
ghc-7.10.3
λ> :package Data.Text
text-1.2.2.0Compile your sandbox with --enable-documentation.
λ> :doc Type
λ> :doc Data.TextPerformance in GHCi not representative of code compiled with -O2 and in many cases it can be order of magnitudes slower and even diverge when the compiled code would optimize allocations.
For large projects, GHCi with the default flags can use quite a bit of memory and take a long time to compile. To speed compilation by keeping artificats for compiled modules around, we can enable object code compilation instead of bytecode.
:set -fobject-codeEnabling object code compliation may complicate type inference, since type information provided to the shell can sometimes be less informative than source-loaded code. This under specificity can result in breakage with some langauge extensions. In that case, you can temporarily reenable bytecode compilation on a per module basis with the -fbyte-code flag.
:set -fbyte-code
:load MyModule.hsDon't compile anything, just typecheck.
:set -fno-codeStrive to have your code loadable into GHCi, it helps to iterate quickly and let others figure out how to use your code. If you can't load your code into GHCi it makes it harder to onboard developers.
What can break ghci?
CPP Macros - Using the preprocessor to generate code that's contingent on environmental variables or generates typeclasses using macros.
FFI - Unsafely allocating persistent objects off-heap. Manual system calls.
Trying to dynamically load/relocate a static archive files needed for statically linked libraries. ( from war stories about llvm-general )
Using libffi and dlsym to dynamically load symbols into the process space and link them. ( war story )
Deriving isn't magic, it's just mechanical. Sometimes adding -dsuppress-module-prefixes will help clear this up.
λ> :set -ddump-derived
λ> data Pie = Apple | Pumpkin deriving (Show, Generic)
==================== Derived instances ====================
Derived instances:
instance Show Pie where
showsPrec _ Apple = showString "Apple"
showsPrec _ Pumpkin = showString "Pumpkin"
showList = showList__ (showsPrec 0)
instance Generic Pie where
from Apple = M1 (L1 (M1 U1))
from Pumpkin = M1 (R1 (M1 U1))
to (M1 (L1 (M1 U1))) = Apple
to (M1 (R1 (M1 U1))) = Pumpkin
instance Datatype D1Pie where
datatypeName _ = "Pie"
moduleName _ = "Ghci5"
instance Constructor C1_0Pie where conName _ = "Apple"
instance Constructor C1_1Pie where conName _ = "Pumpkin"
Generic representation:
Generated datatypes for meta-information:
D1Pie
C1_0Pie
C1_1Pie
Representation types:
type Rep Pie = D1 D1Pie (C1 C1_0Pie U1 :+: C1 C1_1Pie U1)Suppose we wanted to write generic substitutions that we can automatically have GHC write for (typing environment, complex types containing Type, containers, etc). We could write the boilerplate, or we could use GHC.Generics.
-- | Type substitution over type variables.
newtype Subst = Subst (Map.Map Id Type)
deriving (Eq, Ord, Show, Generic)
data Type
= TVar Var
| TCon Var
| TApp Type Type
| TForall [Pred] [Var] Type
deriving (Show, Eq, Ord, Generic)class Types a where
-- | Apply a type substitution.
apply :: Subst -> a -> a
default apply :: (Generic a, GTypes (Rep a)) => Subst -> a -> a
apply su a = to (gapply su (from a))
instance Types Type where
apply s (TApp a b) = TApp (apply s a) (apply s b)
apply _ t@TCon{} = t
apply s t@(TVar p) = Map.findWithDefault t p (sMap s)
instance Types Id where
apply s x = xUsing GHCi we can inspect the structure of the associated data type Rep from the derived Generic class.
λ> import GHC.Generics
λ> :kind! Rep Type
Rep Type :: * -> *
= D1
Types.Type.D1Type
((C1 Types.Type.C1_0Type (S1 NoSelector (Rec0 Var))
:+: C1 Types.Type.C1_1Type (S1 NoSelector (Rec0 Var)))
:+: (C1
Types.Type.C1_2Type
(S1 NoSelector (Rec0 Type) :*: S1 NoSelector (Rec0 Type))
:+: (C1
Types.Type.C1_3Type
(S1 NoSelector (Rec0 Type) :*: S1 NoSelector (Rec0 Type))
:+: C1
Types.Type.C1_4Type
(S1 NoSelector (Rec0 [Pred])
:*: (S1 NoSelector (Rec0 [Var]) :*: S1 NoSelector (Rec0 Type))))))type Par0 = K1 P
type Rec0 = K1 R
type S1 = M1 S
type C1 = M1 C
type D1 = M1 Dclass GTypes f where
gapply :: Subst -> f a -> f a
instance Types c => GTypes (K1 i c) where
gapply su (K1 c) = K1 (apply su c)
instance GTypes f => GTypes (M1 i c f) where
gapply su (M1 fp) = M1 (gapply su fp)
instance GTypes U1 where
gapply _ u = u
instance (GTypes f, GTypes g) => GTypes (f :+: g) where
gapply su (L1 fp) = L1 (gapply su fp)
gapply su (R1 fp) = R1 (gapply su fp)
instance (GTypes f, GTypes g) => GTypes (f :*: g) where
gapply su (fp :*: gp) = gapply su fp :*: gapply su gpWe get instances of this for free now!
instance Types Pred where
instance Types a => Types [a] where
instance (Types a, Types b) => Types (a,b) where
instance Types a => Types (Located a) where
apply su (Located l a) = Located l (apply su a)ghcid is a lightweight IDE hook that allows continuous feedback whenever code is updated. It is run from the command line in the root of the cabal project directory by specifying a command to run (e.g., ghci, cabal repl, or stack repl).
ghcid --command="cabal repl" # Run cabal repl under ghcid
ghcid --command="stack repl" # Run stack repl under ghcid
ghcid --command="ghci baz.hs" # Open baz.hs under ghcidVim has wonderful support for Haskell using ghc-mod and syntastic. NeoVim has made a lot of progress in the last couple of months with haskell-vim.
map <Leader>s :SyntasticToggleMode<CR>
set statusline+=%#warningmsg#
set statusline+=%{SyntasticStatuslineFlag()}
set statusline+=%*
let g:syntastic_always_populate_loc_list = 1
let g:syntastic_auto_loc_list = 0
let g:syntastic_check_on_open = 0
let g:syntastic_check_on_wq = 0
map <silent> tw :GhcModTypeInsert<CR>
map <silent> ts :GhcModSplitFunCase<CR>
map <silent> tq :GhcModType<CR>
map <silent> te :GhcModTypeClear<CR>
let g:SuperTabDefaultCompletionType = '<c-x><c-o>'
if has("gui_running")
imap <c-space> <c-r>=SuperTabAlternateCompletion("\<lt>c-x>\<lt>c-o>")<cr>
else " no gui
if has("unix")
inoremap <Nul> <c-r>=SuperTabAlternateCompletion("\<lt>c-x>\<lt>c-o>")<cr>
endif
endif
GHC's default Outputtable form is kind of gnarly, we can tame it a bit with some suppressions.
In your .bashrc or .zshrc file
alias ghci-core="ghci -ddump-simpl -dsuppress-idinfo \
-dsuppress-coercions -dsuppress-type-applications \
-dsuppress-uniques -dsuppress-module-prefixes"
λ> let fibs = 0 : zipWith (+) (1 : fibs) fibs
==================== Simplified expression ====================
GHC.Base.returnIO
@ [()]
(GHC.Types.:
@ ()
((\ (@ a_ayL) ($dNum_ayM :: GHC.Num.Num a_ayL) ->
let {
a_syZ :: a_ayL
[LclId,
Str=DmdType,
Unf=Unf{Src=<vanilla>, TopLvl=False, Value=False, ConLike=False,
WorkFree=False, Expandable=False, Guidance=IF_ARGS [] 130 0}]
a_syZ = GHC.Num.fromInteger @ a_ayL $dNum_ayM (__integer 0) } in
letrec {
fibs_apE :: [a_ayL]
[LclId,
Str=DmdType,
Unf=Unf{Src=<vanilla>, TopLvl=False, Value=True, ConLike=True,
WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 30}]
fibs_apE = GHC.Types.: @ a_ayL a_syZ a_sz0;
a_sz0 [Occ=LoopBreaker] :: [a_ayL]
[LclId,
Str=DmdType,
Unf=Unf{Src=<vanilla>, TopLvl=False, Value=False, ConLike=False,
WorkFree=False, Expandable=False, Guidance=IF_ARGS [] 200 0}]
a_sz0 =
GHC.List.zipWith
@ a_ayL
@ a_ayL
@ a_ayL
(GHC.Num.+ @ a_ayL $dNum_ayM)
(GHC.Types.:
@ a_ayL
(GHC.Num.fromInteger @ a_ayL $dNum_ayM (__integer 1))
fibs_apE)
fibs_apE; } in
fibs_apE)
`cast` (UnivCo mkUnsafeCo representational
(forall a_ayI. GHC.Num.Num a_ayI => [a_ayI]) ()
:: (forall a_ayI. GHC.Num.Num a_ayI => [a_ayI]) ~R# ()))
(GHC.Types.[] @ ()))Becomes this:
λ> let fibs = 0 : zipWith (+) (1 : fibs) fibs
==================== Simplified expression ====================
returnIO
(: (let {
a :: Int
a = I# 0 } in
letrec {
fibs :: [Int]
fibs = : a a;
a :: [Int]
a = zipWith (+ $fNumInt) (: (I# 1) fibs) fibs; } in
fibs `cast` ...)
([]))DSL — A domain specific language, where code is written in one language and errors are given in another.
EDSL — A embedded domain specific language, where code is written in one language and errors are given in the errors of the encoding between the host language. Synonyms: suffering, pain, insanity
Not anymore though! New feature in GHC 8.0. Going to be big.
data ErrorMessage where
Text :: Symbol -> ErrorMessage
ShowType :: t -> ErrorMessage
-- Put two messages next to each other
(:<>:) :: ErrorMessage -> ErrorMessage -> ErrorMessage
-- Put two messages on top of each other
(:$$:) :: ErrorMessage -> ErrorMessage -> ErrorMessage{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-} -- NOOOOOO
import GHC.TypeLits
type family Coerce a b where
Coerce Int Int = Int
Coerce Float Float = Float
Coerce Int Float = Float
Coerce Float Int = TypeError (Text "Cannot cast to smaller type")
data Expr a where
EInt :: Int -> Expr Int
EFloat :: Float -> Expr Float
ECoerce :: Expr b -> Expr c -> Expr (Coerce b c)
foo :: Expr Int
foo = ECoerce (EFloat 3) (EInt 4)λ> :load error_dsl.hs
[1 of 1] Compiling Main ( error_dsl.hs, interpreted )
error_dsl.hs:21:7: error:
• Cannot cast to smaller type
• In the expression: ECoerce (EFloat 3) (EInt 4)
In an equation for ‘foo’: foo = ECoerce (EFloat 3) (EInt 4)
Failed, modules loaded: none.Little known fact that hlint will check for a HLint.hs file in your project and allow it to supply custom hint rules that integrate with your editor. These are specified in it's own rule language. Good for enforcing company-wide style around functions.
For example:
ignore "Avoid Lambda"
ignore "Eta reduce"
error "generalize fmap" = map ==> fmap
-- AMP fallout
error "generalize mapM" = mapM ==> traverse
error "generalize mapM_" = mapM_ ==> traverse_
error "generalize forM" = forM ==> for
error "generalize forM_" = forM_ ==> for_
error "Avoid return" =
return ==> pure
where note = "return is obsolete as of GHC 7.10"I don't personally use them that much ( ghc-mod is more interactive ) but they are useful sometime.
{-# LANGUAGE NoImplicitPrelude #-}
class Functor f where
fmap :: (a -> b) -> f a -> f b
instance Functor [] where
fmap f (x:xs) = f x : fmap f _sample.hs:7:32:
Found hole ‘_’ with type: [a]
Where: ‘a’ is a rigid type variable bound by
the type signature for fmap :: (a -> b) -> [a] -> [b]
at sample.hs:7:3
Relevant bindings include
xs :: [a] (bound at sample.hs:7:13)
x :: a (bound at sample.hs:7:11)
f :: a -> b (bound at sample.hs:7:8)Can put type holes inside the function context to infer constraints, and you can name the holes. Enabling -XPartialTypeSignatures will allow the compilation to continue if the program is well-typed under inference ( it still needs to be able to resolve dictionaries! ).
{-# LANGUAGE PartialTypeSignatures #-}
foo :: (Show _a, _) => _a -> _
foo x = show (succ x)Warnings, not errors. Compilation proceeds.
sample2.hs:3:18: Warning:
Found hole ‘_’ with inferred constraints: (Enum _a)
In the type signature for ‘foo’: (Show _a, _) => _a -> _
sample2.hs:3:30: Warning:
Found hole ‘_’ with type: String
In the type signature for ‘foo’: (Show _a, _) => _a -> _