Record wildcards

- February 10, 2014
Kwang's Haskell Blog - Record wildcards

Record wildcards

Posted on February 10, 2014 by Kwang Yul Seo

Haskell record syntax is a bit verbose. For records with many fields, it is tiresome to write each field individually in a record pattern, as in

data C = C {a :: Int, b :: Int, c :: Int, d :: Int}

f (C {a = 1, b = b, c = c, d = d}) = b + c + d

Record wildcard syntax lets us use .. in a record pattern, which simplifies pattern f=f to f. The above pattern can be rewritten with record wildcards syntax

f (C {a = 1, ..}) = b + c + d

This simple example does not show the merit of record wildcards vividly. Let’s see a real world example. hs-java is a package written by Ilya V. Portnov, which provides data types for Java .class files format and functions to assemble/disassemble Java bytecode.

The datatype for a JVM class file is Class, which has many fields as in

data Class stage = Class {
  magic :: Word32,                         -- ^ Magic value: 0xCAFEBABE
  minorVersion :: Word16,
  majorVersion :: Word16,
  constsPoolSize :: Word16,                -- ^ Number of items in constants pool
  constsPool :: Pool stage,                -- ^ Constants pool itself
  accessFlags :: AccessFlags stage,        -- ^ See @JVM.Types.AccessFlag@
  thisClass :: Link stage B.ByteString,    -- ^ Constants pool item index for this class
  superClass :: Link stage B.ByteString,   -- ^ --/-- for super class, zero for java.lang.Object
  interfacesCount :: Word16,               -- ^ Number of implemented interfaces
  interfaces :: [Link stage B.ByteString], -- ^ Constants pool item indexes for implemented interfaces
  classFieldsCount :: Word16,              -- ^ Number of class fileds
  classFields :: [Field stage],            -- ^ Class fields
  classMethodsCount :: Word16,             -- ^ Number of class methods
  classMethods :: [Method stage],          -- ^ Class methods
  classAttributesCount :: Word16,          -- ^ Number of class attributes
  classAttributes :: Attributes stage      -- ^ Class attributes
  }

It is declared as an instance of Binary class for serialization. Its put method uses the record wildcards syntax not to repeat field names as in the following:

instance Binary (Class File) where
  put (Class {..}) = do
    put magic
    put minorVersion
    put majorVersion
    putPool constsPool
    put accessFlags
    put thisClass
    put superClass
    put interfacesCount
    forM_ interfaces put
    put classFieldsCount
    forM_ classFields put
    put classMethodsCount
    forM_ classMethods put
    put classAttributesCount
    forM_ (attributesList classAttributes) put

You can see the real difference by comparing this with a more verbose version which does not use record wildcards.

instance Binary (Class File) where
  put (Class {magic=magic, minorVersion=minorVersion, majorVersion=majorVersion, constsPool=constsPool, accessFlags=accessFlags, thisCla    ss=thisClass, superClass=superClass, interfacesCount=interfacesCount, interfaces=interfaces, classFieldsCount=classFieldsCount, classFie    lds=classFields, classMethodsCount=classMethodsCount, classMethods=classMethods, classAttributesCount=classAttributesCount, classAttributes=classAttributes}) = do
 ...
Read more

Overloaded string literals

- January 15, 2014
Kwang's Haskell Blog - Overloaded string literals

Overloaded string literals

Posted on January 15, 2014 by Kwang Yul Seo

In Haskell, the type of a string literal "Hello World" is always String which is defined as [Char] though there are other textual data types such as ByteString and Text. To put it another way, string literals are monomorphic.

GHC provides a language extension called OverloadedStrings. When enabled, literal strings have the type IsString a => a. IsString moudle is defined in Data.String module of base package:

class IsString a where
    fromString :: String -> a

ByteString and Text are examples of IsString instances, so you can declare the type of string literals as ByteString or Text when OverloadedStrings is enabled.

{-# LANGUAGE OverloadedStrings #-}
a :: Text
a = "Hello World"

b :: ByteString
b = "Hello World"

Of course, String is also an instance of IsString. So you can declare the type of a string literal as String as usual.

c :: String
c = "Hello World"
Read more

DoAndIfThenElse language extension

- January 13, 2014
Kwang's Haskell Blog - DoAndIfThenElse language extension

DoAndIfThenElse language extension

Posted on January 13, 2014 by Kwang Yul Seo

Have you ever encountered “Unexpected semi-colons in conditional” errors while building your project with Cabal and wondered why? This blog post explains about this puzzling error.

Here is a simple program which prints the given command line arguments.

import System.Environment
 
main = do
    args <- getArgs
    if length args < 1 then
        putStrLn "Usage: DoAndIfThenElse [args]"
    else
        putStrLn $ concat args

You can build this Haskell program with ghc –make:

ghc --make DoAndIfThenElse.hs
[1 of 1] Compiling Main             ( DoAndIfThenElse.hs, DoAndIfThenElse.o )
Linking DoAndIfThenElse ...

Okay. Then let’s create a Cabal build script and build this program with Cabal.

name:            DoAndIfThenElse
version:         0.0.1
cabal-version:   >= 1.8
build-type:      Simple
 
executable DoAndIfThenElse
  hs-source-dirs:    src
  main-is:           DoAndIfThenElse.hs
  build-depends:     base

The source code is exactly the same, but now GHC suddenly complains about the unexpected semi-colons we never inserted anyway.

src/DoAndIfThenElse.hs:5:8:
    Unexpected semi-colons in conditional:
        if length args < 1 then putStrLn
                                  "Usage: DoAndIfThenElse [args]"; else putStrLn $ concat args
    Perhaps you meant to use -XDoAndIfThenElse?

You can fix this problem by adding DoAndIfThenElse language pragma at the beginning:

{-# LANGUAGE DoAndIfThenElse #-}

Or you can fix it by changing the indentation of then and else of if expression.

if length args < 1
    then putStrLn "Usage: DoAndIfThenElse [args]"
    else putStrLn $ concat args

So the problem is on the indentation. You have to keep then and else at deeper indentation levels than the if block they belong.

ghc –make is okay because GHC automatically turns on the syntax extension DoAndIfThenElse. However, Cabal is more picky, so you have to turn it on manually either at the top of your code files, or in your Cabal files.

There is also a StackOverflow question on this issue, Unexpected semi-colons in conditional.

Read more