2011-09-22 42 views
13

我寫下列分配代碼最近幾次,想知道是否有把它寫一個較短的方式。這樣的功能是否已經存在? (或者說,什麼是這個功能的更好的名字?)

foo :: IO String 
foo = do 
    x <- getLine 
    putStrLn x >> return x 

爲了讓事情變得乾淨了一點,我寫了這個功能(雖然我不知道這是一個合適的名稱):

constM :: (Monad m) => (a -> m b) -> a -> m a 
constM f a = f a >> return a 

然後我就可以使FOO這樣的:

​​

請問這樣的功能/成語已經存在?如果不是,我的constM有什麼更好的名字?

+4

似乎可以使用普通的仿函數定義此函數:'constF ::函子F =>(A - >爲F b) - >一 - > F A; constF f a = a <$ f a' – fuz

+0

@FUZxxl,這也可以,謝謝...這是常量名稱的正確用法呢? –

+0

你是什麼意思? – fuz

回答

18

好了,讓我們考慮這樣的事情可能被簡化的方式。非一元版本將我想看看像const' f a = const a (f a),這顯然等同於flip const有更具體的類型。隨着單子版本,然而,f a結果可以做任意的事情仿函數的非參數結構(即,內容通常被稱爲「副作用」),包括取決於a價值的東西。這告訴我們的是,儘管假裝就像我們放棄f a的結果一樣,但我們實際上並沒有採取任何行動。返回a不變的仿函數的參數部分遠不如必要的,我們可以用別的東西代替return,仍然有一個概念上類似的功能。

因此,我們可以得出結論的第一件事是,它可以被看作是像下面這樣的功能的一個特殊情況:

doBoth :: (Monad m) => (a -> m b) -> (a -> m c) -> a -> m c 
doBoth f g a = f a >> g a 

從這裏,有兩種不同的方式來尋找的底層結構某種。


一種觀點是承認分割多個功能中的一個參數的圖案,然後重新組合的結果。這是由Applicative/Monad實例爲功能體現的概念,像這樣:

doBoth :: (Monad m) => (a -> m b) -> (a -> m c) -> a -> m c 
doBoth f g = (>>) <$> f <*> g 

...或者,如果你喜歡:

doBoth :: (Monad m) => (a -> m b) -> (a -> m c) -> a -> m c 
doBoth = liftA2 (>>) 

當然,liftA2相當於liftM2所以,如果取消對單子的操作到另一個單子都有事做單子變壓器你可能想知道;一般的關係,有尷尬,但在這種情況下,它的工作原理很容易,讓這樣的事情:

doBoth :: (Monad m) => ReaderT a m b -> ReaderT a m c -> ReaderT a m c 
doBoth = (>>) 

...模適當的包裝和這樣當然。要專門回到您的原始版本,return的原始使用現在需要ReaderT a m a類型,這不應該太難以識別爲讀取器單元的ask函數。


另一透視是要認識到與類型,如(Monad m) => a -> m b功能可直接組成,很像純函數。函數(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)給出了等價於函數組合(.) :: (b -> c) -> (a -> b) -> (a -> c)的直接函數,或者您也可以使用Control.Categorynewtype包裝函數Kleisli以通用方式處理同一事物。但是,我們仍然需要分解參數,所以我們真正需要的是一個「分支」組合,其單獨不具有Category;通過使用Control.Arrow以及我們得到(&&&),讓我們重寫功能如下:

doBoth :: (Monad m) => Kleisli m a b -> Kleisli m a c -> Kleisli m a (b, c) 
doBoth f g = f &&& g 

由於我們不關心第一Kleisli箭頭的結果,只是它的副作用,我們可以放棄的一半元組中的顯而易見的方式:

doBoth :: (Monad m) => Kleisli m a b -> Kleisli m a c -> Kleisli m a c 
doBoth f g = f &&& g >>> arr snd 

這讓我們回到通用形式。專業你原來,return現在變得簡單id

constKleisli :: (Monad m) => Kleisli m a b -> Kleisli m a a 
constKleisli f = f &&& id >>> arr snd 

由於常規功能也Arrow S,上述定義在那裏工作,以及如果你推廣的類型簽名。然而,它可以是有啓發擴大導致純函數的定義和簡化如下:

  • \f x -> (f &&& id >>> arr snd) x
  • \f x -> (snd . (\y -> (f y, id y))) x
  • \f x -> (\y -> snd (f y, y)) x
  • \f x -> (\y -> y) x
  • \f x -> x

所以我們回到flip const,正如所料!


總之,你的功能是在任(>>)flip const一些變化,但在依賴於不同的方式 - 同時使用ReaderT環境和基礎單子的(>>)前者,後者使用特定的Arrow的隱含副作用和期望Arrow副作用以特定順序發生。由於這些細節,不可能有任何通用化或簡化可用。從某種意義上說,你使用的定義非常簡單,這就是爲什麼我給出的替代定義更長和/或涉及一定數量的打包和解包。

這樣的功能將是一個自然的除了某種形式的「單子工具庫」。雖然Control.Monad提供了一些結合在一起的線程,但它並非詳盡無遺,我也不能在標準庫中找到或回想這個函數的任何變化。但是,如果在一個或多個實用程序庫中發現它,我一點都不會感到驚訝。

已經大多與存在的問題,分配,我真的不能提供關於命名超越你可以從上述有關相關概念的討論採取什麼太多指導。

作爲最後的一邊,還請注意,你的函數具有基於一個一元表達式的結果沒有控制流的選擇,因爲在執行表達式不管是主要目標。擁有獨立的參數內容的計算結構(即a型的Monad m => m a的東西),通常是你實際上並不需要一個完整的Monad,並可能與Applicative更普遍的概念獲得通過的標誌。

+0

不是他的問題的答案。無論如何,無論如何都要進行很好的分析:-) – luqui

+0

@luqui:那麼,它是爲了尋找底層結構以找到某些事物的概括,因爲所描述的* exact *函數不存在......是的,那種迷路在某處丟失了。應該稍微修改一下,以便更清楚一些。 –

1

我真的沒有線索這正是媒體鏈接存在,但你看到這個有很多解析器發電機只能用不同的名稱(例如,以獲得事情括號內) - 那裏是normaly某種運營商(例如fparsec中的>>..>>)。要真的給一個名字,我可能會把它叫做忽略

+1

我已經看到人們在'ignore :: Monad m => m a - > m()','ignore m = m >> return()'中使用'ignore'這個名字。 –

+0

@Judah爲了抑制關於丟棄陳述結果的警告? – fuz

+0

我不確定忽略是否正確,因爲我需要返回給定的值。 –

3

嗯,我不認爲constM是適當這裏。

map :: (a -> b) -> [a] -> [b] 
mapM :: (Monad m) => (a -> m b) -> [a] -> m b 

const :: b -> a -> b 

因此,或許:

constM :: (Monad m) => b -> m a -> m b 
constM b m = m >> return b 

功能你M -ing似乎是:

f :: (a -> b) -> a -> a 

它沒有選擇,只能忽略它的第一個參數。所以這個函數純粹沒什麼好說的。

我看到它作爲一種方式,嗯,觀察一個值與副作用observe,effect,sideEffect可能是體面的名字。 observe是我的最愛,但也許只是因爲它很吸引人,並不是因爲它很清楚。

+0

我認爲觀察比我的名字更清晰,我猜它不需要'M',因爲純粹的相當於這個沒有任何意義。 –

+0

我很懷疑'觀察'。我們當然不會觀察函數產生的值,並且由於函數只需要'Applicative',所以我們不一定要觀察任何副作用。 –