Eben Kadile
Eben Kadile

Reputation: 779

What is the nicest way to make a function that takes Float or Double as input?

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

Answers (2)

leftaroundabout
leftaroundabout

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

Rein Henrichs
Rein Henrichs

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

Related Questions