以下内容均摘自:https://blog.csdn.net/qq_34291505
一、Scala基本数据类型
Scala是静态语言,在编译期间会检查每个对象的类型。对于类型不匹配的非法操作,在编译时就能被发现。对于动态语言而言,这种非法操作需要等到运行时才能被发现,此时可能造成严重错误。所以,静态语言相比诸如Python这样的动态语言在某些方面是有优势的。对于Chisel而言,我们就需要这种优势。因为Chisel需要编译成Verilog,我们不能产生非法的Verilog语句并且等到模块运行时才去发现它。
Scala标准库定义了一些基本类型,如下表所示。除了“String”类型是属于java.lang包之外,其余都在Scala的包里。
Byte | 8-bit有符号整数,补码表示,范围是 -2^{7} 到 2^{7}-1 |
---|---|
Short | 16-bit有符号整数,补码表示,范围是 -2^{15} 到 2^{15}-1 |
Int | 32-bit有符号整数,补码表示,范围是 -2^{31} 到 2^{31}-1 |
Long | 64-bit有符号整数,补码表示,范围是 -2^{63} 到 2^{63}-1 |
Char | 16-bit字符,Unicode编码,范围是 0 到 2^{16}-1 |
String | 字符串 |
Float | 32-bit单精度浮点数,符合IEEE 754标准 |
Double | 64-bit双精度浮点数,符合IEEE 754标准 |
Boolean | 布尔值,其值为true或者false |
事实上,在定义变量时,应该指明变量的类型,只不过Scala的编译器具有自动推断类型的功能,可以根据赋给变量的对象的类型,来自动推断出变量的类型。如果要显式声明变量的类型,或者无法推断时,则只需在变量名后面加上一个冒号“ : ”,然后在等号与冒号之间写出类型名即可。
二、函数相关
1、函数字面量
函数式编程有两个主要思想,其中之一就是:函数是一等(first-class)的值。换句话说,一个函数的地位与一个Int值、一个String值等等,是一样的。既然一个Int值可以成为函数的参数、函数的返回值、定义在函数体里、存储在变量里,那么,作为地位相同的函数,也可以这样。你可以把一个函数当参数传递给另一个函数,也可以让一个函数返回一个函数,亦可以把函数赋给一个变量,又或者像定义一个值那样在函数里定义别的函数(即前述的嵌套函数)。就像写一个整数字面量“1”那样,Scala也可以定义函数的字面量。函数字面量是一种匿名函数的形式,它可以存储在变量里、成为函数参数或者当作函数返回值,其定义形式为:
(参数1: 参数1类型, 参数2: 参数2类型, …) => { 函数体 }
通常,函数字面量会赋给一个变量,这样就能通过“变量名(参数)”的形式来使用函数字面量。在参数类型可以被推断的情况下,可以省略类型,并且参数只有一个时,圆括号也可以省略。
函数字面量的形式可以更精简,即只保留函数体,并用下划线“_”作为占位符来代替参数。在参数类型不明确时,需要在下划线后面显式声明其类型。多个占位符代表多个参数,即第一个占位符是第一个参数,第二个占位符是第二个参数……因此不能重复使用某个参数。例如:
scala> val f = (_: Int) + (_: Int)
f: (Int, Int) => Int = $$Lambda$1072/1534177037@fb42c1c
scala> f(1, 2)
res0: Int = 3
无论是用“def”定义的函数,还是函数字面量,它们的函数体都可以把一个函数字面量作为一个返回结果,这样就成为了返回函数的函数;它们的参数变量的类型也可以是一个函数,这样调用时给的入参就可以是一个函数字面量。类型为函数的变量,其冒号后面的类型写法是“(参数1类型, 参数2类型,...) => 返回结果的类型”。
例如:
scala> val add = (x: Int) => {
(y: Int) => x + y }
add: Int => (Int => Int) = $$Lambda$1192/1767705308@55456711
scala> add(1)(10)
res0: Int = 11
scala> def aFunc(f: Int => Int) = f(1) + 1
aFunc: (f: Int => Int)Int
scala> aFunc(x => x + 1)
res1: Int = 3
在第一个例子中,变量add被赋予了一个返回函数的函数字面量。在调用时,第一个括号里的“1”是传递给参数x,第二个括号里的“10”是传递给参数y。如果没有第二个括号,得到的就不是11,而是“(y: Int) => 1 + y”这个函数字面量。
在第二个例子中,函数aFunc的参数f是一个函数,并且该函数要求是一个入参为Int类型、返回结果也是Int类型的函数。在调用时,给出了函数字面量“x => x + 1”。这里没有显式声明x的类型,因为可以通过f的类型来推断出x必须是一个Int类型。在执行时,首先求值f(1),结合参数“1”和函数字面量,可以算出结果是2。那么,“f(1) + 1”就等于3了。
2、柯里化
对大多数编程语言来说,函数只能有一个参数列表,但是列表里可以有若干个用逗号间隔的参数。Scala有一个独特的语法——柯里化,也就是一个函数可以有任意个参数列表。柯里化往往与另一个语法结合使用:当参数列表里只有一个参数时,在调用该函数时允许单个参数不用圆括号包起来,改用花括号也是可行的。这样,在自定义类库时,自定义方法就好像“if(...) {...}”、“while(...) {...}”、“for(...) {...}”
等内建控制结构一样,让人看上去以为是内建控制,丝毫看不出是自定义语法。例如:
scala> def add(x: Int, y: Int, z: Int) = x + y + z
add: (x: Int, y: Int, z: Int)Int
scala> add(1, 2, 3)
res0: Int = 6
scala> def addCurry(x: Int)(y: Int)(z: Int) = x + y + z
addCurry: (x: Int)(y: Int)(z: Int)Int
scala> addCurry(1)(2) {
3}
res1: Int = 6
3、传名参数
第四点介绍了函数字面量如何作为函数的参数进行传递,以及如何表示类型为函数时参数的类型。如果某个函数的入参类型是一个无参函数,那么通常的类型表示法是“() => 函数的返回类型”。在调用这个函数时,给出的参数就必须写成形如“() => 函数体”这样的函数字面量。
为了让代码看起来更舒服,也为了让自定义控制结构更像内建结构,Scala又提供了一个特殊语法——传名参数。也就是类型是一个无参函数的函数入参,传名参数的类型表示法是“=> 函数的返回类型”,即相对常规表示法去掉了前面的空括号。在调用该函数时,传递进去的函数字面量则可以只写“函数体”,去掉了“() =>”。例如:
var assertionEnabled = false
// predicate是类型为无参函数的函数入参
def myAssert(predicate: () => Boolean) =
if(assertionEnabled && !predicate())
throw new AssertionError
// 常规版本的调用
myAssert(() => 5 > 3)
// 传名参数的用法,注意因为去掉了空括号,所以调用predicate时不能有括号
def byNameAssert(predicate: => Boolean) =
if(assertionEnabled && !predicate)
throw new AssertionError
// 传名参数版本的调用,看上去更自然
byNameAssert(5 > 3)
可以看到,传名参数使得代码更加简洁、自然,而常规写法则很别扭。事实上,predicate的类型可以改成Boolean,而不必是一个返回布尔值的函数,这样调用函数时与传名参数是一致的。例如:
// 使用布尔型参数的版本
def boolAssert(predicate: Boolean) =
if(assertionEnabled && !predicate)
throw new AssertionError
// 布尔型参数版本的调用
boolAssert(5 > 3)
尽管byNameAssert和boolAssert在调用形式上是一样的,但是两者的运行机制却不完全一样。如果给函数的实参是一个表达式,比如“5 > 3”这样的表达式,那么boolAssert在运行之前会先对表达式求值,然后把求得的值传递给函数去运行。而myAssert和byNameAssert则不会一开始就对表达式求值,它们是直接运行函数,直到函数调用入参时才会对表达式求值,也就是例子中的代码运行到“!predicate”时才会求“5 > 3”的值。
4、偏函数
在Scala里,万物皆对象。函数是一等值,与整数、浮点数、字符串等等相同,所以函数也是一种对象。既然函数也是一个对象,那么必然属于某一种类型。为了标记函数的类型,Scala提供了一系列特质:Function0、Function1、Function2……Function22来表示参数为0、1、2……22个的函数。与元组很像,因此函数的参数最多只能有22个。当然也可以自定义含有更多参数的FunctionX,但是Scala标准库没有提供,也没有必要。
除此之外,还有一个特殊的函数特质:偏函数PartialFunction。偏函数的作用在于划分一个输入参数的可行域,在可行域内对入参执行一种操作,在可行域之外对入参执行其他操作。偏函数有两个抽象方法需要实现:apply和isDefinedAt。其中,isDefinedAt用于判断入参是否在可行域内,是的话就返回true,否则返回false;apply是偏函数的函数体,用于对入参执行操作。使用偏函数之前,应该先用isDefinedAt判断入参是否合法,否则可能会出现异常。
定义偏函数的一种简便方法就是使用case语句组。广义上讲,case语句就是一个偏函数,所以才可以用于模式匹配。一个case语句就是函数的一个入口,多个case语句就有多个入口,每个case语句又可以有自己的参数列表和函数体。例如:
val isInt1: PartialFunction[Any, String] = {
case x: Int => x + " is a Int."
}
// 相当于
val isInt2 = new PartialFunction[Any, String] {
def apply(x: Any) = x.asInstanceOf[Int] + " is a Int."
def isDefinedAt(x: Any) = x.isInstanceOf[Int]
}
注意apply方法可以隐式调用。x.isInstanceOf[T]判断x是不是T类型(及其超类)的对象,是的话就返回true。x.asInstanceOf[T]则把x转换成T类型的对象,如果不能转换则会报错。
偏函数PartialFunction[Any, Any]是Function1[Any, Any]的子特质,因为case语句只有一个参数。[Any, Any]中的第一个Any是输入参数的类型,第二个Any是返回结果的类型。如果确实需要输入多个参数,则可以用元组、列表或数组等把多个参数变成一个集合。
在用case语句定义偏函数时,前述的各种模式类型、模式守卫都可以使用。最后的通配模式可有可无,但是没有时,要保证运行不会出错。
上述代码运行如下:
scala> isInt1(1)
res0: String = 1 is a Int.
scala> isInt2(1)
res1: String = 1 is a Int.
scala> isInt1.isDefinedAt('1')
res2: Boolean = false
scala> isInt2.isDefinedAt('1')
res3: Boolean = false
scala> isInt1('1')
scala.MatchError: 1 (of class java.lang.Character)
at scala.PartialFunction$ $anon$1.apply(PartialFunction.scala:255)
at scala.PartialFunction$ $anon$1.apply(PartialFunction.scala:253)
at $anonfun$1.applyOrElse(<console>:12)
at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:34)
... 28 elided
scala> isInt2('1')
java.lang.ClassCastException: java.lang.Character cannot be cast to java.lang.Integer
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
at $anon$1.apply(<console>:13)
at $anon$1.apply(<console>:12)
... 28 elided
三、类和对象相关
1、单例对象与伴生对象
在Scala里,除了用new可以构造一个对象,也可以用“object”开头定义一个对象。它类似于类的定义,只不过不能像类那样有参数,也没有构造方法。因此,不能用new来实例化一个object的定义,因为它已经是一个对象了。这种对象和用new实例化出来的对象没有什么区别,只不过new实例的对象是以类为蓝本构建的,并且数量没限制,而object定义的对象只能有这一个,故而得名“单例对象”。
如果某个单例对象和某个类同名,那么单例对象称为这个类的“伴生对象”,同样,类称为这个单例对象的“伴生类”。伴生类和伴生对象必须在同一个文件里,而且两者可以互访对方所有成员。在C++、Java等oop语言里,类内部可以定义静态变量。这些静态变量不属于任何一个用new实例化的对象,而是它们的公有部分。Scala追求纯粹的面向对象属性,即所有的事物都是类或对象,但是静态变量这种不属于类也不属于对象的事物显然违背了Scala的理念。所以,Scala的做法是把类内所有的静态变量从类里移除,转而集中定义在伴生对象里,让静态变量属于伴生对象这个独一无二的对象。
既然单例对象和new实例的对象一样,那么类内可以定义的代码,单例对象同样可以拥有。例如,单例对象里面可以定义字段和方法。Scala允许在类里定义别的类和单例对象,所以单例对象也可以包含别的类和单例对象的定义。因此,单例对象除了用作伴生对象,通常也可以用于打包某方面功能的函数系列成为一个工具集,或者包含主函数成为程序的入口。
“object”后面定义的单例对象名可以认为是这个单例对象的名称标签,因此可以通过句点符号访问单例对象的成员——“单例对象名.成员”,也可以赋给一个变量——“val 变量 = 单例对象名”,就像用new实例的对象那样。例如:
scala> class A {
val a = 10 }
defined class A
scala> val x = new A
x: A = A@7e5831c4
scala> x.a
res0: Int = 10
scala> (new A).a
res1: Int = 10
scala> object B {
val b = "a singleton object" }
defined object B
scala> B.b
res2: String = a singleton object
scala> val y = B
y: B.type = B$@4489b853
scala> y.b
res3: String = a singleton object
前面说过,定义一个类,就是定义了一种类型。从抽象层面讲,定义单例对象却并没有定义一种类型。实际上每个单例对象有自己独特的类型,即object.type。可以认为新类型出现了,只不过这个类型并不能用来归类某个对象集合,等同于没有定义新类型。即使是伴生对象也没有定义类型,而是由伴生类定义了同名的类型。后续章节将讲到,单例对象可以继承自超类或混入特质,这样它就能出现在需要超类对象的地方。例如下面的例子中,可以明确看到X.type和Y.type两种新类型出现,并且是不一样的:
scala> object X
defined object X
scala> object Y
defined object Y
scala> var x = X
x: X.type = X$@630bb67
scala> x = Y
<console>:17: error: type mismatch;
found : Y.type
required: X.type
x = Y
^
2、工厂对象与工厂方法
如果定义一个方法专门用来构造某一个类的对象,那么这种方法就称为“工厂方法”。包含这些工厂方法集合的单例对象,也就叫“工厂对象” 。通常,工厂方法会定义在伴生对象里。尤其是当一系列类存在继承关系时,可以在基类的伴生对象里定义一系列对应的工厂方法。使用工厂方法的好处是可以不用直接使用new来实例化对象,改用方法调用,而且方法名可以是任意的,这样对外隐藏了类的实现细节。例如:
// students.scala
class Students(val name: String, var score: Int) {
def exam(s: Int) = score = s
override def toString = name + "'s score is " + score + "."
}
object Students {
def registerStu(name: String, score: Int) = new Students(name, score)
}
将文件students.scala编译后,并在解释器里用“import Students._”导入单例对象后,就能这样使用:
scala> import Students._
import Students._
scala> val stu = registerStu("Tim", 100)
stu: Students = Tim's score is 100.
3、apply方法
有一个特殊的方法名——apply,如果定义了这个方法,那么既可以显式调用——“对象.apply(参数)” ,也可以隐式调用——“对象(参数)”。隐式调用时,编译器会自动插入缺失的“.apply”。如果apply是无参方法,应该写出空括号,否则无法隐式调用。无论是类还是单例对象,都能定义这样的apply方法。
通常,在伴生对象里定义名为apply的工厂方法,就能通过“伴生对象名(参数)”来构造一个对象。也常常在类里定义一个与类相关的、具有特定行为的apply方法,让使用者可以隐式调用,进而隐藏相应的实现细节。例如:
// students2.scala
class Students2(val name: String, var score: Int) {
def apply(s: Int) = score = s
def display() = println("Current score is " + score + ".")
override def toString = name + "'s score is " + score + "."
}
object Students2 {
def apply(name: String, score: Int) = new Students2(name, score)
}
将文件students2.scala编译后,就能在解释器里这样使用:
scala> val stu2 = Students2("Jack", 60)
stu2: Students2 = Jack's score is 60.
scala> stu2(80)
scala> stu2.display
Current score is 80.
其中,“Students2(“Jack”, 60)”被翻译成“Students2.apply(“Jack”, 60)” ,也就是调用了伴生对象里的工厂方法,所以构造了一个Students2的对象并赋给变量stu2。“stu2(80)”被翻译成“stu2.apply(80)” ,也就是更新了字段score的数据。
4、主函数
主函数是Scala程序唯一的入口,即程序是从主函数开始运行的。要提供这样的入口,则必须在某个单例对象里定义一个名为“main”的函数,而且该函数只有一个参数,类型为字符串数组Array[String],函数的返回类型是Unit。任何符合条件的单例对象都能成为程序的入口。例如:
// students2.scala
class Students2(val name: String, var score: Int) {
def apply(s: Int) = score = s
def display() = println("Current score is " + score + ".")
override def toString = name + "'s score is " + score + "."
}
object Students2 {
def apply(name: String, score: Int) = new Students2(name, score)
}
// main.scala
object Start {
def main(args: Array[String]) = {
try {
val score = args(1).toInt
val s = Students2(args(0), score)
println(s.toString)
} catch {
case ex: ArrayIndexOutOfBoundsException => println("Arguments are deficient!")
case ex: NumberFormatException => println("Second argument must be a Int!")
}
}
}
使用命令“scalac students2.scala main.scala”将两个文件编译后,就能用命令“scala Start 参数1 参数2”来运行程序。命令里的“Start”就是包含主函数的单例对象的名字,后面可以输入若干个用空格间隔的参数。这些参数被打包成字符串数组供主函数使用,也就是代码里的args(0)、args(1)。例如:
PS E:\Microsoft VS\Scala> scala Start Tom
Arguments are deficient!
PS E:\Microsoft VS\Scala> scala Start Tom aaa
Second argument must be a Int!
PS E:\Microsoft VS\Scala> scala Start Tom 100
Tom's score is 100.
主函数的一种简化写法是让单例对象混入“App”特质(特质在后续章节讲解),这样就只要在单例对象里编写主函数的函数体。例如:
// main2.scala
object Start2 extends App {
try {
var sum = 0
for(arg <- args) {
sum += arg.toInt
}
println("sum = " + sum)
} catch {
case ex: NumberFormatException => println("Arguments must be Int!")
}
}
将文件编译后,就可以如下使用:
PS E:\Microsoft VS\Scala> scala Start2 10 -8 20 AAA
Arguments must be Int!
PS E:\Microsoft VS\Scala> scala Start2 10 -8 20 8
sum = 30
四、for表达式与for循环
要实现循环,在Scala里推荐使用for表达式。不过,Scala的for表达式是函数式风格的,没有引入指令式风格的“for(i = 0; i < N; i++)”。一个Scala的for表达式的一般形式如下:
for( seq ) yield expression
整个for表达式算一个语句。在这里,seq代表一个序列。换句话说,能放进for表达式里的对象,必须是一个可迭代的集合。比如常用的列表(List)、数组(Array)、映射(Map)、区间(Range)、迭代器(Iterator)、流(Stream)和所有的集(Set),它们都混入了特质Iterable。可迭代的集合对象能生成一个迭代器,用该迭代器可以逐个交出集合中的所有元素,进而构成了for表达式所需的序列。关键字“yield”是“产生”的意思,也就是把前面序列里符合条件的元素拿出来,逐个应用到后面的“expression”,得到的所有结果按顺序产生一个新的集合对象。
如果把seq展开来,其形式如下:
for {
p <- persons // 一个生成器
n = p.name // 一个定义
if(n startsWith "To") // 一个过滤器
} yield n
seq是由“生成器”、“定义”和“过滤器”三条语句组成,以分号隔开,或者放在花括号里让编译器自动推断分号。生成器“p <- persons”的右侧就是一个可迭代的集合对象,把它的每个元素逐一拿出来与左侧的模式进行匹配(有关模式匹配请见后续章节)。如果匹配成功,那么模式里的变量就会绑定上该元素对应的部分;如果匹配失败,并不会抛出匹配错误,而是简单地丢弃该元素。
在这个例子里,左侧的p是一个无需定义的变量名,它构成了变量模式,也就是简单地指向persons的每个元素。大多数情况下的for表达式的生成器都是这么简单。定义就是一个赋值语句,这里的n也是一个无需定义的变量名。定义并不常用,比如这里的定义就可有可无。过滤器则是一个if语句,只有if后面的表达式为true时,生成器的元素才会继续向后传递,否则就丢弃该元素。这个例子中,是判断persons的元素的name字段是否以“To”为开头。最后,name以“To”为开头的persons元素会应用到yield后面的表达式,在这里仅仅是保持不变,没有任何操作。总之,这个表达式的结果就是遍历集合persons的元素,按顺序找出所有name以“To”为开头的元素,然后把这些元素组成一个新的集合。例如:
// test.scala
class Person(val name: String)
object Alice extends Person("Alice")
object Tom extends Person("Tom")
object Tony extends Person("Tony")
object Bob extends Person("Bob")
object Todd extends Person("Todd")
val persons = List(Alice, Tom, Tony, Bob, Todd)
val To = for {
p <- persons
n = p.name
if(n startsWith "To")
} yield n
println(To)
PS E:\Microsoft VS\Scala> scala test.scala
List(Tom, Tony, Todd)
每个for表达式都以生成器开始。如果一个for表达式中有多个生成器,那么出现在后面的生成器比出现在前面的生成器变得更频繁,也就是指令式编程里的嵌套的for循环。例如计算乘法口诀表:
scala> for {
| i <- 1 to 9
| j <- i to 9
| } yield i * j
res0: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5, 6,
7, 8, 9, 4, 6, 8, 10, 12, 14, 16, 18, 9, 12, 15, 18, 21, 24, 27, 16, 20, 24,
28, 32, 36, 25, 30, 35, 40, 45, 36, 42, 48, 54, 49, 56, 63, 64, 72, 81)
如果只想把每个元素应用到一个Unit类型的表达式,那么就是一个“for循环”
,而不再是一个“for表达式”
。关键字“yield”也可以省略。例如:
scala> var sum = 0
sum: Int = 0
scala> for(x <- 1 to 100) sum += x
scala> sum
res0: Int = 5050
五、match表达式
match表达式的作用相当于“switch”,也就是把作用对象与定义的模式逐个比较,按匹配的模式执行相应的操作。例如:
scala> def something(x: String) = x match {
| case "Apple" => println("Fruit!")
| case "Tomato" => println("Vegetable!")
| case "Cola" => println("Beverage!")
| case _ => println("Huh?")
| }
something: (x: String)Unit
scala> something("Cola")
Beverage!
scala> something("Toy")
Huh?
六、隐式参数
函数最后一个参数列表可以用关键字“implicit”声明为隐式的,这样整个参数列表的参数都是隐式参数。注意,是整个参数列表,即使括号里有多个参数,也只需要开头写一个“implicit”。而且每个参数都是隐式的,不存在部分隐式部分显式。
当调用函数时,若缺省了隐式参数列表,则编译器会尝试插入相应的隐式定义。当然,也可以显式给出参数,但是要么全部缺省,要么全部显式给出,不能只写一部分。
要让编译器隐式插入参数,就必须事先定义好符合预期类型的隐式变量(val和var可以混用,关键在于类型)、隐式单例对象或隐式函数(别忘了函数也能作为函数的参数进行传递),这些隐式定义也必须用“implicit”修饰。隐式变量、单例对象、函数在当前作用域的引用也必须满足“单标识符”原则,即不同层次之间需要用“import”来解决。
隐式参数的类型应该是“稀有”或“特定”的,类型名称最好能表明该参数的作用。如果直接使用Int、Boolean、String等常用类型,容易引发混乱。例如:
// test.scala
class PreferredPrompt(val preference: String)
class PreferredDrink(val preference: String)
object Greeter {
def greet(name: String)(implicit prompt: PreferredPrompt,
drink: PreferredDrink) = {
println("Welcome, " + name + ". The system is ready.")
print("But while you work, ")
println("why not enjoy a cup of " + drink.preference + "?")
println(prompt.preference)
}
}
object JoesPrefs {
implicit val prompt = new PreferredPrompt("Yes, master> ")
implicit val drink = new PreferredDrink("tea")
}
scala> Greeter.greet("Joe")
<console>:12: error: could not find implicit value for parameter prompt: PreferredPrompt
Greeter.greet("Joe")
^
scala> import JoesPrefs._
import JoesPrefs._
scala> Greeter.greet("Joe")
Welcome, Joe. The system is ready.
But while you work, why not enjoy a cup of tea?
Yes, master>
scala> Greeter.greet("Joe")(prompt, drink)
Welcome, Joe. The system is ready.
But while you work, why not enjoy a cup of tea?
Yes, master>
scala> Greeter.greet("Joe")(prompt)
<console>:15: error: not enough arguments for method greet: (implicit
prompt: PreferredPrompt, implicit drink: PreferredDrink)Unit.
Unspecified value parameter drink.
Greeter.greet("Joe")(prompt)