Als Beispiel meines comment oben, können Sie Code mit dem Schreib State
Monade wie
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
import Data.Text (Text)
import qualified Data.Text as Text
import Control.Monad.State
data MyState = MyState
{ _count :: Int
, _messages :: [Text]
} deriving (Eq, Show)
makeLenses ''MyState
type App = State MyState
incrCnt :: App()
incrCnt = modify (\my -> my & count +~ 1)
logMsg :: Text -> App()
logMsg msg = modify (\my -> my & messages %~ (++ [msg]))
logAndIncr :: Text -> App()
logAndIncr msg = do
incrCnt
logMsg msg
app :: App()
app = do
logAndIncr "First step"
logAndIncr "Second step"
logAndIncr "Third step"
logAndIncr "Fourth step"
logAndIncr "Fifth step"
Beachten Sie, dass die Verwendung zusätzlicher Betreiber von Control.Lens
können auch schreiben Sie incrCnt
und logMsg
als
incrCnt = count += 1
logMsg msg = messages %= (++ [msg])
, die einen anderen Vorteil der Zusammenarbeit mit der lens
Bibliothek State
in Kombination ist, aber für die Zwecke des Vergleichs ich nicht mit ihnen in diesem Beispiel.So schreiben Sie das Bestehen der entsprechende Code oben mit nur Argument wäre es eher wie
incrCnt :: MyState -> MyState
incrCnt my = my & count +~ 1
logMsg :: MyState -> Text -> MyState
logMsg my msg = my & messages %~ (++ [msg])
logAndIncr :: MyState -> Text -> MyState
logAndIncr my msg =
let incremented = incrCnt my
logged = logMsg incremented msg
in logged
An dieser Stelle ist es nicht so schlimm ist, aber sobald wir in den nächsten Schritt bekommen ich denke, Sie werden sehen, wo der Code-Duplizierung wirklich kommt in:
app :: MyState -> MyState
app initial =
let first_step = logAndIncr initial "First step"
second_step = logAndIncr first_step "Second step"
third_step = logAndIncr second_step "Third step"
fourth_step = logAndIncr third_step "Fourth step"
fifth_step = logAndIncr fourth_step "Fifth step"
in fifth_step
ein weiterer Vorteil dieses in einer Monad
Instanz Einwickeln ist, dass Sie die volle Leistung des Control.Monad
und Control.Applicative
damit verwenden können:
app = mapM_ logAndIncr [
"First step",
"Second step",
"Third step",
"Fourth step",
"Fifth step"
]
Dies ermöglicht viel mehr Flexibilität bei der Verarbeitung von Werten, die zur Laufzeit im Vergleich zu statischen Werten berechnet werden.
Der Unterschied zwischen der manuellen Statusübergabe und der Verwendung der State
Monade ist einfach, dass die State
Monade eine Abstraktion über den manuellen Prozess ist. Es passt auch zu einigen anderen allgemeineren Abstraktionen, wie Monad
, Applicative
, Functor
und einigen anderen. Wenn Sie auch den StateT
Transformer verwenden, können Sie diese Operationen mit anderen Monaden zusammenstellen, z. B. IO
. Können Sie all dies ohne State
und StateT
tun? Natürlich kannst du das, und es gibt niemanden, der dich davon abhält, aber der Punkt ist, dass State
dieses Muster abstrahiert und dir Zugriff auf eine riesige Toolbox mit allgemeineren Werkzeugen gibt. Auch eine kleine Änderung an den oben genannten Typen macht die gleichen Funktionen in mehreren Kontexten arbeiten:
incrCnt :: MonadState MyState m => m()
logMsg :: MonadState MyState m => Text -> m()
logAndIncr :: MonadState MyState m => Text -> m()
Diese jetzt mit App
oder mit StateT MyState IO
oder einem anderen Monade Stapel mit einer MonadState
Implementierung arbeiten. Es macht es deutlich mehr wiederverwendbar als einfache Argumentübergabe, was nur durch die Abstraktion möglich ist, die StateT
ist.
Wahrscheinlich müssen Sie viele der Funktionen, die bereits von der 'State'-Monade angeboten werden, neu implementieren. Stellen Sie sich Letzteres als Designmuster vor. Sie können "State" auch einfach mit anderen Monaden kombinieren. – Jubobs
Aber wenn ich State nicht verwende, muss ich es nicht mit anderen Monaden kombinieren. Ich würde einige Codebeispiele bevorzugen. – ais
Nun, in dem Beispiel, das Sie geben, ist die Verwendung von "State" wahrscheinlich zu viel. Hast du ein konkretes Beispiel aus der Praxis? – Jubobs