Líně, čistě, funkcionálně

Práce SOČ, obor 18: Informatika

Jan Špaček

Obsah práce

„Užití funkcionálního programovacího jazyka Haskell k řešení úloh Ústředních kol ČR Soutěže v programování“

Řešené úlohy

Krunimír ‐ jednoduchý programovací jazyk kreslící želví grafiku

Bílá paní ‐ hledání cesty v bludišti s pohyblivými zvědy a možností procházet zdmi.

Haskell

Krunimír

Jednoduchý programovací jazyk na kreslení želví grafiky.

Želví grafika

Po písku se pohybuje želva a kreslí za sebou stopu. Uživatel želvu ovládá jednoduchým procedurálním programovacím jazykem.

Jazyk

Základní příkazy:

  • forward(délka)
  • right(úhel), left(úhel)
  • pen(tloušťka pera)
  • color(červená,zelená,modrá)

Řídící struktury:

  • if(podmínka) { ... }
  • repeat(počet iterací) { ... }
  • define jméno_procedury(param1, param2, ...) {...}
  • jméno_procedury(arg1, arg2, ...)
  • split { ... }

Jako výrazy (argumenty příkazů a procedur) lze použít:

  • číselné literály,
  • parametry aktuální procedury,
  • binární operace +, -, *, / (dělení celočíselné, priority a asociativity jako v matematice),
  • prefixovou negaci (operátor -).

Jediným „typem“ je celé číslo.

define square(side) {
  pen(1)
  repeat(4) {
    forward(side) right(90)
  }
  pen(0)
}

define row() {
  repeat(4) {
    square(114) forward(163) 
  }
  forward(-652) right(90)
  forward(163) left(90)
}

forward(301) left(90)
forward(301) right(180)
repeat(4) { row() }
define koch(n) {
  if(n) {
    koch(n-1)
    left(60) koch(n-1)
    right(120) koch(n-1)
    left(60) koch(n-1)
  }
  if(1-n) {
    forward(2)
  }
}

pen(0) forward(-175) 
left(90) forward(250) 
pen(1)
right(120) koch(5)
right(120) koch(5)
right(120) koch(5)

Řešení

Program je rozdělen na několik částí:

  • Parser přečte program a vytvoří abstraktní syntaktický strom (AST).
  • Evaluator vyhodnotí dané AST a vrátí výslednou stopu, kterou želva za sebou zanechá.
  • Renderer stopu vykreslí do obrázku.

Jednotlivé části jsou víceméně nezávislé.

Krunimir.Ast

Definice typů představující syntaktický strom (AST)

Typ Stmt reprezentuje příkaz:

data Stmt = 
    ForwardStmt Expr
  | LeftStmt Expr
  | RightStmt Expr
  | PenStmt Expr
  | ColorStmt Expr Expr Expr
  | RepeatStmt Expr [Stmt]
  | IfStmt Expr [Stmt]
  | SplitStmt [Stmt]
  | CallStmt String [Expr]
  deriving Show

Expr představuje výraz:

data Expr =
    LiteralExpr Integer
  | VariableExpr String
  | BinopExpr Op Expr Expr
  | NegateExpr Expr
  deriving Show

data Op = AddOp | SubOp | MulOp | DivOp
  deriving Show

Krunimir.Parser

Exportuje jedinou funkci parse:

parse :: FilePath -> String -> Either ParseError Program

Založen na PEG gramatice:

program      <- space* top-stmt* eof
top-stmt     <- define / stmt

define       <- "define" space+ identifier 
                lparen (identifier (comma identifier)*)? rparen
                lbrace stmt* rbrace

