I made this function in python
def calc(a): return lambda op: {
' ': lambda b: calc(a b),
'-': lambda b: calc(a-b),
'=': a}[op]
So you can make a calculation like this
calc(1)(" ")(1)(10)("-")(7)("=")
And the result will be 5
I wanna make the same function in haskell to lean about lambdas - however I am getting parse errors:
My code looks like this:
calc :: Int -> (String -> Int)
calc a = \ op
| op == " " = \ b calc a b
| op == "-" = \ b calc a b
| op == "=" = a
main = calc 1 " " 1 " " 10 "-" 7 "="
CodePudding user response:
There are numerous syntactical problems with the code you have posted. I won't address them here, though: you will discover them yourself after going through a basic Haskell tutorial. Instead I'll focus on a more fundamental problem with the project, which is that the types don't really work out. Then I'll show a different approach that gets you the same outcome, to show you it is possible in Haskell once you've learned more.
While it's fine in Python to sometimes return an function-of-int and sometimes a string, this isn't allowed in Haskell. GHC has to know at compile time what type will be returned; you can't make that decision at runtime based on whether a string is "="
or not. So you need a different type for the "keep calc
ing" argument than the "give me the answer" argument.
This is possible in Haskell, and in fact is a technique with a lot of applications, but it's maybe not the best place for a beginner to start. You are inventing continuations. You want calc 5 plus 2 minus 1 equals
to produce 6, for some definitions of the functions used therein. Achieving this requires some advanced features of the Haskell language and some funny types, which is why I say it is not for beginners. But, below is an implementation that meets this goal. I won't explain it in detail, because there is too much for you to learn first. Hopefully after some study of Haskell fundamentals, you can return to this interesting problem and understand my solution.
{-# LANGUAGE ExplicitForAll, RankNTypes #-}
type Calc a = forall r. (a -> r) -> r
calc :: a -> Calc a
calc x k = k x
equals :: a -> a
equals = id
lift2 :: (a -> a -> a) -> a -> a -> Calc a
lift2 f x y = calc (f x y)
plus :: Num a => a -> a -> Calc a
plus = lift2 ( )
minus :: Num a => a -> a -> Calc a
minus = lift2 (-)
ghci> calc 5 plus 2 minus 1 equals
6
CodePudding user response:
A Haskell function of type A -> B
has to return a value of the fixed type B
every time it's called (or fail to terminate, or throw an exception, but let's neglect that).
A Python function is not similarly constrained. The returned value can be anything, with no type constraints. As a simple example, consider:
def foo(b):
if b:
return 42 # int
else:
return "hello" # str
In the Python code you posted, you exploit this feature to make calc(a)(op)
to be either a function (a lambda) or an integer.
In Haskell we can't do that. This is to ensure that the code can be type checked at compile-time. If we write
bar :: String -> Int
bar s = foo (reverse (reverse s) == s)
the compiler can't be expected to verify that the argument always evaluates to True
-- that would be undecidable, in general. The compiler merely requires that the type of foo is something like Bool -> Int
. However, we can't assign that type to the definition of foo
shown above.
So, what we can actually do in Haskell?
One option could be to abuse type classes. There is a way in Haskell to create a kind of "variadic" function exploiting some kind-of convoluted type class machinery. That would make
calc 1 " " 1 " " 10 "-" 7 :: Int
type-check and evaluate to the wanted result. I'm not attempting that: it's complex and "hackish", at least in my eye. This hack was used to implement printf
in Haskell, and it's not pretty to read.
Another option is to create a custom data type and add some infix operator to the calling syntax. This also exploits some advanced feature of Haskell to make everything type check.
{-# LANGUAGE GADTs, FunctionalDependencies, TypeFamilies, FlexibleInstances #-}
data R t where
I :: Int -> R String
F :: (Int -> Int) -> R Int
instance Show (R String) where
show (I i) = show i
type family Other a where
Other String = Int
Other Int = String
(#) :: R a -> a -> R (Other a)
I i # " " = F (i ) -- equivalent to F (\x -> i x)
I i # "-" = F (i-) -- equivalent to F (\x -> i - x)
F f # i = I (f i)
I _ # s = error $ "unsupported operator " s
main :: IO ()
main =
print (I 1 # " " # 1 # " " # 10 # "-" # 7)
The last line prints 5
as expected.
The key ideas are:
The type
R a
represents an intermediate result, which can be an integer or a function. If it's an integer, we remember that the next thing in the line should be a string by makingI i :: R String
. If it's a function, we remember the next thing should be an integer by havingF (\x -> ...) :: R Int
.The operator
(#)
takes an intermediate result of typeR a
, a next "thing" (int or string) to process of typea
, and produces a value in the "other type"Other a
. Here,Other a
is defined as the typeInt
(respectivelyString
) whena
isString
(resp.Int
).