data Game = Game { _score :: Int , _units :: [Unit] , _boss :: Unit } deriving (Show)
data Unit = Unit { _health :: Int , _position :: Point } deriving (Show)
data Point = Point { _x :: Double , _y :: Double } deriving (Show)
lens
function from Control.Lens
. For example, we can define a lens score
for the _score
field as follows: import Control.Lens score :: Lens' Game Int score = lens _score (\game v -> game { _score = v })
Lens
as a map for navigating complex data types. We use a lens of a score
to come from the Game
type to _score
.Lens' Game Int
means that we should start with Game
and end Int
(for the _score
field in our case). Similarly, our other lenses clearly reflect the starting and ending points of their types: units :: Lens' Game [Unit] units = lens _units (\game v -> game { _units = v }) boss :: Lens' Game Unit boss = lens _boss (\game v -> game { _boss = v }) health :: Lens' Unit Int health = lens _health (\unit v -> unit { _health = v }) position :: Lens' Unit Point position = lens _position (\unit v -> unit { _position = v }) x :: Lens' Point Double x = lens _x (\point v -> point { _x = v }) y :: Lens' Point Double y = lens _y (\point v -> point { _y = v })
Template Haskell
to create lenses for us: {-# LANGUAGE TemplateHaskell #-} import Control.Lens data Game = Game { _score :: Int , _units :: [Unit] , _boss :: Unit } deriving (Show) data Unit = Unit { _health :: Int , _position :: Point } deriving (Show) data Point = Point { _x :: Double , _y :: Double } deriving (Show) makeLenses ''Game makeLenses ''Unit makeLenses ''Point
makeLenses
declaration makeLenses
after the declaration of data types. initialState :: Game initialState = Game { _score = 0 , _units = [ Unit { _health = 10 , _position = Point { _x = 3.5, _y = 7.0 } } , Unit { _health = 15 , _position = Point { _x = 1.0, _y = 1.0 } } , Unit { _health = 8 , _position = Point { _x = 0.0, _y = 2.1 } } ] , _boss = Unit { _health = 100 , _position = Point { _x = 0.0, _y = 0.0 } } }
import Control.Monad.Trans.Class import Control.Monad.Trans.State strike :: StateT Game IO () strike = do lift $ putStrLn "*shink*" boss.health -= 10
strike
) prints a similar sound to us in the console, further reduces the health of the boss 10 health units.StateT Game IO
monad. You may think that this is such a built-in language where we create a layer of pure game states (i.e. StateT Game
) on top of side effects (i.e. IO
) so that we can simultaneously change states and print our sweetest effects from the battle to the console . All you need to remember now is that if we want to use side effects we need to use the lift
function.ghci
). For this we need the initial state: >>> execStateT strike initialState *shink* Game {_score = 0, _units = [Unit {_health = 10, _position = Poin t {_x = 3.5, _y = 7.0}},Unit {_health = 15, _position = Point {_ x = 1.0, _y = 1.0}},Unit {_health = 8, _position = Point {_x = 0 .0, _y = 2.1}}], _boss = Unit {_health = 90, _position = Point { _x = 0.0, _y = 0.0}}}
execStateT
function takes our code with the states and our initial state, starts it, and produces a new state. The interpreter automatically displays to us on the screen, and we can immediately analyze the result. The output turned out porridge, however, if you train your eye, you can see that the boss now has only 90 health units. >>> newState <- execStateT strike initialState *shink*
>>> newState^.boss.health 90
boss.health -= 10
boss
and health
are just the lenses we defined above(-=)
- infix function(.)
- functional composition from haskel Prelude
!(.)
Is a functional composition ?! Really?!Lens' ab
is a synonym for the type of functions of higher order: type Lens' ab = forall f . (Functor f) => (b -> fb) -> (a -> fa)
Lens' ab
is a higher order function that takes type (b -> fb)
as an input argument and returns a new function of type (a -> fa)
. Functor
- part of the theory, which can now be regarded as "magic".boss
and health
: boss :: Lens' Game Unit -- : boss :: (Functor f) => (Unit -> f Unit) -> (Game -> f Game) health :: Lens' Unit Int -- : health :: (Functor f) => (Int -> f Int) -> (Unit -> f Unit)
(.) :: (b -> c) -> (a -> b) -> (a -> c) (f . g) x = f (gx)
a ~ (Int -> f Int) b ~ (Unit -> f Unit) c ~ (Game -> f Game)
(.) :: ((Unit -> f Unit) -> (Game -> f Game)) -> ((Int -> f Int ) -> (Unit -> f Unit)) -> ((Int -> f Int ) -> (Game -> f Game))
Lens'
, we will get: (.) :: Lens' Game Unit -> Lens' Unit Int -> Lens' Game Int boss . health :: Lens' Game Int
(.)
Is the categorical composition operator, and the id
identity function is also a lens: (.) :: Lens' xy -> Lens' yz -> Lens' xz id :: Lens' xx
bossHP :: Lens' Game Int bossHP = boss.health
boss.health
. boss.health
. strike :: StateT Game IO () strike = do lift $ putStrLn "*shink*" bossHP -= 10
>>> newState^.bossHP 90
fireBreath :: StateT Game IO () fireBreath = do lift $ putStrLn "*rawr*" units.traversed.health -= 3
traversed :: Traversal' [a] a
traversed
helps us to “get to the bottom” of the values ​​in the list so that we can work with it as a whole, instead of manually traversing the entire list. However, this time we use the type Traversal'
instead of Lens'
.Traversal'
is the same Lens'
, only weaker: type Traversal' ab = forall f . (Applicative f) => (b -> fb) -> (a -> fa)
Traversal'
and Lens'
, we get a weaker type, namely Traversal'
. It works regardless of the order in which we combine: (.) :: Lens' ab -> Traversal' bc -> Traversal' ac (.) :: Traversal' ab -> Lens' bc -> Traversal' ac units :: Lens' Game [Unit] units.traversed :: Traversal' Game Unit units.traversed.health :: Traversal' Game Int
>>> :t units.traversed.health units.traversed.health :: Applicative f => (Int -> f Int) -> Game -> f Game
Traversal' Game Int
! partyHP :: Traversal' Game Int partyHP = units.traversed.health fireBreath :: StateT Game IO () fireBreath = do lift $ putStrLn "*rawr*" partyHP -= 3
partyHP
function to find out the new health value: >>> newState <- execStateT fireBreath initialState *rawr* >>> newState^.partyHP <interactive>:3:11: No instance for (Data.Monoid.Monoid Int) arising from a use of `partyHP' .........
Traversal'
weaker than Lens'
: bypassed can point to multiple values, so they do not support a well-defined way to show a single value. The system helped us get rid of a possible bug.toListOf
function: toListOf :: Traversal' ab -> a -> [b]
>>> toListOf partyHP newState [7,12,5]
toListOf
function: (^..)
: >>> initialState^..partyHP [10,15,8] >>> newState^..partyHP [7,12,5]
fireBreath
. around :: Point -> Double -> Traversal' Unit Unit around center radius = filtered (\unit -> (unit^.position.x - center^.x)^2 + (unit^.position.y - center^.y)^2 < radius^2 )
filtered
is actually not theoretically enumerated, because it does not save the number of elements fireBreath :: Point -> StateT Game IO () fireBreath target = do lift $ putStrLn "*rawr*" units.traversed.(around target 1.0).health -= 3
> initialState^..units.traversed.position [Point {_x = 3.5, _y = 7.0},Point {_x = 1.0, _y = 1.0},Point {_x = 0.0, _y = 2.1}]
>>> newState <- execStateT (fireBreath (Point 0.5 1.5)) initialState *rawr* >>> (initialState^..partyHP, newState^..partyHP) ([10,15,8],[10,12,5])
retreat :: StateT Game IO () retreat = do lift $ putStrLn "Retreat!" zoom (units.traversed.position) $ do x += 10 y += 10
partyLoc :: Traversal' Game Point partyLoc = units.traversed.position retreat :: StateT Game IO () retreat = do lift $ putStrLn "Retreat!" zoom partyLoc $ do x += 10 y += 10
>>> initialState^..partyLoc [Point {_x = 3.5, _y = 7.0},Point {_x = 1.0, _y = 1.0},Point {_x = 0.0, _y = 2.1}] >>> newState <- execStateT retreat initialState Retreat! >>> newState^..partyLoc [Point {_x = 13.5, _y = 17.0},Point {_x = 11.0, _y = 11.0},Point {_x = 10.0, _y = 12.1}]
zoom :: Traversal ab -> StateT b IO r -> StateT a IO r
zoom
feature has several theoretical great features. For example, we expect that a composition of scaled 2x lenses should give the same result as the scaling of their compositions. zoom lens1 . zoom lens2 = zoom (lens1 . lens2)
zoom id = id
zoom
function is a functor, which means it obeys the laws of the functor! battle :: StateT Game IO () battle = do -- ! forM_ ["Take that!", "and that!", "and that!"] $ \taunt -> do lift $ putStrLn taunt strike -- ! fireBreath (Point 0.5 1.5) replicateM_ 3 $ do -- ! retreat -- zoom (boss.position) $ do x += 10 y += 10
>>> execStateT battle initialState Take that! *shink* and that! *shink* and that! *shink* *rawr* Retreat! Retreat! Retreat! Game {_score = 0, _units = [Unit {_health = 10, _position = Poin t {_x = 33.5, _y = 37.0}},Unit {_health = 12, _position = Point {_x = 31.0, _y = 31.0}},Unit {_health = 5, _position = Point {_x = 30.0, _y = 32.1}}], _boss = Unit {_health = 70, _position = P oint {_x = 30.0, _y = 30.0}}}
lens
library, which is considered to be one of the royal treasures of the Haskell ecosystem. You can also use lenses for pure programming in order to compress powerful and complex structures into very readable and elegant code. However, you can still write a lot about this wonderful library.Source: https://habr.com/ru/post/190442/
All Articles