No, but there are workarounds. As you saw, the type you'd need to write is something like moneyOf :: (c :: Currency) -> Int -> Money c
, where c
is bound both in the type and in the function implementation itself (moneyOf _ amt = Money amt
). This isn't something we can do in Haskell. So what can we do instead? There are two options, depending on how much you really want.
Option 1: Proxies. Define a poly-kinded type
data Proxy (t :: k) = Proxy
The idea behind this type is that you can use Proxy :: Proxy t
as a way of passing around a reified term-level representation of the type t. So, for instance, we can define:
moneyOf :: Proxy c -> Int -> Money c
moneyOf _ = Money
Then, we can call it like moneyOf (Proxy :: Proxy USD) 10
to get Money 10 :: Money USD
. A trick you can use is to instead give the function the type proxy k -> Int -> Money c
instead (note the lowercase proxy
!), so that proxy
will unify with arbitrary function types.¹ This is great for passing arguments to functions in order to fix their return types, but it doesn't really let you do anything beyond that.
As you've described your problem, I think proxies are probably the best fit for solving it. (Assuming that plain type signatures, like Money 10 :: Money USD
, don't work, that is – those are even simpler when you can use them!)
Option 2: Singleton types. However, if you find that you need more generality (or if you're just curious), then another approach is to create a singleton type like the following:
data SingCurrency (c :: Currency) where
SUSD :: SingCurrency USD
SEUR :: SingCurrency EUR
SYEN :: SingCurrency YEN
This is called a "singleton type" because each SingCurrency c
has only one member (e.g., SUSD
is the unique value of type SingCurrency USD
). Now, you can write
moneyOf :: SingCurrency c -> Int -> Money c
moneyOf _ = Money
Here, moneyOf SUSD 10
evaluates to Money 10 :: Money USD
. But this alone doesn't buy you anything beyond using (except a bit less typing). Singletons get particularly fun when you want to produce them:
class SingCurrencyI (c :: Currency) where
sing :: SingCurrency c
instance SingCurrencyI USD where scur = SUSD
instance SingCurrencyI EUR where scur = SEUR
instance SingCurrencyI YEN where scur = SYEN
Now, if you have a SingCurrencyI c
constraint, you can automatically produce the corresponding SingCurrency c
value with sing
, thus allowing you to move from the type level to the term level. (Note that, although all Currency
s are instances of SingCurrencyI
, you need to specify the constraint explicitly if you want it.²) I can't think of any good examples of using this off the top of my head; I think my suggestion would be to use singletons only if you find yourself in a situation where you realize you can't accomplish what you need, and realize that the extra type-value synchronization of singletons would help you (and where you can't redesign yourself out of the situation).
If you do find yourself using singletons, the machinery is all set up for you in the singletons
package, in more generality: there's a data family Sing :: k -> *
that takes the place of SingCurrency
; and there's have a type class SingI :: k -> Constraint
that takes the place of SingCurrencyI
, which has the single member sing :: SingI a => Sing a
. There's also a function withSingI :: Sing n -> (SingI n => r) -> r
which allows you to convert freely from Sing n
into SingI n
(the other direction is just sing
). (These are all provided in Data.Singletons
.) There's also some Template Haskell in Data.Singletons.TH
that allows you to write
singletons [d|data Currency = USD | EUR | YEN|]
at the top level of your program in order to define the Currency
type along with the appropriate Sing
and SingI
instances. (You need the following language extensions enabled, too: KindSignatures
, DataKinds
, TypeFamilies
, GADTs
or ExistentialQuantification
, ScopedTypeVariables
, and TemplateHaskell
.)
This is really powerful – it's almost like dependent types, if you squint – but it can be a massive pain to use. Indeed, if you want more information, there's a paper talking about exactly that: "Hasochism: The Pleasure and Pain of Dependently Typed Haskell Programming", by Sam Lindley and Conor McBride. It's definitely readable by anybody who's thinking about these ideas already, although the material is sort of intrinsically tricky; be warned that their notation is slightly different, though. I don't, unfortunately, know of any good blogpost or tutorial-style introductions to this stuff.
¹ I'm uncertain about the status of that type unification rule with type families, though….
² Otherwise, the run-time dictionary containing sing
won't be passed in, so the value wouldn't be available at run-time.
moneyOf :: Proxy c -> Money c
, to be called asmoneyOf (Proxy :: Proxy USD)
, as far as I know. – HengistCurrency
:data SCurrency (c :: Currency) where SUSD :: SCurrency USD; SEUR :: SCurrency EUR; SYEN :: SCurrency Yen
and write the functionSCurrency c -> Money c
. In this case, since you don't need to know anything about the type of currency (since it is a phantom type) you can get away with just using Proxy. – RheometermoneyOf
to beCurrency -> Int -> Money c
right? – Disvalue