FP in industry

Viačeslav Pozdniakov @poznia, Adform, VU

FP in for industry

Viačeslav Pozdniakov @poznia, Adform, VU

This talk is not about:

  • Immutable data structures
  • Concurrency and parallelism
  • ... other FP mantras

We will:

  • Talk about (good) software design (any PL)
  • Discuss some (almost) unique features of Haskell
  • Light intro into Haskell and type-classes
  • See reasons for a high level of integration between Haskell libs
  • Run a simple app, once started in Adform, but now abandoned (1:1:1)

Part I

Any API + business logics + storage app:

  1. Active Record
  2. A la DDD (persistence is a plugin)
  3. Something in the middle

Simple design

Domain model is not a model any more

						
@Entity
public class A {
  @JsonProperty("banner_id")
  @Id
  public int bannerId;
}
						
					

Desired interface

						
public class S {
  @JsonProperty("banner_id")
  public int bannerId;
}
						
					

Desired model

						
public class A {
  public int bannerId;
}
						
					

Desired entity

						
@Entity
public class B {
  @Id
  public int bannerId;
}
						
					

Advanced design

Wouldn't it be awesome to have

Why not possible (i.e., in Java)

You are forced to declare a class C and implement interface I in class C in a same compilation unit.

Part II

OMG, Haskell code

						
data BannerSetup = BannerSetup {
  bannerId :: BannerId
  , clientDivId :: ClientDivId
  , eshopId :: EshopId
  , templateId :: TemplateId
}
						
					

Odd constructors

						
data BannerId = BannerId Integer
data ClientDivId = ClientDivId Integer
data EshopId = EshopId Integer
data TemplateId = TemplateId Integer
						
					

Haskell is simple

						
routes :: ScottyM ()
routes = do
  post "/" $ readBannerSetup >>= save >> raw "Ok"
						
					

Signature of jsonData

						
readBannerSetup :: ActionM BannerSetup
readBannerSetup = jsonData
						
					
API:
						
jsonData :: FromJSON a => ActionM a
						
					

Implement FromJSON

						
instance FromJSON BannerSetup where
  parseJSON (Object v) = do
    b <- v .: "banner_id"
    c <- v .: "client_division_id"
    e <- v .: "eshop_id"
    t <- v .: "template_id"
    return $ BannerSetup (BannerId b) (ClientDivId c) (EshopId e) (TemplateId t)
  parseJSON _ = mzero
						
					

Signatures rule!

						
routes :: ScottyM ()
routes = do
  post "/" $ readBannerSetup >>= save >> raw "Ok"

readBannerSetup :: ActionM BannerSetup
readBannerSetup = jsonData
						
					
API:
						
jsonData :: FromJSON a => ActionM a
						
					

Ready to save?

						
save :: ToRow a => a -> ActionM ()
save d = liftIO $ bracket getConnection close $ \conn -> void $
  execute conn "insert into banners " ++
  	"(banner_id, client_div_id, eshop_id, template_id)" ++
  	"values (?, ?, ?, ?)" d						
						
					

Make BannerSetup a ToRow

						
instance ToRow BannerSetup where
  toRow (BannerSetup (BannerId b) (ClientDivId c) (EshopId e) (TemplateId t)) =
    toRow [toField b, toField c, toField e, toField t]
						
					

List them all!

						
routes :: ScottyM ()
routes = do
  post "/" $ readBannerSetup >>= save >> raw "Ok"
  get "/" $ loadAllBanners >>= renderJson
  						
					

Read from DB

						
loadAllBanners :: FromRow a => ActionM [a]
loadAllBanners = liftIO $ bracket getConnection close $ \conn ->
  query_ conn "select * from banners"
						
					
						
instance FromRow BannerSetup where
  fromRow = do
    b <- field
    c <- field
    e <- field
    t <- field
    return $ BannerSetup (BannerId b) (ClientDivId c) (EshopId e) (TemplateId t)
						
					

ToJSON (with some magic)

						
renderJson :: [BannerSetup] -> ActionM ()
renderJson = json
  						
					
						
instance ToJSON BannerSetup where
  toJSON (BannerSetup (BannerId b) (ClientDivId c) (EshopId e) (TemplateId t)) =
    object [
        "banner_id" .= b, "client_division_id" .= c,
        "eshop_id" .= e, "template_id" .= t
      ]
  		
					

But we did not implement ToJSON [a]

						
ToJSON Bool	 
ToJSON Char	 
ToJSON Double	 
ToJSON Float	 
ToJSON Int
-- skip some	 
ToJSON Value	 
ToJSON DotNetTime	 
ToJSON [Char]	 
ToJSON a => ToJSON [a]	 
ToJSON (Ratio Integer)	 
ToJSON a => ToJSON (Maybe a)
-- skip many
						
					

Can I do it in my PL?

Yes, you can. Use:

  • Scala implicits
  • Rust traits
  • ..?

Scala example

We want
						
case class BannerSetup(bannerId: Int, clientDivId: Int, eshopId: Int, templateId: Int)
						
					
To "implement"
						
trait ToRow[T] {
  def toRows(value: T): List[AnyVal]
}
						
					

Implicit magic

To run this code:
						
def save[T: ToRow](t: T) = {
  val rows = implicitly[ToRow[T]].toRows(t)
  for (row <- rows) println(row.toString)
}
						
					
We need such a glue:
						
implicit object BannerSetupToRow extends ToRow[BannerSetup] {
  def toRows(b: BannerSetup) = List(b.bannerId, b.clientDivId, b.eshopId, b.templateId)
}
						
					

Rust example

No way, Rust is not a FPL :)

Some stats

➜  dco-bannerz git:(master) cloc src
       4 text files.
       4 unique files.                              
       0 files ignored.

http://cloc.sourceforge.net v 1.60  T=0.02 s (259.6 files/s, 6814.6 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Haskell                          4             21              0             84
-------------------------------------------------------------------------------
SUM:                             4             21              0             84
-------------------------------------------------------------------------------
					

The End

We are hiring