patternMinor
L system equations
Viewed 0 times
systemequationsstackoverflow
Problem
This is my first program on my second try to learn haskell :)
Program invocation is something like this:
It takes two arguments, first argument is number of iterations, second argument is a filename to read L-System from.
File structure is simple, first line is axiom, each line of the form 'x ys' treated as transformation rule. So, something like this:
As output, program should return something like;
So, how does it look?
import Data.Maybe (mapMaybe)
import Control.Exception (try, IOException)
import System.Environment (getArgs)
type Rule = (Char, String)
type Axiom = String
type LSystem = (Axiom, [Rule])
getRule :: Char -> [Rule] -> String
getRule ch [] = [ch]
getRule ch ((rulekey, rulevalue):rest)
| ch == rulekey = rulevalue
| otherwise = getRule ch rest
lSystemIterate :: Int -> LSystem -> String
lSystemIterate 0 (axiom, _) = axiom
lSystemIterate n (axiom, rules) =
lSystemIterate (n-1)
(concatMap (\ch -> getRule ch rules) axiom, rules)
splitRule :: String -> Maybe (Char, String)
splitRule (x:' ':rest) = Just (x,rest)
splitRule _ = Nothing
readLSystem :: String -> IO (Maybe LSystem)
readLSystem filename = do
strorexc return Nothing
Right [] -> return Nothing
Right str -> return (Just (ax, mapMaybe splitRule rules))
where (ax:rules) = lines str
iterateIOMaybe :: Int -> IO (Maybe LSystem) -> IO (Maybe String)
iterateIOMaybe n = fmap $ fmap (lSystemIterate n)
main = do
args do
maybelsystem putStrLn $ lSystemIterate (read n) lsystem
Nothing -> putStrLn "File doesn't exist, or it is empty"
_ -> putStrLn "Usage: progname iterations filename"Program invocation is something like this:
runhaskell lsystem.hs 3 dragon.ls assuming that it is saved as lsystem.hs.It takes two arguments, first argument is number of iterations, second argument is a filename to read L-System from.
File structure is simple, first line is axiom, each line of the form 'x ys' treated as transformation rule. So, something like this:
FX
X X+YF+
Y -FX-YAs output, program should return something like;
FX+YF++-FX-YF++-FX+YF+--FX-YF+So, how does it look?
Solution
This is good! You have restricted imports, type aliases, type signatures on all top-level definitions (except
I might also reconsider your error handling with
Just something to think about. Note also how I aligned both cases of the function on
You could do with another few type aliases as well to clear up the purposes of
I think the type of
It also shows that
The remaining set of changes I have are all function implementation related. You can leverage
For instance,
The next thing I'd recognize is that lazy evaluation helps simplify dealing with potentially infinite sequences of values. Instead of generating a finite number of states in
(You also seem to have some dead code,
main, which you should add but is at least obvious), total functions... All I've got are details.splitRule should use the Rule type alias in its return. I.e., splitRule :: String -> Maybe Rule. I would probably also name that function “readRule,” split refers to an implementation detail; you are providing the function with what you know (or hope) is a single serialized rule to be parsed, not (say) dividing a rule off by splitting it into two.I might also reconsider your error handling with
readRule, right now your program silently discards rules it can't parse. That's certainly a valid choice but you might find that it actually makes sense to halt program execution on badly defined input, like so—readRule :: String -> Rule
readRule (x:' ':rest) = (x, rest)
readRule bad = error $ "readRule: Can't parse " ++ show badJust something to think about. Note also how I aligned both cases of the function on
= so their right hand sides start in the same column. This can be tedious but aligning things like multiline functions or lists can really help readers of your code as they scan through, the extra whitespace and visual similarity helps stop your Haskell code from looking like word soup.You could do with another few type aliases as well to clear up the purposes of
Chars and Strings in your program. At the very least I'd give—type Predecessor = Char
type Successor = String
type Rule = (Predecessor, Successor)I think the type of
getRule is more obvious then.getRule :: Predecessor -> [Rule] -> SuccessorIt also shows that
getRule might not be the correct name, because the function doesn't actually return a Rule but instead the application of the set of system rules (or the identity production if the Predecessor is a terminal). I think apply makes sense here.The remaining set of changes I have are all function implementation related. You can leverage
Prelude functions to achieve a lot in Haskell, doing so often makes your code more readable as common patterns get expressed by familiar names.For instance,
apply looks a lot like finding an element in an association list, which in the Prelude is provided by lookup :: (Eq a) => a -> [(a, b)] -> Maybe b. Digging a little further into base we can also deal with that Maybe using Data.Maybe.fromMaybe :: a -> Maybe a -> a.apply :: Predecessor -> [Rule] -> Successor
apply p rules = fromMaybe (terminal p) $ lookup p rules
terminal :: Predecessor -> Successor
terminal p = [p]The next thing I'd recognize is that lazy evaluation helps simplify dealing with potentially infinite sequences of values. Instead of generating a finite number of states in
lsystemIterate, generate every state lazily and return the iteration requested.states :: LSystem -> [String]
states (axiom, rules) = iterate (concatMap (`apply` rules)) axiom
after :: LSystem -> Int -> String
after l n = states l !! n(You also seem to have some dead code,
iterateIOMaybe.)Code Snippets
readRule :: String -> Rule
readRule (x:' ':rest) = (x, rest)
readRule bad = error $ "readRule: Can't parse " ++ show badtype Predecessor = Char
type Successor = String
type Rule = (Predecessor, Successor)getRule :: Predecessor -> [Rule] -> Successorapply :: Predecessor -> [Rule] -> Successor
apply p rules = fromMaybe (terminal p) $ lookup p rules
terminal :: Predecessor -> Successor
terminal p = [p]states :: LSystem -> [String]
states (axiom, rules) = iterate (concatMap (`apply` rules)) axiom
after :: LSystem -> Int -> String
after l n = states l !! nContext
StackExchange Code Review Q#87567, answer score: 2
Revisions (0)
No revisions yet.