Reputation: 449
I am writing some code to express "expressions" built from members of a given type:
data Expr a = Simply a | Add (Expr a, Expr a) | Mult (Expr a, Expr a)
One can define some infix operators to make it more convenient to construct these:
(+++) :: Expr a -> Expr a -> Expr a
x +++ y = Add (x, y)
(***) :: Expr a -> Expr a -> Expr a
x *** y = Mult (x, y)
Hence one can just write
(Simply "bar") +++ ((Simply "fo") *** (Simply "o"))
to get
Add (Simply "bar",Mult (Simply "fo",Simply "o"))
Now the question is if there is a way to omit the constructor Simply
as well, i.e. to consider any member of the type a
implicitly as a member of Expr a
and obtain the example above directly from ("bar") +++ (("fo") *** ("o"))
.
Upvotes: 2
Views: 183
Reputation: 3805
You can make a typeclass of expressions like this:
class IsExpr a b | a -> b where
ex :: a -> Expr b
This defines a class of types a which are expressions of types b, where b is declared to be determined by a using a functional dependency.
Then you go ahead and make String
into an expression of type Expr String
:
instance IsExpr String String where
ex = Simply
Furthermore, somewhat unsurprisingly, expressions are also expressions:
instance IsExpr (Expr a) a where
ex = id
Now you can redefine your operators so that they can take any combination of things which yield expressions of the same type, i.e. any argument can be a String
or an Expr String
or whatever you define IsExpr
instances for:
(++++) :: (IsExpr a c, IsExpr b c) => a -> b -> Expr c
x ++++ y = Add (ex x, ex y)
(****) :: (IsExpr a c, IsExpr b c) => a -> b -> Expr c
x **** y = Mult (ex x, ex y)
This does need some extensions though: TypeSynonymInstances
(only if you use String
s), FlexibleInstances
, MultiParamTypeClasses
and FunctionalDependencies
. Of course there is a complexity/benefit tradeoff, so maybe the benefit isn't worth the additional complexity.
BTW, I'd rather define the data constructors as Add (Expr a) (Expr a)
. You are adding an
additional indirection by using a tuple.
Upvotes: 10
Reputation: 21811
If you insist on using mathematical operators on Strings, you can make make a new typeclass with those operations:Expr a
an instance of the Num
class:
{-# LANGUAGE TypeFamilies #-}
class ExprOps a where
type Internal a
(+++) :: a -> a -> a
(***) :: a -> a -> a
fromInternal :: Internal a -> a
instance ExprOps (Expr a) where
type Internal (Expr a) = a
x +++ y = Add (x,y)
x *** y = Mult (x,y)
fromInternal = Simply
Then you can simply write
foo :: Expr String
foo = "bar" +++ "fo" *** "o"
which still results in the expression
Add (Simply "bar",Mult (Simply "fo",Simply "o"))
but does not require any explicit Simply
constructors.
Upvotes: 2
Reputation: 53871
You can remove the Simply
constructor using the -XOverloadedStrings
extension.
{-# LANGUAGE OverloadedStrings, FlexibleInstances #-}
import Data.String
instance IsString (Expr String) where
fromString = Simply
And then create a Num
instance to get normal operators
instance Num (Expr a) where
(+) = curry Add
(*) = curry Mult
And then you can do things like
"foo" + "bar" + "baz" :: Expr String
All this does is cause GHC to interpret "..."
's as fromString "..."
.
Upvotes: 3