Reputation: 371
I am trying to do something that will take as an input functions defined after 'where' clause and output a string composed of it. It is mentioned to be something that will scope the functions listed after 'where' clause to writeOut function. What I want to come with(It's not working):
writeOut :: [Char] -> [Char]
writeOut x = x
where
number :: Char -> [Char]
number n = [n]
multiplication :: [Char] -> [Char] -> [Char]
multiplication a b = a++"*"++b
addition :: [Char] -> [Char] -> [Char]
addition a b = "("++a++"+"++b++")"
subtraction :: [Char] -> [Char] -> [Char]
subtraction a b = "("++a++"-"++b++")"
...
So, for example, for an input:
writeOut multiplication (addition (number '1') (subtraction (number '2') (number '3'))) (number '3')
would give me an output:
(1+(2-3))*3
it is working properly when I delete writeOut function and just leave what is after 'where' keyword, but I really want to 'lock it' under the 'writeOut' function.
You may ask why I am trying to do it, as I want another function that will calculate the expression for me:
writeOut multiplication (addition (number '1') (subtraction (number '2') (number '3'))) (number '3')
Output: (1+(2-3))*3
calc multiplication (addition (number '1') (subtraction (number '2') (number '3'))) (number '3')
Ouput: 0
Just in case you want to see what is working "properly":
number :: Char -> [Char]
number n = [n]
multiplication :: [Char] -> [Char] -> [Char]
multiplication a b = a++"*"++b
addition :: [Char] -> [Char] -> [Char]
addition a b = "("++a++"+"++b++")"
subtraction :: [Char] -> [Char] -> [Char]
subtraction a b = "("++a++"-"++b++")"
Upvotes: 2
Views: 198
Reputation: 55079
At the moment, your definitions of number
, multiplication
, addition
, and so on are all functions operating on strings or characters. So, even supposing they were defined at the top level, so you could refer to them in an expression like this:
writeOut multiplication (addition (number '1') (subtraction (number '2') (number '3'))) (number '3')
What this would imply is that writeOut
must be able to accept the function multiplication
as its first argument, the result of (addition …)
as its second, and the result of (number …)
as its third, so it would need a type like ([Char] -> [Char] -> [Char]) -> [Char] -> [Char] -> [Char]
. Overloading writeOut
to work this way and allow any other expression as arguments isn’t really possible; if you try to do it using typeclasses, you’ll quickly find that it produces a lot of ambiguous types and thus requires a lot of type annotations.
But there are multiple better approaches, depending on what you’re really trying to accomplish here. The simplest thing is to move these functions to the top level:
writeOut :: [Char] -> [Char]
writeOut x = x
number :: Char -> [Char]
number n = [n]
multiplication :: [Char] -> [Char] -> [Char]
multiplication a b = a++"*"++b
…
And invoke writeOut
like writeOut (multiplication (number '2') (number '3'))
with the expression wrapped in parentheses. But here writeOut
is completely redundant: it’s equivalent to the identity function id
, which merely returns its argument unmodified. All the actual work of constructing a string is being done by the other functions.
That raises the essential point: these functions aren’t really constructing an expression, they’re building a string, and throwing away information about the structure of the expression. If you also want to be able to compute the value of the expression with your calc
function, you’ll need to generalise this somewhere.
There are two typical ways to do this.
One way is to make a data type for representing expressions, and then your two functions writeOut
and calc
will consume a value of this type and produce a string or number, respectively:
-- An expression is:
data Expression
-- A literal number, containing an ‘Int’ (or whichever type you need);
= Number Int
-- Or an operation on some subexpressions.
| Addition Expression Expression
| Multiplication Expression Expression
| Subtraction Expression Expression
| …
-- You can add this clause to allow ‘Expression’ to be displayed
-- as Haskell code for debugging purposes in GHCi. Generally you
-- should derive ‘Show’ instead of making your own instance.
--
-- deriving (Show)
writeOut :: Expression -> String
writeOut expression = case expression of
Number n -> show n
Addition a b -> concat ["(", writeOut a, " + ", writeOut b, ")"]
…
calc :: Expression -> Int
calc expression = case expression of
Number n -> n
Addition a b -> calc a + calc b
…
Then you can construct an expression and, separately, transform it into a string, number, or whatever else you need, e.g. in GHCi:
> :type Number
Number :: Int -> Expression
> :type Multiplication
Multiplication :: Expression -> Expression -> Expression
> example = Multiplication (Addition (Number 1) (Subtraction (Number 2) (Number 3))) (Number 3)
> :type example
example :: Expression
> writeOut example
"((1+(2-3))*3)"
> calc example
0
> calc (Plus (Number 1) (Number 2))
3
Notice that you still need parentheses around the argument of writeOut
or calc
; if you wrote writeOut Plus (Number 1) (Number 2)
, that would mean calling writeOut
with three arguments, Plus
, Number 1
, and Number 2
, which is not what you want.
If you don’t want to use the capital constructor names, you can make lowercase constructor functions that are equivalent:
number :: Int -> Expression
number n = Number n
-- Or:
-- number = Number
addition :: Expression -> Expression -> Expression
addition a b = Addition a b
-- Or:
-- addition = Addition
…
The second technique is known as tagless final style. It’s slightly more advanced and probably not what you want here, but I include it for completeness. Essentially, instead of a data type, you make a typeclass, representing the set of types that are interpretations of expressions. In your case, those are String
, representing the interpretation of an expression as pretty-printed text (writeOut
), and Int
(or another numeric type like Integer
, Rational
, or Double
), the interpretation of an expression as a value.
You express that by making a typeclass whose methods are the possible terms in your expressions:
-- The class of interpretations of expressions.
class Interpretation a where
number :: Int -> a
addition :: a -> a -> a
subtraction :: a -> a -> a
multiplication :: a -> a -> a
…
And then instances for the different interpretation types:
-- Place this at the top of the file if you include
-- the type signatures in the definitions below.
{-# LANGUAGE InstanceSigs #-}
instance Interpretation String where
number :: Int -> String
number n = show n
addition :: String -> String -> String
addition a b = concat ["(", a, " + ", b, ")"]
…
instance Interpretation Int where
number :: Int -> Int
number n = n
addition :: Int -> Int -> Int
addition a b = a + b
…
Then an expression like multiplication (addition …) (number 3)
has an overloaded type, Interpretation a => a
, and you can evaluate it at different concrete types:
> example :: Interpretation a => a; example = multiplication (addition (number 1) (subtraction (number 2) (number 3))) (number 3)
> example :: String
"((1+(2-3))*3)"
> example :: Int
0
However, while this is flexible, it also makes it more involved to introduce new operations, and requires some slightly more advanced type system features in order to do things like parsing an expression from a string at runtime.
Therefore I recommend you stick to the data type approach, which is what a Haskeller would generally reach for by default unless they had a specific reason not to.
If you want to confine the construction and interpretation of expressions to these particular functions, what you can do is only export the addition
, multiplication
, &c. functions from a module, and leave the data type private, then the only way to construct an argument to writeOut
& calc
will be to call those functions:
module Expr
( number
, addition
, subtraction
…
, writeOut
, calc
) where
-- Expression data type, *not* exported:
data Expression = …
-- Constructor functions, exported:
number :: Int -> Expression
number = Number
…
-- Computations on expressions, also exported:
writeOut :: Expression -> String
writeOut = …
calc :: Expression -> Int
calc = …
Upvotes: 1
Reputation: 477854
You can make use of the type system to prevent people from accessing what is wrapped in a data constructor. For example:
module MyModule(
Locked
, number, multiplication, addition, subtraction
, writeOut
) where
data Locked a = Locked a
writeOut :: Locked a -> a
writeOut (Locked a) = a
number :: Char -> Locked [Char]
number n = Locked [n]
multiplication :: Locked [Char] -> Locked [Char] -> Locked [Char]
multiplication (Locked a) (Locked b) = Locked (a++"*"++b)
addition :: Locked [Char] -> Locked [Char] -> Locked [Char]
addition (Locked a) (Locked b) = Locked ("("++a++"+"++b++")")
subtraction :: Locked [Char] -> Locked [Char] -> Locked [Char]
subtraction (Locked a) (Locked b) = Locked ("("++a++"-"++b++")")
The module here thus does not export the Locked
data constructor, and thus although you can use number
, multiplication
, etc. everywhere, you can not access the result wrapped in the Locked
data constructor without eventually making use of writeOut
.
Another advantage of using Locked
, is that people can not use an arbitrary String
: indeed, number
here only allows to introduce a single character directly, you thus can not concatenate two arbitrary characters. The Locked
thus prevents pople from generating something like 13
, since number '1'
and number '3'
can only be combined with addition
, subtraction
, etc.
But people can still use the functions everywhere, except that it will be quite useless without eventually using writeOut
.
Upvotes: 3
Reputation: 532478
It appears you are trying to implement a DSL and an evaluator for that DSL. Rather than a set of functions, define a data type, then write an evaluator for that type.
data Expr = Number Int
| Mult Expr Expr
| Add Expr Expr
| Subtract Expr Expr
writeOut :: Expr -> String
writeOut (Number x) = show x
writeOut (Add x y) = "(" ++ writeOut x ++ " + " ++ writeOut y ++ ")"
writeOut (Subtract x y) = "(" ++ writeOut x ++ " - " ++ writeOut y ++ ")"
writeOut (Mult x y) = "(" ++ writeOut x ++ " * " ++ writeOut y ++ ")"
print $ writeOut (Mult (Add (Number 1) (Subtract (Number 2) (Number 3))) (Number 3)
I leave it as an exercise to eliminate the outermost parentheses.
As a bonus assignment, define a different evaluator that evaluate the expression to a single Int
, rather than display it:
evaluate :: Expr -> Int
evaluate = undefined
Upvotes: 5