Reputation: 779
Say I want to implement the Fermi function (the simplest example of a logistic curve) so that if it's passed a Float
it returns a Float
and if it's passed a Double
it returns a Double
. Here's what I've got:
e = 2.7182845904523536
fermiFunc :: (Floating a) => a -> a
fermiFunc x = let one = fromIntegral 1 in one/(one + e^(-x))
The problem is that ghc says e
is a Double
. Defining the variable one
is also kinda gross. The other solution I've thought of is to just define the function for doubles:
e = 2.7182845904523536
fermiFuncDouble :: Double -> Double
fermiFuncDouble x = 1.0/(1.0 + e^(-x))
Then using Either
:
fermiFunc :: (Floating a) => Either Float Double -> a
fermiFunc Right x = double2Float (fermiFuncDouble (float2Double x))
fermiFunc Left x = fermiFuncDouble x
This isn't very exciting though because I might as well have just written a separate function for the Float
case that just handles the casting and calls fermiFuncDouble
. Is there a nice way to write a function for both types?
Upvotes: 3
Views: 186
Reputation: 120741
Don't write e^x
, ever, in any language. That is not the exponential function, it's the power function.
The exponential function is called exp
, and its definition actually has little to do with the power operation โ it's defined, depending on your taste, as a Taylor series or as the unique solution to the ordinary differential equation dโd๐ฅ exp ๐ฅ = exp ๐ฅ with boundary condition exp 0 = 1. Now, it so happens that, for any rational n, we have exp n โก (exp 1)n and that motivates also defining the power operation for numbers in โ or โ addition to โ, namely as
az := exp (z ยท ln a)
...but e๐ฅ should be understood as really just a shortcut for writing exp(๐ฅ) itself.
So rather than defining e
somewhere and trying to take some power of it, you should use exp
just as it is.
fermiFunc :: Floating a => a -> a
fermiFunc x = 1/(1 + exp (-x))
...or indeed
fermiFunc = recip . succ . exp . negate
Upvotes: 8
Reputation: 15605
Assuming that you want floating point exponent, that's (**)
. (^)
is integral exponent. Rewriting your function to use (**)
and letting GHC infer the type gives:
fermiFunc x = 1/(1 + e ** (-x))
and
> :t fermiFunc
fermiFunc :: (Floating a) => a -> a
Since Float
and Double
both have Floating
instances, fermiFunc
is now sufficiently polymorphic to work with both.
(Note: you may need to declare a polymorphic type for e
to get around the monomorphism restriction, i.e., e :: Floating a => a
.)
In general, the answer to "How do I write a function that works with multiple types?" is either "Write it so that it works universally for all types." (parametric polymorphism, like map
), "Find (or create) one or more typeclasses that they share that provides the behaviour you need." (ad hoc polymorphism, like show
), or "Create a new type that is the sum of those types." (like Either
).
The latter two have some tradeoffs. For instance, type classes are open (you can add more at any time) while sum types are closed (you must modify the definition to add more types). Sum types require you to know which type you are dealing with (because it must be matched up with a constructor) while type classes let you write polymorphic functions.
You can use :i
in GHCi to list instances and to list instance methods, which might help you to locate a suitable typeclass.
Upvotes: 8