IHP uses lots of GHC language extensions to create the “magic” that comes along with the framework. One the the first nonstandard language extensions you’ll come across while developing with IHP is the fancy “@” symbol.

action UsersAction = do
    users <- query @User |> fetch
    render IndexView { .. }

@User appears like a normal parameter to the function, which can be highly misleading if you try and extrapolate this idea into other contexts. When I first started working with IHP, I assumed this was the equivalent to Swift’s self property on types, which gives you a value representing the type that can be passed around:

let typeOfInt = Int.self

This is not the case however!

let
  typeOfUser = @User
in
  -- parse error!

So what’s actually happening here?

What’s the type?

Say we want to test the behavior of Haskell’s read and show functions, which each have a corresponding type class which defines which values can be passed to these functions.

read :: (Read a) => String -> a
show :: (Show a) => a -> String

Given any value which has both Read and Show instances defined, we should be able to convert back and forth between it and its String representation.

Let’s test for integers:

let intString = "123"
read intString

*** Exception: Prelude.read: no parse

Huh? Of course read should know how to parse a simple integer, right?

If you think about the above code carefully, you might notice that Haskell has no way of knowing what type read intString should be! Obviously to us it represents an integer, but in Haskell’s eyes it’s given an arbitrary string and has no way of knowing which instance of the type class Read it should produce.

@ me bro

Type application to the rescue! The “@” symbol allows us to explicity pass a type to the function:

let intString = "123"
read @Int intString

123

Note, @Int is not a regular parameter! The regular parameters we’re used to working with are actually called value parameters, and read only has one: a String.

Behind the scenes, Haskell also includes type parameters which specify the types that satisfy a function’s constraints, or the stuff before the => symbol in function declarations.

Normally Haskell can do this for us:

readFn :: String -> Int
readFn intString = read intString

readFn "123"

123

Since the function returns an Int, the type parameter can be inferred. In the simple example earlier however, this was not the case.

Type Application in IHP

Say we’re writing a script that wants to print out the name of the newest user to signup for our application.

run :: Script
run = do
    user <- query @User |> orderBy #createdAt |> fetchOne
    print (get #createdAt user)

To understand what @User is doing, we need to look at the definition of query:

query :: forall model table. table ~ GetTableName model => DefaultScope table => QueryBuilder table

query doesn’t take any value parameters! All the info that gets passed to the function is contained in its type constraints. So if we omitted @User, Haskell would have no way of knowing what the model type should be.

Conclusion

Type application is extremely powerful and is used all over both the IHP codebase and IHP applications. Understanding what’s going on when you see the @ symbol is a good first step for uncovering some of the “magic” behind the framework.

Does anything else in IHP confuse you? Let me know below and I will write about it in a future article! Thanks for reading!