مكدس محول Haskell Monad وتوقيعات الكتابة
-
21-09-2019 - |
سؤال
أحاول إنشاء كومة من محولات Monad وأواجه مشكلة في الحصول على توقيعات النوع الصحيحة لوظائفي. (ما زلت جديدًا على هاسكل)
يجمع المكدس بين محولات Statet متعددة نظرًا لأن لديّ حالات متعددة أحتاج إلى تتبعها (يمكن أن يكون اثنان منها مضطربًا ، لكنني سأصل إلى ذلك في ثانية) وكتاب لتسجيل الدخول.
هذا ما لدي حتى الآن:
module Pass1 where
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map
import Types
data Msg = Error String
| Warning String
type Pass1 a = WriterT [Msg] (StateT Int (StateT [Line] (StateT [Address] Identity))) a
runPass1 addrs instrs msgs = runIdentity (runStateT (runStateT (runStateT (runWriterT msgs) 1) instrs) addrs)
--popLine :: (MonadState s m) => m (Maybe s)
--popLine :: (Monad m) => StateT [Line] m (Maybe Line)
popLine :: (MonadState s m) => m (Maybe Line)
popLine = do
ls <- get
case ls of
x:xs -> do
put xs
return $ Just x
[] -> return Nothing
incLineNum :: (Num s, MonadState s m) => m ()
incLineNum = do
ln <- get
put $ ln + 1
curLineNum :: (MonadState s m) => m s
curLineNum = do
ln <- get
return ln
evalr = do l <- popLine
--incLineNum
return l
أود popLine
للعبث مع [Line]
الدولة و xLineNum
وظائف للتأثير على Int
حالة. evalr
هو الحساب الذي سيتم نقله إلى runPass1
.
كلما قمت بتحميل الكود ، أتعامل مع الأخطاء التي تكون عمومًا من النوع التالي:
Pass1.hs:23:14:
No instance for (MonadState [t] m)
arising from a use of `get' at Pass1.hs:23:14-16
Possible fix: add an instance declaration for (MonadState [t] m)
In a stmt of a 'do' expression: ls <- get
In the expression:
do ls <- get
case ls of {
x : xs -> do ...
[] -> return Nothing }
In the definition of `popLine':
popLine = do ls <- get
case ls of {
x : xs -> ...
[] -> return Nothing }
Pass1.hs:22:0:
Couldn't match expected type `s' against inferred type `[Line]'
`s' is a rigid type variable bound by
the type signature for `popLine' at Pass1.hs:21:23
When using functional dependencies to combine
MonadState [Line] m,
arising from a use of `get' at Pass1.hs:23:14-16
MonadState s m,
arising from the type signature for `popLine'
at Pass1.hs:(22,0)-(28,31)
When generalising the type(s) for `popLine'
Pass1.hs:23:14:
Could not deduce (MonadState [Line] m)
from the context (MonadState s m)
arising from a use of `get' at Pass1.hs:23:14-16
Possible fix:
add (MonadState [Line] m) to the context of
the type signature for `popLine'
or add an instance declaration for (MonadState [Line] m)
In a stmt of a 'do' expression: ls <- get
In the expression:
do ls <- get
case ls of {
x : xs -> do ...
[] -> return Nothing }
In the definition of `popLine':
popLine = do ls <- get
case ls of {
x : xs -> ...
[] -> return Nothing }
لا يبدو أن أي من التوقيعات صحيحة ، لكن PopLine هي الوظيفة الأولى ، لذا فهي الوحيدة التي تسبب خطأً على الفور.
أحاول إضافة ما يقترحه في توقيع النوع (على سبيل المثال: popLine :: (MonadState [Line] m) => ...
ولكن بعد ذلك أخطاء مثل ذلك:
Pass1.hs:21:0:
Non type-variable argument in the constraint: MonadState [Line] m
(Use -XFlexibleContexts to permit this)
In the type signature for `popLine':
popLine :: (MonadState [Line] m) => m (Maybe Line)
يبدو أنني دائمًا أحصل على هذه الرسالة كلما حاولت القيام بشيء ليس متغيرًا نوعًا. يبدو أنه يحب (MonadState s m)
طيب وخطأ في شيء آخر ، ولكن عندما أحاول ذلك مع [a]
بدلاً من s
أخطاء مماثلة لما سبق. (في البداية كان [الخط] و int في حالة واحدة ، لكنني كنت أتلقى هذا الخطأ لذلك اعتقدت أنني سأحاول وضعها في حالات منفصلة).
GHC 6.10.4 ، Kubuntu
لذا ، هل يمكن لأي شخص أن يخبرني بما يجري وإعطاء شرح / أظهر لي توقيعات النوع الصحيحة ، أو هل يعرف أي شخص مرجعًا جيدًا على هذه الأشياء (الشيء الوحيد الذي ساعد حتى الآن هو "Monad Transformers خطوة بخطوة" ، لكن هذا يستخدم وظيفة حالة aux واحدة فقط و statet واحدة)؟
شكرا كثيرا مسبقا.
يحرر
إليك رمز التجميع الذي يتضمن اقتراحات JFT و Edward:
{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- needed for: deriving (Functor,Monad)
{-# LANGUAGE MultiParamTypeClasses #-} -- needed for: MonadState instance
{-# LANGUAGE FlexibleContexts #-} -- needed for: (MonadState PassState m) => ...
module Pass1 where
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import Types
type Lines = [Line]
type Addresses = [Address]
type LineNum = Int
type Messages = [Msg]
data Msg = Error String
| Warning String
data PassState = PassState { passLineNum :: LineNum
, passLines :: Lines
, passAddresses :: Addresses
}
newtype Pass1 a = Pass1 { unPass1 :: WriterT Messages (State PassState) a
}
deriving (Functor,Monad)
instance MonadState PassState Pass1 where
get = Pass1 . lift $ get
put s = Pass1 . lift $ put s
runPass1 :: PassState -> Pass1 a -> ((a, Messages), PassState)
runPass1 state = flip runState state .
runWriterT .
unPass1
curLineNum :: (MonadState PassState m) => m LineNum
curLineNum = do
state <- get
return $ passLineNum state
nextLine :: (MonadState PassState m) => m (Maybe Line)
nextLine = do
state <- get
let c = passLineNum state
let l = passLines state
case l of
x:xs -> do
put state { passLines = xs, passLineNum = (c+1) }
return $ Just x
_ -> return Nothing
evalr :: Pass1 (Maybe Line,LineNum)
evalr = do
l <- nextLine
c <- curLineNum
--tell $ Warning "hello"
return (l,c)
أنا مجتمعة incLineNum
و popLine
داخل nextLine
ما زلت بحاجة إلى جعل الكاتب موناد جزءًا من العمل ، لكنني أعتقد أنني أعرف من أين أذهب من هنا. شكرا يا رفاق.
المحلول
كان هناك العديد من المشكلات مع مقتطف الكود الخاص بك. لقد أصلحت المقتطف الخاص بك إضافة شرح لما تم كسره وأضفت بعض النصائح الأناقة إذا كنت تهتم.
module Pass1_JFT where
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map
{ - استبدال أنواع الاستيراد الخاصة بك بتعريفات بسيطة -}
--import Types
type Line = String
type Address = String
type LineNumber = Int
{- ليس جزءًا من سؤالك ولكن بلدي 2 سنت هنا ... قل أنك تريد تغيير المجموعة لحالتك إذا كنت لا تستخدم اسم الاسم المستعار الذي يجب عليك البحث عنه إلى الأمام الذي استخدمته. بدلاً من ذلك ، فقط قم بتغيير هذه التعريفات إذا لزم الأمر -}
type Lines = [Line]
type Addresses = [Address]
type Messages = [Msg]
data Msg = Error String
| Warning String
{- ما هذا int في statet int؟ اسمه أسهل للقراءة والعقل والتغيير. FTW التصريحي لنستخدم عدد الكتان بدلاً من ذلك -}
--type Pass1 a = WriterT [Msg] (StateT Int (StateT [Line] (StateT [Address] Identity))) a
{- دعنا نستخدم نوعًا "حقيقيًا" بحيث يمكن اشتقاق الحالات. نظرًا لأن Pass1 ليس نقل موناد ، أي لم يتم تعريفه على أنه Pass1 Ma ، لا توجد فائدة من استخدام Statet لأعمق هوية STATET IE [العنوان] لذلك دعونا فقط نستخدم حالة [العنوان] -}
newtype Pass1 a = Pass1 {
unPass1 :: WriterT Messages (StateT LineNumber (StateT Lines (State Addresses))) a
}
deriving (Functor,Monad)
--runIdentity (runStateT (runStateT (runStateT (runWriterT msgs) 1) instrs) addrs)
{- دعنا نقشر هذا المكدس من أقصى الخارجي (أقصى درجات في الإعلان) حتى الأعمق كان الهوية في إعلانك الأصلي. لاحظ أن RunWritert لا يأخذ حالة انطلاق ... المعلمة الأولى لـ RunStatet (و RunState) ليست الحالة الأولية ولكن الموناد ... لذلك دعونا نقلب! -}
runPass1' :: Addresses -> Lines -> Messages -> Pass1 a -> ((((a, Messages), LineNumber), Lines), Addresses)
runPass1' addrs instrs msgs = flip runState addrs .
flip runStateT instrs .
flip runStateT 1 .
runWriterT . -- then get process the WriterT (the second outermost)
unPass1 -- let's peel the outside Pass1
{- الآن هذه الوظيفة الأخيرة لا تفعل ما تريد لأنك تريد تقديم سجل أولي لإلحاقه بـ Writert. نظرًا لأنه محول موناد ، سنقوم ببعض الخدعة هنا -}
-- I keep the runStateT convention for the order of the arguments: Monad then state
runWriterT' :: (Monad m,Monoid w) => WriterT w m a -> w -> m (a,w)
runWriterT' writer log = do
(result,log') <- runWriterT writer
-- let's use the monoid generic append in case you change container...
return (result,log `mappend` log')
runPass1 :: Addresses -> Lines -> Messages -> Pass1 a -> ((((a, Messages), LineNumber), Lines), Addresses)
runPass1 addrs instrs msgs = flip runState addrs .
flip runStateT instrs .
flip runStateT 1 .
flip runWriterT' msgs . -- then get process the WriterT (the second outermost)
unPass1 -- let's peel the outside Pass1
{- هل تنوي استدعاء PopLine مباشرة من مكدس Pass1؟ إذا كان الأمر كذلك ، فأنت بحاجة إلى "تعليم" Pass1 لتكون "خطوط Monadstate" للقيام بذلك ، دعنا نشتق Pass1 (لهذا السبب أعلننا ذلك مع NewType!) -}
instance MonadState Lines Pass1 where
-- we need to dig inside the stack and "lift" the proper get
get = Pass1 . lift . lift $ get
put s = Pass1 . lift . lift $ put s
{ - من الأفضل الحفاظ على شيء عام ولكن يمكن أن نكون مكتوبة الآن: popline :: pass1 (ربما السطر) -}
popLine :: (MonadState Lines m) => m (Maybe Line)
popLine = do
ls <- get
case ls of
x:xs -> do
put xs
return $ Just x
[] -> return Nothing
{- حسنًا الآن أحصل على int => عدد الكتان .... يمكن أن نجعل Pass1 ومثيل من الكتان monadstate ولكن لا ينبغي أن يفسد عدد الكتان مع ذلك بدلاً من ذلك ، سأقوم بترميز المنحدر مباشرةً وسأوفر مثيلًا monadreader للقنصلية إذا لزم الأمر
check ":t incLineNum and :t curLineNum"
-}
incLineNum = Pass1 . lift $ modify (+1)
curLineNum = Pass1 $ lift get
evalr = do l <- popLine
incLineNum
return l
هناك استجابة طويلة المذهلة ولكن Monad و Monad Stack كما تراه يمثل تحديًا في البداية. لقد أصلحت الرمز ولكني أشجعك على لعب وتفقد أنواع الوظائف المختلفة لفهم ما يجري والمقارنة مع الأصل. إن الاستدلال على نوع Haskell يعني أن التعليقات التوضيحية عادة ما تكون غير ضرورية (ما لم يتم إزالة الغموض). بشكل عام ، يكون النوع الذي نقدمه للوظيفة أقل عامًا وهو الاستنتاج ، لذا من الأفضل عدم كتابة التعليقات التوضيحية. نوع التعليق التوضيحي هو بالتأكيد تقنية تصحيح جيد على الرغم من ؛)
هتافات
PS real World Haskell Chapter on Monad Transformer ممتاز:http://book.realworldhaskell.org/read/monad-transformers.html
نصائح أخرى
بشكل عام ، ستجد أن الكود ينتهي أكثر وضوحًا باستخدام Statet واحد بهيكل مركب أكبر لجميع أجزاء الحالة التي تحتاجها. أحد الأسباب الجيدة هو أنه عندما تتوصل إلى قطعة من الحالة ، نسيت أنه يمكنك دائمًا زراعة الهيكل بموجب حقل واحد ، ويمكنك استخدام السكر السجل لكتابة تحديثات حقل مفردة أو تتحول إلى شيء مثل FCLABELS أو ACCOSSER حزم لمعالجة الدولة.
data PassState = PassState { passLine :: Int, passLines :: [Line] }
popLine :: MonadState PassState m => m (Maybe Line).
popLine = do
state <- get
case passLines state of
x:xs -> do
put state { passLines = xs }
return (Just x)
_ -> return Nothing