我在这里做了一个新的改进版本。
原始问题
这是一个非常基础的lolcats翻译器(仅适用于“我可以吃芝士汉堡”这个短语)在Haskell。这是我第一次尝试Haskell(或任何功能编程语言)。我确定我的代码绝对不好(文档很糟糕),但是可以用。
Teh Codez
import Data.Strings
main = do
putStrLn "Enter your text to translate"
inputText <- getLine
let capsText = strToUpper inputText
let a = strReplace "HAVE" "HAS" capsText
let b = strReplace "CAN I" "I CAN" a
let c = strReplace "CHEESEBURGER" "CHEEZBURGER" b
let d = strReplace " A " " " c
putStrLn (d)
getLine
#1 楼
在Haskell中,最好的做法是将功能代码与IO分开。在这种情况下,您可以(因此应该)定义一个lolcat :: String -> String
函数。请确保在所有函数上都添加类型声明-您没有为main
写一个类型声明。定义变量
a
,b
,c
和d
是过大的。我会将其写成函数的组合。lolcat :: String -> String
lolcat = strReplace " A " " " .
strReplace "CHEESEBURGER" "CHEEZBURGER" .
strReplace "CAN I" "I CAN" .
strReplace "HAVE" "HAS" .
strToUpper
评论
\ $ \ begingroup \ $
我认为该代码与问题中原始代码之间的区别在突出功能性和命令式编程风格之间的区别方面做得很好。一个可能使人困惑的大问题是无点样式:本质上,我们在定义一个函数lolcat时没有提到该函数的参数-乍一看可能很难理解,但从本质上讲,这与说相同lolcat(x)= strReplace“ A”“”(strReplace“ CHEESEBURGER”“ CHEEZEBURGER”(...(strToUpper(x))...))
\ $ \ endgroup \ $
–vijrox
16年6月9日在15:20
\ $ \ begingroup \ $
当初来自命令式背景的第一次学习函数式编程时,无点样式可能会造成混淆,但是一旦习惯了,它确实是不错/可取的
\ $ \ endgroup \ $
–vijrox
16年6月9日在15:22
\ $ \ begingroup \ $
即使是初学者,我也认为这是一个很好的无点风格演示。
\ $ \ endgroup \ $
–user253751
16年6月10日在6:25
#2 楼
我同意200_success,只是输入完整的程序,因为您说您是一个初学者。import Data.Strings
encode :: String -> String
encode = strReplace " A " " "
. strReplace "CHEESEBURGER" "CHEEZBURGER"
. strReplace "CAN I" "I CAN"
. strReplace "HAVE" "HAS"
. strToUpper
main :: IO ()
main = do
putStrLn "Enter your text to translate"
input <- getLine
putStrLn $ encode input
主要功能可能更常见地写为:
main :: IO ()
main = do
putStrLn "Enter your text to translate"
putStrLn =<< encode <$> getLine
编辑:
@MichaelKlein让我解释
(<$>)
和(=<<)
,所以我将尽可能简短地进行解释。<$>
是fmap
的内联版本,这是Functor
类型类中的唯一函数。类型类有点像像Java这样的命令性语言中的接口。class Functor f where
fmap :: (a -> b) -> f a -> f b
fmap
用于在某些数据结构中运行函数(a -> b)
f a
fmap (+1) [1] == [2]
fmap (+1) (Just 3) == Just 4
(+1) <$> (Just 3) == Just 4
(<$>) :: f a -> ( a -> b ) -> f b
在Haskell,我们将世界视为数据结构
IO
。因此,就像可以在列表或Maybe中运行函数一样(例如Just 3
),也可以在IO
内部运行函数。encode :: String -> String
getLine :: IO String
encode <$> getLine :: IO String
(=<<)
是Monad
类型类的一部分,很难简要解释,因为您需要首先了解其他一些类型类。(=<<) :: (a -> m b) -> m a -> m b
(=<<)
发音为“ bind”,并且是更常见的(>>=)
的翻转版本。重要的一点是Haskell具有用于处理monad的特殊语法:do
表示法。此:
main = do
input <- getLine
putStrLn input
是这种糖吗?
main = getLine >>= \input -> putStrLn input
与此相同:
main = getLine >>= putStrLn
所有这些也都相等:
main = do
putStrLn "burger"
input <- getLine
putStrLn $ encode input
main = putStrLn "burger"
>> getLine >>= \input -> putStrLn $ encode input
main = putStrLn "burger"
>> encode <$> getLine >>= putStrLn
main = do
putStrLn "burger"
putStrLn =<< encode <$> getLine
评论
\ $ \ begingroup \ $
也可以添加类型签名<$> :: f a->(a-> b)-> f b和= << ::(a-> m b)-> m a-> m b
\ $ \ endgroup \ $
– Caleth
16年6月9日在12:05
\ $ \ begingroup \ $
@BlackCap感谢您添加一些解释。在这种情况下,我认为通常只需解释一下它们的含义就可以了,例如在您在这里使用它们的情况下,并提到像显示它们所做的一样,但是对于不同的类型而言它们稍有不同。超越+1。
\ $ \ endgroup \ $
– Michael Klein
16年6月9日在14:53
#3 楼
删除重复您要重复很多
strReplace
: vvvvvvvvv
let a = strReplace "HAVE" "HAS" capsText
let b = strReplace "CAN I" "I CAN" a
let c = strReplace "CHEESEBURGER" "CHEEZBURGER" b
let d = strReplace " A " " " c
^^^^^^^^^^
什么变化是用什么代替:
[("HAVE", "HAS"), ("CAN I", "I CAN"), ...]
因此,您的程序应陈述一次替换的想法,然后包含此“替换说明”列表。
为了更清楚地说明,您应该使用的函数具有
String -> [(String, String)] -> String
类型和示例用法:> replaceByTuples "foobar" [("foo", "fuu"),("bar", "baz")]
"fuubaz"
使用此功能和特定的替换清单将很容易。
此功能在扰流板中。我建议您尝试自己实现它。
import Data.List.Utils;
replaceByTuples s t = (foldl (.) id $ map (\(start, end) -> replace start end) t) s
有关此功能的更多信息和更好的实现,请参见:https://stackoverflow.com/questions / 7862221 /我如何在haskell中替换字符串
评论
\ $ \ begingroup \ $
我认为您应该交换该replaceByTuples函数的参数,以便使用curried更方便。您也可以通过foldr(uncurry replace):-)轻松实现它
\ $ \ endgroup \ $
–贝尔吉
16年6月9日在13:09
\ $ \ begingroup \ $
@Bergi Wow,没想到这么简单的实现。是的。 )同意交换意见。
\ $ \ endgroup \ $
– Caridorc
16年6月9日在13:13
#4 楼
我同意其他两个答案,但我会在main中使用interact
。这将使您的程序无限循环,直到没有更多可能不是您想要的输入为止,但它将解决使控制台保持打开状态的问题。您必须定义一个eachLine
函数,以便该程序一次处理一行代码,如该stackoverflow答案中所述。正如200_success所述,为所有顶部编写类型签名是一个好习惯级别的功能和
main
传统上具有类型签名:main :: IO ()
。由于main末尾有getLine
,因此它将具有类型签名:main :: IO String
,这会给程序带来意想不到的副作用,即在终止时打印额外的字符串。显式地编写类型签名使编译器可以解决这些问题。这将是完整的程序:
import Data.Strings
main :: IO ()
main = do
putStrLn "Enter your text to translate"
interact (eachLine lolcat)
lolcat :: String -> String
lolcat = strReplace " A " " " .
strReplace "CHEESEBURGER" "CHEEZBURGER" .
strReplace "CAN I" "I CAN" .
strReplace "HAVE" "HAS" .
strToUpper
eachLine :: (String -> String) -> String -> String
eachLine f = unlines . map f . lines
评论
@JamesFaix大多数情况下是正确的,但是Haskell的变量不像其他语言中那样可变,因此主要是惯用风格的问题,而不是安全性。@Bergi getLine在打印输出后使Windows控制台保持打开状态。有关如何执行此操作的其他想法?
@MichaelBrandonMorris:我知道了。通常,您只是从控制台运行程序,因此无论如何它将保持打开状态。
@Bergi好的,但是假设有人通过可执行文件(双击,开始菜单等)运行它。我有什么选择?
在这种特殊情况下,您可以仅在main函数的末尾递归调用main,以便您的程序反复要求输入。实现此目标的另一种方法是从Control.Monad中永久实现