stmt         <- repeat-stmt / if-stmt / split-stmt / proc-stmt
repeat-stmt  <- "repeat" lparen expr rparen lbrace stmt* rbrace
if-stmt      <- "if" space* lparen expr rparen lbrace stmt* rbrace
split-stmt   <- "split" space* lbrace stmt* rbrace
proc-stmt    <- "forward" space* lparen expr rparen
              / "left" space* lparen expr rparen
              / "right" space* lparen expr rparen
              / "pen" space* lparen expr rparen
              / "color" space* lparen expr comma expr comma expr rparen
              / identifier space* lparen (expr (comma expr)*)? rparen
expr         <- add-expr
add-expr     <- mul-expr (add-op space* mul-expr)*
mul-expr     <- neg-expr (mul-op space* neg-expr)*
neg-expr     <- (neg-op space*)? a-expr

add-op       <- "+" / "-"
mul-op       <- "*" / "/"
neg-op       <- "-"

a-expr       <- lit-expr / var-expr / lparen expr rparen
lit-expr     <- integer
var-expr     <- identifier

integer      <- digit+ space*
identifier   <- letter alpha-num space*

lparen       <- "(" space*
rparen       <- ")" space*
lbrace       <- "{" space*
rbrace       <- "}" space*
comma        <- "," space*

digit        <- [0-9]
letter       <- [a-zA-Z]
alpha-num    <- [a-zA-Z0-9]
space        <- [ \t\r\n\v\f]
eof          <- !.

Implementován pomocí monadických parserů z knihovny parsec.

Gramatika PEG:

define       <- "define" space+ identifier
              lparen (identifier (comma identifier)*)? rparen
              lbrace stmt* rbrace

Odpovídající kód Haskellu:

define :: Parser Define
define = do
  string "define" >> skipMany1 space
  name <- identifier
  params <- parens $ identifier `sepBy` comma
  stmts <- braces $ many stmt
  return $ Define name params stmts

Dílčí parsery se skládají do větších celků...

...užitím vysokoúrovňových kombinátorů.

Z výsledků se sestavuje AST.

Krunimir.Evaluator

Exportuje funkci eval, která vyhodnotí program:

eval :: Program -> Trace

Trace představuje stopu želvy:

data Trace
  = EmptyTrace
  | SplitTrace Trace Trace
  | SegmentTrace Segment Trace
  deriving Show

data Segment = Segment Point Point Color Int 
  deriving Show

type Point = (Float,Float)
type Color = (Int,Int,Int)

Vykreslování

  • PNG — rastrový formát, použita knihovna GD.
  • SVG — vektorový formát, založen na XML.
Gosperova křivka

Gosperova křivka

Hilbertova křivka

Hilbertova křivka

Křivka arrowhead

Křivka arrowhead

Bílá paní

Hledání nejkratší cesty v bludišti (hradu)

  • v hradě se po cyklických drahách pohybují zvědové
  • bílá paní může procházet zdmi (nerada)
<
@
>

Algoritmus

Použitý algoritmus je založen na prohledávání do šířky.

Jednoduché hledání nejkratší cesty

Přechody mezi stavy (x,y), konec jakmile dorazíme do cíle.

Hledání nejkratší cesty se zvědy

Přechody mezi stavy (x,y,t), konec jakmile dorazíme do cíle.

Dva zvědové s periodou 2:

Stačí sledovat stavy (x,y,t mod p)

Hledání nejkratší cesty se zvědy a procházením zdí

Projdeme zdí pouze pokud neexistuje žádná jiná cesta k cíli.

<
>

Implementace

moves :: Bool -> Slice -> Slice -> (Loc,Path) -> [(Loc,Path)]
flood :: Castle -> [Slice] -> STArray s (Int,Loc) (Maybe Path) ->
  [(Loc,Path)] -> ST s (Either Path [(Loc,Path)])
navigate :: Castle -> [Slice] -> Bool -> Maybe [Loc]

Demo

Zdrojové kódy na serveru GitHub: github.com/honzasp/funsp

Stránky (včetně prezentace) honzasp.github.io/funsp

Jan Špaček Wichterlovo gymnázium, Ostrava-Poruba
?