13
\$\begingroup\$

Since it was getting boring to always do OOP Languages I decided to dabble in the functional realm of programming.

For that I chose the narrowly missed community-challenge by Edward: Resistor Mania, since it really is a quick-to solve using recursive and functional style.

Challenge description:

In electronics, two resistors in series have a combined resistance \$R_1+R_2\$, and two resistors in parallel have combined resistance of \$\displaystyle \frac{R_1 R_2}{R_1 + R_2}\$. Given an infinite supply of \$270\Omega\$ resistors with \$5\%\$ tolerance, write a program to describe how to combine them into any arbitrary resistance value.

To make things a little easier for me, I decided to ditch the tolerance for this first prototype. Instead of \$270\Omega\$ I also ask the user to provide the values for our resistors.

The code works as expected for rather simple inputs, and I didn't check for more complicated stuff yet.

The ouptut comes as a wiring help that uses simple "equations" to describe parallel and combining wiring.

Output of some manual tests:

*Main> resistor_mania 60 120
"60.0+60.0"
*Main> resistor_mania 120 60
"120.0||(120.0)"
*Main> resistor_mania 120 120
"120.0"

Code:

module Main where

import System.IO

main = do
    hSetBuffering stdin LineBuffering;
    putStrLn "Please enter the value of resistors we use";
    res_input <- getLine;
    putStrLn "Please enter the value you want to model with these resistors";
    target_value <- getLine;
    putStrLn (show (resistor_mania (read res_input) (read target_value)));


{-
Calculate the remaining resistor-strenght necessary to get to a given target
 when going in parallel to the given existing resistor.
-}
p_resistor :: Fractional a => a -> a -> a
p_resistor = (\existing target -> (1 / ((1 / target) - (1 / existing))));

{-
Resistor_Mania: Create a wiring schema for a given target resistor value
with one single available kind of resistor
-}
resistor_mania :: (Show a, Ord a, Fractional a) => a -> a -> String
resistor_mania resistor target =
    if resistor == target;
    then (show resistor);
    else if resistor < target;
    then (show resistor) ++ "+" ++ (resistor_mania resistor (target - resistor));
    else (show resistor) ++ "||(" ++ (resistor_mania resistor (p_resistor resistor target)) ++ ")";

There's quite a few things that I hope can be improved here. For one I don't like how p_resistor is declared as a lambda, and resistor_mania isn't.
Another thing I don't like is the concatenation of Strings in the control structure and the type-signature of resistor_mania makes me uneasy :/

\$\endgroup\$
8
\$\begingroup\$

Guards

Using so many if then and else is really weird in Haskell, I suggest using the guards:

resistor_mania resistor target
    | resistor == target = (show resistor)
    | resistor < target = (show resistor) ++ "+" ++ (resistor_mania resistor (target - resistor))
    | otherwise = (show resistor) ++ "||(" ++ (resistor_mania resistor (p_resistor resistor target)) ++ ")"

This is the standard way of listing mutually exclusive conditions in a function definition.

Semicolons

Haskell uses a two-dimensional syntax, semicolons are not needed and should be omitted as they convey no additional information, just noise.

Prefer mainstream function definitions

p_resistor = (\existing target -> (1 / ((1 / target) - (1 / existing))))

Becomes:

p_resistor existing target = (1 / ((1 / target) - (1 / existing))))

That looks maybe less cool, but surely faster to understand.

Types

the type-signature of resistor_mania makes me uneasy :/

Haskell is type-centric, so it is good that you are worrying about types.

I deleted your types and asked the compiler what types it would like for your functions.

*Main> :t p_resistor
p_resistor :: Double -> Double -> Double
*Main> :t resistor_mania
resistor_mania :: Double -> Double -> String

I think these suggested types are simpler, and I would use them instead of your bulky type declarations.

Non-closing parenthesis

$ is like a parenthesis but you do not need to close it:

putStrLn $ show $ resistor_mania (read res_input) (read target_value)

This is somewhat subjective though...

\$\endgroup\$
  • \$\begingroup\$ Also, 1 / ((1 / target) - (1 / existing)) is likely easier understood as (target * existing) / (existing - target) \$\endgroup\$ – Mokosha Jan 7 '16 at 20:26
4
\$\begingroup\$

Instead of guards, I would use

resistor_mania resistor target = case compare resistor target of
    EQ -> show resistor
    LT -> show resistor ++ "+" ++ resistor_mania resistor (target - resistor)
    GT -> show resistor ++ "||(" ++ resistor_mania resistor (p_resistor resistor target) ++ ")"

This is like a switch statement. Looking at it, I see the further refactoring of

resistor_mania resistor target = show resistor ++ case compare resistor target of
    EQ -> ""
    LT -> "+" ++ resistor_mania resistor (target - resistor)
    GT -> "||(" ++ resistor_mania resistor (p_resistor resistor target) ++ ")"

Since resistor never changes, you could do

resistor_mania resistor = go where
    go target = show resistor ++ case compare resistor target of
        EQ -> ""
        LT -> "+" ++ go (target - resistor)
        GT -> "||(" ++ go (p_resistor resistor target) ++ ")"

readLn instead of getLine will read the line for you after getting it. I also recommend the above use of $. (That's actually just an operator of type (a -> b) -> a -> b which applies the function on the left to the argument on the right; it has lowest operator precedence, so it's handy for avoiding parentheses.) Yes, that lambda (\) is unnecessary.

For fun, you could replace the () by $ in the output. Then you could actually implement this in terms of unfoldr:

resistor_mania resistor = supersperse (show resistor) .: unfoldr $
    \target -> case compare resistor target of
        EQ -> Nothing
        LT -> Just (" + $ ", target - resistor)
        GT -> Just (" || $ ", p_resistor resistor target)

(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(f .: g) x = f . g x

supersperse :: [a] -> [[a]] -> [a]
supersperse xs xss = xs ++ intercalate xs xss ++ xs
\$\endgroup\$
  • 5
    \$\begingroup\$ aaaand I've got no idea what you're doing there in the last code-block. I think there is an explanation, but... I have no clue what the heck happens there... also I'm not sure how the output changes you mention are helping me :/ \$\endgroup\$ – Vogel612 Jan 5 '16 at 21:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.